Skip to content

About

Measure where to auto-compact from your own agent history: cost vs context-rot trade-offs per model and role. Local, zero-token, MIT.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

compactlab

Your agent history says where to auto-compact.
Measure cost vs. context-rot trade-offs per model and per role, then tune auto-compact safely.

CI License: MIT Python 3.11+

compactlab demo printing cost, balanced and time thresholds per model and role (synthetic data)
uvx --from git+https://github.com/LemonCANDY42/compactlab compactlab demo

Builds a report from bundled synthetic sessions and opens it. No history or API key needed; uvx fetches compactlab from GitHub.


Claude Code and Codex compact a conversation when it reaches a token threshold. Set it too low and you pay for frequent summaries, cache rewrites and re-reading what the summary dropped. Set it too high and every turn re-reads a huge, cached-but-not-free prefix, and long contexts get less reliable. The right number depends on how your sessions grow, your models and their prices. compactlab replays your own session logs at every threshold and tells you where the trade-off sits, per model and per role (main agent vs subagent).

compactlab HTML report: group sidebar, profile switch, KPI cards and per-day impact of each profile (synthetic demo data)

Quickstart

uv tool install git+https://github.com/LemonCANDY42/compactlab   # or: pipx install git+https://github.com/LemonCANDY42/compactlab

compactlab demo                       # synthetic data -> report (zero config)
compactlab doctor                     # where are my logs and configs?
compactlab report --open              # lite analysis of your last 30 days -> HTML report
compactlab simulate --out result.json # same analysis, saved for apply
compactlab apply --result result.json --claude-settings ~/.claude/settings.json   # dry-run diff
compactlab verify                     # days later: did the change do what the replay predicted?

compactlab installs from GitHub (it is not on PyPI). Python 3.11+, no runtime dependencies.

What you get

For each model and role, three profiles, side by side, each compared with the harness default (what happens if you change nothing) and with your current setting when compactlab can read it. A profile is the price you put on an hour of agent wall-clock, plus guardrails:

Profile Value of time Guardrails
cost $10/h (money first) none on window size; warns below 25% of the max window and when compaction wait grows by more than 30 min/day
balanced (default) $250/h never below 40% of the max window; at most 30 min/day of added compaction wait
time $600/h (time first) never below 50% of the max window; at most 30 min/day of added compaction wait

Within its guardrails, each profile picks the largest threshold whose score (list-price replay cost + re-discovery penalty + value of time × wall-clock) is within 2% of its lowest, with assumed recall at or above a floor (0.90). --profile chooses which one apply, recommend and the report lead with; --value-of-time overrides that profile's price of time. Both work on a saved result without re-running. no action needed is shown instead of a number when the default never compacts in those sessions, and keep default when a profile would not beat it.

Every profile reports Δcost, Δwall-clock in hours and minutes per day (time saved by smaller contexts minus time added by compactions and re-discovery, fitted from your own timestamps), compactions per day and recall against the default and your current setting. Costs are list-price estimates (no discounts, plans or credits). Recall in lite mode is an assumed proxy, not a measurement. When a group has too few timed compactions (fewer than 20) its latency fit is not used: it borrows the pooled fit of all sessions of that harness, or a deliberately pessimistic prior, and says so.

Sample output (synthetic demo data, not a benchmark; output is bilingual by default, --lang en shown; * marks the selected profile):

claude-opus-5-5 · main · 6 sessions · 1308 turns · 10 days · window 1000k
  profile            window action                   Δcost vs default     Δtime (/day)  compactions/d           recall
  default              967k -                             $403 (base)                0            0.8            0.871
  current              600k -                            -$102 (-25%)        -1m (-0m)            1.4            0.909
  balanced*            500k set                          -$135 (-33%)      -0.1h (-1m)            1.7   0.916 (+0.045)
  cost                 175k set                          -$233 (-58%)      -0.2h (-1m)            6.5   0.922 (+0.051)
  time                 650k set                           -$88 (-22%)        -6m (-1m)            1.2   0.902 (+0.030)
  · vs current: cost -$131 (-43%) -0.2h; balanced -$32 (-11%) -0.1h; time +$14 (+5%) -4m
  · time model: turn latency from this group's data, compaction latency from this group's data (22 own samples)
  · balanced: guardrail moved the pick from 300k to 500k
  · cost: below 25% of the max window; expect frequent lossy compactions
  · time: guardrail moved the pick from 325k to 650k
  · default: models with a native 1M window compact at about 967K tokens by default; current: modelSettings.claude-opus-5-5.autoCompactWindow in settings.json

