Thirty days. One project. 3.6 billion tokens of input sent to the API. The number comes from the transcripts of my own Claude Code sessions, not from a vendor’s slide. Ninety-eight percent came back from the provider’s cache at a tenth of the price — and the average request still carried half a million tokens of context.
Half a million tokens is a book. Every request re-sends the book.
That is what talking to a model with no memory means. It is a courier who carries a dossier between you and a specialist and, at every visit, reads the specialist the entire file from page one — the new page and the four hundred before it — because nothing is kept between visits. The specialist charges by the word.
Headroom proposes to put an editor at the door.
What the editor is
Headroom is an open-source
(Apache 2.0) proxy that runs on your own machine. You point the agent at
http://127.0.0.1:8787 instead of the API; the proxy reads each request,
edits it, and forwards the result. Nothing leaves your computer except what
already would have. It speaks the Anthropic, OpenAI and Gemini wire formats,
so the same process serves Claude Code, Codex, Cursor, Aider and a dozen
others.
For Claude Code specifically, three pieces arrive together:
- the proxy, the editor itself;
- a plugin, two hooks that check at session start (and before every shell command) that the proxy is alive, and start it if it is not;
- an MCP server, three tools the model can call:
headroom_compress,headroom_stats, and the one that matters,headroom_retrieve.
What it does to the dossier
Four moves, in order.
It reads before it cuts. A content router classifies every block of the conversation — a tool’s output, a log, a JSON payload, a source file, your own prose — and picks a compressor to match. JSON gets a structural crusher that keeps the shape and drops the repetition. Logs and tables get format-native, lossless compaction. Code goes through an AST-aware pass that keeps signatures and drops bodies the model already saw. Prose goes to a small trained model that keeps the sentences that carry weight. Blocks that appear twice across turns are sent once.
Every cut leaves a receipt. This is the part that makes the rest
acceptable. When a compressor drops something, it stores the original in a
local SQLite vault and leaves a marker in its place: <<ccr:hash>>. If the
model later needs what was cut, it calls headroom_retrieve with the hash
and gets the original back. Headroom calls this CCR — compress, cache,
retrieve. The count of retrievals is the alarm bell: if it grows, the editor
is cutting things the specialist actually needed.
It never rewrites a page the specialist has memorized. Providers cache the prefix of a conversation and charge a tenth for the part they already hold. An editor who reorders old pages breaks that cache and costs more than he saves. Headroom tracks which prefix the provider has seen and freezes it.
It keeps the tool catalogue at the door. A Claude Code session with a few
MCP servers can carry hundreds of tool definitions — schemas the model reads
on every request and uses on almost none. With tool search on, only the
names travel; a schema is fetched when the model reaches for that tool. This
is not Headroom’s invention — it is a Claude Code feature — but the proxy
insists on it, for a reason worth knowing: the moment you set a custom
ANTHROPIC_BASE_URL, Claude Code stops deferring schemas unless
ENABLE_TOOL_SEARCH=true is set as well. Route through any proxy without
that variable and your context fills with catalogue.
Two modes, one tension
The editor can be timid or bold.
In cache mode, the default, he touches only the newest turn. Every earlier page stays byte-for-byte as the provider cached it. Safe, cheap, and — by design — small: most of a long conversation is old pages, and old pages are off limits.
In token mode, he may go back and rewrite earlier turns. The dossier gets shorter; the provider’s cache for that request is broken and rebuilt. You trade a one-time cache miss for a permanently smaller file. Headroom’s own estimate is that this stretches a session by a quarter to a third before the context fills.
I learned the difference the honest way. For four days the proxy ran on a real project in cache mode, by accident — a process started by hand had ignored the mode I had configured. Just over 700 million tokens went through it. The message compressor removed 1.7 million of them: a quarter of one percent. The dashboard cheerfully reported thousands of dollars in “cache savings”, which were the provider’s cache, which predates the proxy and owes it nothing. The only layer that pulled real weight was the tool catalogue: 29.6 million schema tokens kept out of the context window.
A quarter of one percent is not a verdict on Headroom. It is a verdict on running it in the wrong mode and trusting a dashboard. So the test restarts tomorrow, in token mode, with rules written down first.
Setting it up as I did today
The CLI installs the same way everywhere; a Python 3.13 tool in its own environment:
# 1. Install the CLI in its own environment (Linux, macOS, Windows).
uv tool install --python 3.13 "headroom-ai[all]"
# or: pip install "headroom-ai[all]"
# 2. One command, every OS: a proxy that survives reboots,
# with the test's guardrails. Linux -> systemd user service,
# macOS -> launchd agent, Windows -> Task Scheduler.
headroom install apply \
--preset persistent-service \
--mode token \
--protect-tool-results Read,Edit,Write \
--env HEADROOM_TOOL_DESC_MAX_CHARS=200 \
--env HEADROOM_LOG_FILE="$HOME/.headroom/logs/requests.jsonl" \
--telemetry
# 3. Route Claude Code through it: writes env + hooks into
# .claude/settings.local.json (--global: ~/.claude/settings.json).
# Then check the wiring.
headroom init claude
headroom doctorStep 2 is the portable route: headroom install apply creates a proxy that
survives reboots and, in the same breath, applies the guardrails of the test.
On Linux it becomes a systemd user service; on macOS a launchd agent; on
Windows a Task Scheduler entry (the tool converts the service preset for you).
Step 3 writes ANTHROPIC_BASE_URL and ENABLE_TOOL_SEARCH into the project’s
.claude/settings.local.json, together with the hooks that keep the proxy
alive. headroom doctor then tells you, in one table, whether the proxy is up,
whether Claude is actually routed, and whether your shell is bypassing it.
Under the hood, on Linux, those are three small files, and I prefer to own them. The settings live in one place:
# ~/.headroom/proxy.env — every proxy setting lives here.
# The service reads it; nothing is configured anywhere else.
HEADROOM_MODE=token
HEADROOM_PROTECT_TOOL_RESULTS=Read,Edit,Write
HEADROOM_TOOL_DESC_MAX_CHARS=200
HEADROOM_LOG_FILE=/home/you/.headroom/logs/requests.jsonl
HEADROOM_TELEMETRY=onThe service reads them and restarts the proxy if it ever dies:
# ~/.config/systemd/user/headroom.service
# Enable: systemctl --user daemon-reload
# systemctl --user enable --now headroom.service
[Unit]
Description=Headroom proxy (Claude Code context optimization, token mode)
Documentation=https://github.com/headroomlabs-ai/headroom
After=default.target
StartLimitIntervalSec=60
StartLimitBurst=10
[Service]
Type=simple
EnvironmentFile=%h/.headroom/proxy.env
ExecStart=%h/.local/bin/headroom proxy --host 127.0.0.1 --port 8787
Restart=always
RestartSec=2
OOMPolicy=continue
[Install]
WantedBy=default.targetAnd a wrapper starts Claude through the proxy. I keep plain claude
untouched on purpose — it is the control group:
#!/usr/bin/env bash
# ~/.local/bin/clh — Claude Code through Headroom.
# Plain `claude` stays proxy-free on purpose: it is the control group.
# Usage: clh [claude args] e.g. clh --resume, clh -c
systemctl --user is-active -q headroom.service \
|| systemctl --user start headroom.service
for _ in $(seq 1 30); do
curl -sf -m1 http://127.0.0.1:8787/readyz >/dev/null && break
sleep 0.5
done
exec env ANTHROPIC_BASE_URL=http://127.0.0.1:8787 ENABLE_TOOL_SEARCH=true \
claude --dangerously-skip-permissions "$@"The Windows twin is a PowerShell script on your PATH; the proxy is kept
alive by the scheduled task from step 2:
# clh.ps1 — Windows twin of the Linux wrapper. Put it on your PATH;
# run `clh --resume`. The proxy itself is kept alive by the
# Task Scheduler entry that `headroom install apply` creates.
$env:ANTHROPIC_BASE_URL = "http://127.0.0.1:8787"
$env:ENABLE_TOOL_SEARCH = "true"
claude --dangerously-skip-permissions @argsIf you would rather not use a wrapper, this is what headroom init claude
writes into the project — the same two variables, plus the hook:
{
"env": {
"ANTHROPIC_BASE_URL": "http://127.0.0.1:8787",
"ENABLE_TOOL_SEARCH": "true"
},
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume",
"hooks": [
{
"type": "command",
"command": "headroom init hook ensure --profile default",
"timeout": 15
}
]
}
]
}
}A note on --dangerously-skip-permissions: that flag is my choice for my
own machine, where the agent works inside git repositories I can restore.
Leave it out if that is not your situation.
The rules of the test
Two principles, agreed before the first request goes through.
Save the most, lose the least — and know what you lost. Compression with loss is still loss; the claim that nothing is ever affected would be dishonest. What the test demands is that every loss be acceptable (it does not change the outcome of the work), predictable (the rules of what is never cut are known in advance), and auditable (what was cut can be counted and recalled).
Let the evidence decide. Before and after are measured from the same source, and the rule for keeping, deepening or abandoning the setup is written before the data comes in. The chart decides, not the impression.
In practice, the guardrails look like this:
| Guardrail | Why |
|---|---|
Bold editingHEADROOM_MODE=token | The lever that was never tested. |
Files are sacredHEADROOM_PROTECT_TOOL_RESULTS=Read,Edit,Write | Whatever the agent read or wrote is never lossy-compressed; it never edits from a summary. |
Shorter catalogueHEADROOM_TOOL_DESC_MAX_CHARS=200 | Tool descriptions truncated to their first sentence — cheap and predictable. |
Conservative proseHEADROOM_TARGET_RATIO unset | The prose compressor keeps its own cautious threshold. |
| Every cut recoverable CCR on (default) | No --no-ccr; retrievals are the alarm. |
A receipt per requestHEADROOM_LOG_FILE=…/requests.jsonl | Tokens before and after, cache reads and writes, latency, which transforms ran. |
And the measurement, in three sources that do not depend on each other: the Claude Code transcripts, which record what the API actually billed per request; the proxy’s own request log, which records what it claims to have done; and the subscription usage poller, which records what the plan’s weekly quota actually consumed. Two projects, three phases each — no proxy, cache mode, token mode — with at least five working days in the last one.
The rule is fixed now, so that I cannot bend it later: keep and deepen if billed tokens per request fall by fifteen percent or more, re-reads and retrievals do not rise by more than a fifth, and the proxy’s added latency stays under four seconds at the ninetieth percentile. Otherwise, fall back to cache mode or lossless compaction — and say so.
What comes next
The editor took his seat tonight. In a few days I will publish what the receipts say: tokens per request by phase, cache reads against cache writes, how far a session stretches before it fills, how often the model asked for an original back, and what the proxy cost in seconds. If the editor earns the chair, I will say so. If he does not, I will say that too.
Versions: Headroom 0.36.5, Claude Code 2.1.278, as of September 2026. The numbers above come from one real project I work on; the project is not the subject and is not named.