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.
uvx --from git+https://github.com/LemonCANDY42/compactlab compactlab demoBuilds 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).
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.
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 |
|---|---|---|
![]() |
![]() |
![]() |
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.)
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.
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).
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-runRescans 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.
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 --demo # synthetic data; keys: ? help, p profile, % unit, c copy, r recalibrate, q quitDetails and key bindings: docs/TUI.md.
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.
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 runIf measured recall at a profile's threshold falls below the floor, deep lowers that profile and says so.
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]
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.
| 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.
- Offline by default. Lite mode makes no network and no model calls. Only
pricing refreshfetches public prices, and only deep mode calls a model, and only after you confirm. - Nothing leaves your machine in lite mode. Reports and
result.jsonhold token counts, model names and session ids, never transcript text or file paths.report --redacthashes the ids. - Config changes are opt-in and reversible.
applyis a dry-run diff. Writing needs--writeplus a pre-authorization file (allowed files, keys, models, value bounds, expiry) or--i-authorize. Every write is backed up, andrevertrestores only the keys it changed. - Sharing is a file you post yourself.
compactlab sharewrites 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-cacheorcompactlab cache clear.
- 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 showlists the source of every row; refresh or override them as needed. - Some log-format details (Codex fork replay, compaction events) are marked
assumedindata/harness_formats.jsonuntil checked against more real files. - Tested on macOS; Linux paths follow the same defaults and run in CI. Windows is untested.
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.
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.
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.