claude-opus-5-5 · subagent · 3 sessions · 143 turns · 4 days · window 1000k
  profile            window action                   Δcost vs default     Δtime (/day)  compactions/d           recall
  default              967k -                              $18 (base)                0            0.0            0.937
  current              600k -                               +$0 (+0%)        +0m (+0m)            0.0            0.937
  balanced*               - no action needed                        -                -            0.0            0.937
  cost                    - no action needed                        -                -            0.0            0.937
  time                    - no action needed                        -                -            0.0            0.937
  · no action needed: the default never compacts in these sessions
  · time model: turn latency from all claude-code sessions (this group is thin), compaction latency from all claude-code sessions (this group is thin) (0 own samples)
  · default: models with a native 1M window compact at about 967K tokens by default; current: modelSettings.claude-opus-5-5.autoCompactWindow in settings.json

The HTML report is one offline file (no fonts, scripts or images fetched), in English or Chinese (follows the browser language; one toggle switches), light or dark (follows the system, or pick one). It leans on icons, delta chips (▼ green saves, ▲ amber adds) and small gauges rather than prose; explanations sit behind ⓘ tooltips and warnings are ⚠ icons with a tooltip. A sidebar lists each harness / model / role group (a top bar on phones); the header states the history's date range, data source and mode. A sticky control bar holds the profile switch and a threshold slider with marks for each profile, the default and your current setting. Below it: KPI cards for the slider position, the per-day impact of every profile, a comparison table, a Pareto frontier and cost and wall-clock curves with hover/tap tooltips and direct labels, where your tokens sit by context size, and any session's real context curve next to the same session replayed at the chosen threshold. Thin data, short histories and borrowed time models are called out in plain language.

Light Dark Phone
report, light report, dark report on a phone

Per-day impact

For each profile of each group, versus the harness default and versus your current setting: money saved or added (USD), wall-clock saved or added (minutes, with compaction wait and the turn-speed gain from a smaller context shown separately), and compactions, each per calendar day, per active day and per month (30 days). The calendar span is the history window actually covered; an active day is a local date with at least one turn. Dividing by calendar days understates a heavy user's day, so both are shown. Sign: -$204.8/day means it saves $204.8 a day; + adds. The report colours savings green and added cost or time amber, and keeps red for broken guardrails. Money is a list-price estimate and recall is a lite-mode proxy. compactlab recommend prints the same table after the snippets (--json adds it under impact):

Per-day impact · claude-opus-5-5 · main · history 10.5 calendar days, 10.5 active days (with ≥1 turn)
  vs harness default 967k
    profile         window      $/day  $/active d     $/month   min/day     wait    turns  min/active d   h/month  compact/d  /month
    balanced*         500k     -$12.9      -$12.9     -$386.1      -0.8     +2.0     -2.9          -0.8      -0.4        1.7      52
    cost              175k     -$22.3      -$22.3     -$668.0      -1.4     +7.8     -9.2          -1.4      -0.7        6.5     195
    time              650k      -$8.4       -$8.4     -$252.6      -0.6     +1.1     -1.7          -0.6      -0.3        1.2      37
  vs your current 600k
    profile         window      $/day  $/active d     $/month   min/day     wait    turns  min/active d   h/month  compact/d  /month
    balanced*         500k      -$3.1       -$3.1      -$93.1      -0.7     +0.1     -0.8          -0.7      -0.3        1.7      52
    cost              175k     -$12.5      -$12.5     -$375.0      -1.2     +5.9     -7.1          -1.2      -0.6        6.5     195
    time              650k      +$1.3       +$1.3      +$40.4      -0.4     -0.8     +0.4          -0.4      -0.2        1.2      37

(Synthetic demo data, --lang en. The demo spreads sessions over every day, so active days equal calendar days.)

Copy the settings

compactlab recommend                                  # Claude Code settings.json fragment + Codex config.toml line
compactlab recommend --scope project --copy           # project-level, onto the clipboard (macOS)
compactlab recommend --scope agent                    # subagent frontmatter (autoCompactWindow)
compactlab recommend --format env                     # CLAUDE_CODE_AUTO_COMPACT_WINDOW instead
compactlab recommend --check                          # are the keys still verified against the docs?

Each snippet names its target file and carries a merge one-liner that leaves other keys alone, the evidence it came from (groups, delta vs default, time per day, where the time model came from) and a revert hint. --profile picks the profile (default balanced); --all-profiles prints all three. Values never exceed the model's window or your caps, and groups that need no change are listed as omitted. recommend only prints or copies; apply is still the only command that writes, under pre-authorization. The report has the same panel with copy buttons.

