Caveman vs Headroom: Which Tokens Actually Get Saved?

56 isolated Opus runs, real benchmark tasks, every token classified — which Claude Code token saver actually works, where — and the verdict a harness bug almost stole.

Verdict#

A tie at the top: caveman and the stack both cut cost per solved task −30%; headroom −18%. Quality is identical across all four — 10/14 each. (An earlier verdict had caveman “breaking a task” — that was our harness, not the tool; § why tasks failed.)

~460k tokens read per run vs ~6k written. Caveman shrinks the 6k; headroom shrinks the 460k.

The matchup#

Caveman makes Claude answer in fragments; headroom compresses what Claude reads. One figure each.

Fig. 1 — Caveman shortens the output path
CAVEMAN ruleset injected at session start Context read as-is 308.0k tokens* CLAUDE Answer′ fragments · code intact 3.4k (−42%) price: +1.6k prompt tax · reasoning −42% — no measured quality cost (§ why tasks failed)

* a shorter transcript also shrinks the replayed context (−33%) — a second-order effect, not compression.

Where it sits: an ordinary plugin — a SessionStart hook injects the ruleset into context, and the model itself changes register.

  • answer text −47% (1.1k → 590 tok/run): explanations and summaries become fragments
  • reasoning −42% (3.8k → 2.2k): the thinking between tool calls shortens with the register
  • tool calls −37% (953 → 605): fewer, tighter calls — the code written is untouched
  • knock-on: cache-read −33% (462k → 308k): a shorter transcript replays less history each turn
Fig. 2 — Headroom compresses the input path
Context files · logs · history 461.9k tokens HEADROOM compresses in flight Context′ same info 254.3k (−45%) CLAUDE Answer unchanged 6.7k ≈ behavior identical — same fixes, same turns · price: +54% wall time

Where it sits: a localhost proxy via ANTHROPIC_BASE_URL. Nothing inside Claude Code changes — so behavior doesn't either.

  • what it compresses: the old tool results re-sent every turn — file contents, pytest/log dumps, JSON — each block typed and routed (JSON structurally crushed · code trimmed AST-aware · prose/logs via a small model)
  • cache-read −45% (462k → 254k tok/run): the history is compressed once, and the saving repeats every turn after
  • what it never touches: everything Claude generates — reasoning, answers, tool calls all ≈ unchanged; same fixes, same turn count

Method#

modelconditionstaskstrialstotal runswallerrors
claude-opus-4-8472562h16m0
tasksourcewhat the agent must do
C1SWE-bench Verifiedpylint — fix a crash when the container CPU quota is fractional
C2SWE-bench Verifiedpytest — fix a regression in dynamically-added xfail markers
C3SWE-bench Verifiedsphinx — fix literalinclude prepend/dedent whitespace handling
C4writing taskcontributor onboarding doc for the pylint codebase
O1SpreadsheetBenchmatch items across columns, return positions
O2SpreadsheetBenchassign unique code numbers by formula
O3SpreadsheetBenchcumulative balance across 2.7 MB workbooks

Where the tokens go#

Every token the model touched — true scale
cache-read (context replayed)cache-writeoutput — everything the model generated
baseline cache-read 461.9k cache-write 24.3k output 5.8k 492k caveman cache-read 308.0k cache-write 19.5k fresh input 1.6k — the skill tax output 3.4k 333k −32% headroom cache-read 254.3k cache-write 21.8k output 6.7k 283k −43% both cache-read 236.0k cache-write 19.2k fresh input 1.6k — the skill tax output 4.9k 261k −47%

replayed context vs output ≈ 80:1. berry tick = all generated output; caveman's −42% cut is invisible at this scale — the point.

TOKENS — MEAN PER RUN
QUALITY
TOKENS — MEAN PER RUN
QUALITY

What a solved task costs#

Divide by solved tasks and the podium reorders.

baseline baseline: $0.740 per solved task $0.74 · 10/14 caveman caveman: $0.514 per solved task — tied winner $0.51 · 10/14 · −30% headroom headroom: $0.604 per solved task $0.60 · 10/14 both both: $0.516 per solved task — the winner $0.52 · 10/14 · −30% $0 $0.74 = baseline