Harness Scope Key Status
Claude Code global / project modelSettings.<model>.autoCompactWindow (v2.1.288+) documented
Claude Code agent subagent frontmatter autoCompactWindow (v2.1.296+, lowers only) documented
Claude Code session CLAUDE_CODE_AUTO_COMPACT_WINDOW env (wins over all), --autocompact flag documented
Codex global / project top-level model_auto_compact_token_limit (project needs trust) documented
Codex session codex -c model_auto_compact_token_limit=N documented
Codex agent / profile role config_file layer, profile file unverified: emitted with a warning
Codex env var none documented not emitted

Facts, sources and verified-on dates live in data/config_knobs.json.

Check the prediction afterwards

compactlab verify splits your logs at the time of the last apply and compares, per model and role, the logged cost and compactions per 1k turns before and after with what a replay of the before sessions predicted (plus a replay of the after sessions, to show when the workload changed). It also checks that compactions really happen near the new threshold, which catches a setting that a project file, env var or CLI flag overrides. It says "too early" until each side has 3 sessions and 2 active days. Cost is the list-price figure, not your invoice, and wall-clock is not verified. Use --id for an older apply. For a threshold you set by hand, describe it instead: compactlab verify --applied-at 2026-10-29T21:05 --model claude-opus-5-5 --new 625k [--old 967k] (the time and old value are yours; it says so when sessions before that time already compacted near the new threshold, i.e. you gave a too-late date).

Run as a service

compactlab serve --every 1d                 # localhost-only live report + JSON API, recalibrates daily (lite)
compactlab schedule print                   # launchd / systemd files to run it unattended; install writes, you load
compactlab consent set --file ~/.claude/settings.json --models claude-opus-5-5   # optional: allow auto-apply
compactlab recalibrate --auto-apply --dry-run

Rescans are incremental, one run at a time, at low priority, with no network. Auto-apply is opt-in and goes through apply's backups and manifests (compactlab revert undoes it). It moves a setting at most 100k per run, at most weekly, only for a clear benefit, and never over a value someone else edited. Details and threat model: docs/SERVICE.md.

In the terminal

On a terminal, demo, simulate, recommend, doctor and serve draw a compact view (colour, boxes, a threshold gauge, was/now bars); piped or with --plain they print the full plain tables. compactlab top is a live, htop-like view of the latest result: models on the left, the selected one's cost and time curve on the right.

compactlab top: models with was/now bars on the left, the selected model's cost and time curves over the threshold on the right (synthetic data)

compactlab top --demo     # synthetic data; keys: ? help, p profile, % unit, c copy, r recalibrate, q quit

Details and key bindings: docs/TUI.md.

In-session lens (Claude Code mod)

mods/compactlab-lens is an optional Claude Code mod. It adds one row above the prompt, and a pane on desktop, with this session's context against the default and the compactlab threshold, the per-day savings simulate estimated, and a ⚠ when the window in force differs. It reads only ~/.compactlab/lens.json, which simulate writes. It never writes settings, calls a model or uses the network. Its "copy" button puts /autocompact <n> on your clipboard.

▸ ●●●●●●●●●●·┃··········┊· 412k ┊967k ┃500k ▼−$204.8/day ▲+4.8m/day ⟳0 ⚠
/plugin install compactlab-lens --marketplace LemonCANDY42/compactlab

See docs/MOD.md. The mod is an independent open-source project, not affiliated with Anthropic.

Two modes

lite (default) deep (opt-in)
Inputs your logs + price table lite + a model you choose, via Claude Code or Codex
Does replay simulation, cost model, re-discovery penalty, summary fact-check heuristics re-summarizes sampled sessions at candidate thresholds, probes them for ground-truth facts, LLM judge on a different model
Recall assumed proxy measured on samples; disagreements with lite are shown
Cost $0, offline shows an estimate first, needs --yes, stops at --budget (checked before each call and against measured spend after it; one call can overshoot the cap)
compactlab simulate --mode deep --executor claude-code --model claude-sonnet-5-5 \
  --judge-model claude-haiku-5-5 --budget 3            # prints the estimate; add --yes to run

If measured recall at a profile's threshold falls below the floor, deep lowers that profile and says so.

How it works

flowchart LR
  L[Claude Code / Codex JSONL] -->|adapters, incremental cache| S[sessions]
  S --> R[replay at every threshold]
  P[prices with provenance] --> R
  S --> C[calibrate re-discovery penalty]
  C --> R
  R --> T[cost / balanced / time profiles + guardrails, Pareto]
  T --> J[(result.json)]
  J --> H[report.html]
  J --> A[apply: dry-run, backup, revert]
Loading