caveman and the stack tie at −30%. an earlier version of this chart had caveman third — a stale-bytecode harness bug had failed its correct sphinx fixes (§ why tasks failed).

conditionsolvedest $/runest $/solvedwall s/run
baseline10/140.5280.74096
caveman10/140.3670.514 −30%60
headroom10/140.4310.604 −18%148
both10/140.3690.516 −30%98

Per task#

Cost vs baseline, per task (pass rate). C = coding, O = spreadsheets.

taskbaselinecavemanheadroomboth
C1 · pylint bugfixref (2/2)−40% (2/2)−57% (2/2)−57% (2/2)
C2 · pytest bugfixref (2/2)+5% (2/2)−41% (2/2)+20% (2/2)
C3 · sphinx bugfixref (2/2)−68% (2/2)−10% (2/2)−42% (2/2)
C4 · onboarding docref (2/2)−15% (2/2)−13% (2/2)−21% (2/2)
O1 · spreadsheetref (0/2)−24% (0/2)−29% (0/2)−48% (0/2)
O2 · spreadsheetref (2/2)−12% (2/2)−35% (2/2)−29% (2/2)
O3 · spreadsheet 2.7MBref (0/2)−13% (0/2)+21% (0/2)−47% (0/2)

no condition separates on quality. O1/O3 are four-way ties (§ why tasks failed); C3's −68% is caveman's biggest legitimate saving. C4, pure writing: −15%.

Why tasks failed#

taskwho failedwhyclassification
C3 · sphinx caveman 0/2
overturned
The gate executed stale bytecode: Apple's shared pyc cache invalidates by (whole-second mtime, file size); caveman's fix was a same-size line reorder that landed in the same second as the pre-fix compile. Its patch was byte-identical to baseline's passing fix. Re-gated on fresh bytecode: 2/2. harness artifact — caught by forensics, gate fixed, verdict reversed
O1 · spreadsheet all four
(0/2 each)
2 of 3 workbooks perfect. Ground truth wants a blank when a lookup has no match (IFERROR(…,"")); every condition wrote Excel-style #N/A instead. benchmark semantics — four-way tie, no distortion
O3 · 2.7 MB all four
(0/2 each)
2 of 3 correct. Ground truth blanks the running balance on rows with no activity (IF(A21=0,"",…)); every condition kept computing the cumulative sum. benchmark semantics — four-way tie, no distortion

After the appeal: zero failures separate the conditions — 10/14 everywhere. The scariest finding reversed on forensics: caveman's speed didn't break the sphinx task, it outran the bytecode cache's one-second mtime resolution and broke our gate. Its −42% reasoning cut shows no measured quality cost in this battery — though N=2 says treat that as absence of evidence, not proof of safety.

Caveats#

  • N=2 — big deltas robust; single-cell effects are flags, not convictions (C3 proved it: the flag was our bug).
  • One model, one battery — each tool's ceiling moves with task mix.
  • Native SWE-bench envs (no Docker; official test patches at gate time) — token measurement, not official scores.
  • Three harness bugs caught and fixed (per-block stream events; a test-path false-flag; a stale-bytecode gate). C2 re-gated 0/8 → 8/8; C3 caveman 0/2 → 2/2.

Reproduce#

python3 fetch_tasks.py            # pin benchmark instances
python3 gym.py smoke              # auth + activation checks
python3 gym.py run --phase full   # 4 conditions × 7 tasks × 2 trials
python3 analyze.py full           # summary + results.json

isolation per run: fresh workspace · --setting-sources "" · pinned tools · scrubbed env · session-scoped plugin/proxy injection · no session persistence

References#

  1. 1caveman — JuliusBrussee/caveman, vendored at ec83e5b
  2. 2headroom — headroomlabs-ai/headroom, PyPI headroom-ai 0.34.0 (proxy mode, defaults)
  3. 3SWE-bench Verified — princeton-nlp, instances pylint-6903 · pytest-7490 · sphinx-10323, official FAIL_TO_PASS/PASS_TO_PASS gates
  4. 4SpreadsheetBench — RUCKBReasoning, instances 59055 · 13894 · 55392, cell-level comparator port
  5. 5runner, analyzer, raw event logs, and per-run gates: the skill-gym repo