Each session becomes the tokens added per turn. compactlab replays that growth for each threshold. It charges cache reads, cache writes (after a compaction, or after an idle gap longer than the cache TTL, which it reads from the logs: 1 hour or 5 minutes, at that tier's write price), output, a summarizer call per compaction, and the re-reads and corrections that tend to follow one. It also adds up wall-clock time from latency curves fitted to your own timestamps. Details and formulas: docs/METHODOLOGY.md. Background: docs/WHY_THRESHOLDS.md.

Supported harnesses

Harness Logs Setting apply writes Status
Claude Code ~/.claude/projects/**/*.jsonl (incl. subagents/, $CLAUDE_CONFIG_DIR) modelSettings.<model>.autoCompactWindow (per model) supported
Codex ~/.codex/sessions/**/rollout-*.jsonl ($CODEX_HOME) top-level model_auto_compact_token_limit (global only) supported
DeepSeek Harness (dsh) ~/.dsh/sessions/**/session[.vN].jsonl[.zstd] ($DSH_HOME; zstd needs Python 3.14+ or the zstd command) none: recommend prints a modelPolicies entry (thresholdRatio) for the compaction-basic plugin experimental: built from dsh's source and snapshot logs, not yet run on real history
OpenCode, Gemini CLI, Hermes, Qwen Code, Kimi CLI, Goose planned, see the roadmap (adapter guide)

None of these harnesses lets subagents have their own threshold, so apply writes the main-role value. dsh reads its threshold as a fraction of the window (default: the lower of 80% and window − 256k output reserve − 65,536, about 727k on DeepSeek's 1M models); where its plugin config lives is not verified, so apply leaves it alone.

Safety and privacy

  • Offline by default. Lite mode makes no network and no model calls. Only pricing refresh fetches public prices, and only deep mode calls a model, and only after you confirm.
  • Nothing leaves your machine in lite mode. Reports and result.json hold token counts, model names and session ids, never transcript text or file paths. report --redact hashes the ids.
  • Config changes are opt-in and reversible. apply is a dry-run diff. Writing needs --write plus a pre-authorization file (allowed files, keys, models, value bounds, expiry) or --i-authorize. Every write is backed up, and revert restores only the keys it changed.
  • Sharing is a file you post yourself. compactlab share writes an anonymous record of the estimated savings (numbers and model names, no ids, paths or timestamps) for the research issue form; it sends nothing. See docs/SHARING.md.
  • The scan cache (~/.compactlab/cache, mode 0600) stores short excerpts. Use --no-cache or compactlab cache clear.

Limitations

  • Lite-mode recall is an assumed curve, not a measurement of any model. Deep mode measures recall on samples of compactlab's excerpts, not the full transcript the harness would summarize.
  • Replayed cost is a list-price estimate for comparing thresholds, not a reconstruction of your invoice (subscriptions, discounts and credits are ignored). Wall-clock estimates come from log timestamps (response time minus the preceding input), a proxy that is flagged when data is thin.
  • The bundled prices are a dated public snapshot (models.dev). compactlab pricing show lists the source of every row; refresh or override them as needed.
  • Some log-format details (Codex fork replay, compaction events) are marked assumed in data/harness_formats.json until checked against more real files.
  • Tested on macOS; Linux paths follow the same defaults and run in CI. Windows is untested.

FAQ

Will it change my settings? Not unless you pass --write with an authorization, and even then only the allow-listed key, with a backup.

Why not just use the lowest-cost threshold? Cost alone favors compacting early, and every compaction costs minutes of waiting and some lost detail. On real histories a cost-first pick can mean several times more compactions than the default. The default balanced profile prices your time at $250/h and never goes below 40% of the window; --profile cost is there if money is what you want to minimize.

Does it work without any history? compactlab demo uses bundled synthetic sessions.

What is the baseline? The harness default from its docs or source: about 967K tokens for Claude models with a native 1M window, 200K for Sonnet/Opus 4.6 without extended context, the context limit otherwise; 90% of the context window for Codex. Sources are in data/harness_defaults.json.

Can I trust the recall numbers? In lite mode, no. Treat them as a guard-rail you can configure. Deep mode gives sampled measurements with their own noise.

Related work

ccusage and CodexBar track token usage and cost from the same logs. compactlab borrows parsing ideas from them, not code. Chroma's Context Rot report covers long-context degradation, and LangWatch's study on optimal context windows looks at the same trade-off on its own agents.

Contributing

Adapters, pricing updates and new fact-check heuristics are welcome. See CONTRIBUTING.md and docs/ADDING_A_HARNESS.md. Tests use synthetic data only. Run python scripts/secrets_scan.py --all before pushing.

License

MIT. 中文说明

About

Measure where to auto-compact from your own agent history: cost vs context-rot trade-offs per model and role. Local, zero-token, MIT.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages