Headroom context compression
Headroom is a local MCP server that compresses text and caches the original so
you can get it back on demand. Three tools:
| Tool |
Input |
Returns |
mcp__headroom__headroom_compress |
content (string) |
JSON: compressed, hash, original_tokens, compressed_tokens, tokens_saved, savings_percent, transforms, note |
mcp__headroom__headroom_retrieve |
hash (string), query (optional string) |
Without query: { source, original_content } — the full original. With query: { source, query, results: [{type, text}], count } — BM25-matched slices (note: different field, and count can be 0) |
mcp__headroom__headroom_stats |
— |
session totals: compressions, tokens saved, cost saved |
The one idea that matters: lossy, but reversible
Compression is lossy. It dedupes repeated lines, drops low-signal tokens,
reorders, and elides whole runs of similar content. A compressed log will be
missing lines; a compressed JSON will be missing fields. Do not treat the
compressed text as ground truth for anything exact — a specific number, an
ID, an error code, a secret, a precise line you need to quote.
But the original is never gone. Every compression caches the full original
under a hash (a local LRU store; no proxy needed). When you need precision,
call headroom_retrieve(hash) and you get the complete original back. This is
CCR — Compress, Cache, Retrieve. Think of compress as putting the bulky
original in a drawer and keeping a labelled index card in your pocket: the card
is enough to reason and plan with, and the drawer is one reach away when a
detail on the card turns out to be too thin.
This is why compression is safe to use liberally for the cases below — you're
never making an irreversible decision.
When to compress (and when not to)
The honest constraint: this setup is MCP-only (no proxy). A tool result is
already in your context the moment the tool returns — compressing it after
the fact does not reclaim that context. So don't compress things just
because they're big and already sitting in front of you. Compress when the
content is about to travel or be re-read, where shrinking it pays off:
- Subagent handoffs — before pasting a large file, log, or search result
into a
Task prompt, compress it and pass compressed + the hash. Tell the
subagent it can call headroom_retrieve with that hash if it needs the full
thing. This keeps the subagent's prompt lean without hiding anything.
- Content you'll re-read across many steps — a big config, schema dump, or
spec you keep coming back to in a long pipeline. Compress once, carry the
compressed view + hash, expand only the slice you need each time (use the
query arg).
- Deliberate stashing — when you're about to write a large intermediate
blob into a notes/scratch file you'll load again later.
Don't bother compressing when: the content is small (under ~50 lines / a
few hundred tokens — the overhead isn't worth it), you only need it once and
right now, or you need every byte exact for the immediate next step (just use
it directly).
Reading the result
headroom_compress returns JSON. The fields you act on:
compressed — the shrunk text. Its tail carries a recovery marker like:
[201 items compressed to 91. Retrieve more: hash=3bfd8310c71ccb929e852bca]
That 201 → 91 is your elision signal: 110 items were dropped from the
view. The bigger that gap, the more was left out, and the more likely a
detail you need lives in the dropped part.
hash — the handle for retrieval. Keep it with the compressed text wherever
the compressed text goes. If you forward compressed into a subagent prompt
or a file, forward the hash right beside it, or retrieval becomes
impossible.
savings_percent / transforms — savings and which strategy fired (e.g.
router:log:..., router:text:...). Useful for a quick sanity check; not
something you usually need to act on.
When (and how) to retrieve — "something was missed"
Retrieve when the compressed view isn't enough. Concrete triggers:
- The marker shows a meaningful elision (
N → M with a large gap) and the
task needs detail from the dropped region.
- You're about to quote, copy, or act on an exact value (a number, path,
ID, stack frame, secret) — the compressed view may have mangled or dropped
it. Retrieve and read it from the original.
- A compressed answer feels suspiciously thin or doesn't add up — fewer
results than expected, a field you know should exist is absent, counts don't
match.
- A subagent (or future you) is handed only the compressed view and hits a wall
needing specifics.
How:
Whole original (the reliable default): headroom_retrieve(hash) →
reads original_content. Use this whenever you need certainty.
Just a slice (optional optimization): pass a query —
headroom_retrieve(hash, query="ECONNREFUSED") runs BM25 over the cached
original and returns matches under a results array (each item is
{type, text}), not original_content. Read results[].text, not
original_content, on query calls.
Important: query matching is fuzzy and imperfect — a multi-word or
structured query can come back with count: 0 even when the data is
there (single keywords match far more reliably than phrases). Treat an
empty results as "my query didn't match," not "the content is gone."
When in doubt, drop the query and do a full headroom_retrieve(hash). The
full retrieve is the source of truth; query is just a way to keep context
lean when you have one clear keyword.
A retrieve that returns source: "local" came from this session's store. If a
hash returns an error ("Content not found"), it likely expired (the store
has a TTL) or came from a different process — re-compress the source if you
still have it.
Worked example + verifying savings
A step-by-step subagent-handoff example (compress → pass hash → retrieve) and
how to confirm real token savings with headroom_stats live in
references/worked-example.md — read it the first time you run this pattern or
when checking that compression is actually paying off.
Automatic compression (optional, advanced)
Everything above is on-demand — you choose what to compress. Headroom can
also run as a proxy that auto-compresses all tool output inline (the source
of its headline savings), with real tradeoffs around prompt caching and being
in the API critical path. If the user wants always-on compression rather than
deliberate handoffs, read references/proxy-mode.md for how to enable it and
what to watch for.
Quick check on the tools
If mcp__headroom__* tools aren't available, the MCP server isn't loaded —
confirm headroom shows in /mcp (it's registered in ~/.claude.json). It
only appears after a Claude Code restart following installation. Without it,
fall back to normal handling (summarize manually) rather than guessing.
Critically: if the tools are missing, say so plainly — never invent a
hash, a savings_percent, or any other figure these tools would have
returned. A fabricated hash is worse than no compression: it looks retrievable
but nothing is stored behind it. Report the real situation and proceed without
the tool.
1---2name: headroom-context-compression3description: Shrink large text before it travels or gets re-read, using the headroom MCP (mcp__headroom__headroom_compress / _retrieve / _stats). Use it BEFORE pasting a 200+ line blob (logs, files, search results, JSON) into a subagent Task prompt, stashing content you'll re-read later, or re-carrying big output through a pipeline — even if the user never says "compress." Every compress returns a hash headroom_retrieve expands, so use it in reverse too: handed a `hash=` marker, recover exact details from the original. NOT gzip/zip, image/video compression, making code concise, or database column compression.4---56# Headroom context compression78Headroom is a local MCP server that compresses text and caches the original so9you can get it back on demand. Three tools:1011| Tool | Input | Returns |12|------|-------|---------|13| `mcp__headroom__headroom_compress` | `content` (string) | JSON: `compressed`, `hash`, `original_tokens`, `compressed_tokens`, `tokens_saved`, `savings_percent`, `transforms`, `note` |14| `mcp__headroom__headroom_retrieve` | `hash` (string), `query` (optional string) | Without `query`: `{ source, original_content }` — the full original. With `query`: `{ source, query, results: [{type, text}], count }` — BM25-matched slices (note: **different field**, and `count` can be 0) |15| `mcp__headroom__headroom_stats` | — | session totals: compressions, tokens saved, cost saved |1617## The one idea that matters: lossy, but reversible1819Compression is **lossy**. It dedupes repeated lines, drops low-signal tokens,20reorders, and elides whole runs of similar content. A compressed log will be21missing lines; a compressed JSON will be missing fields. **Do not treat the22compressed text as ground truth for anything exact** — a specific number, an23ID, an error code, a secret, a precise line you need to quote.2425But the original is never gone. Every compression caches the full original26under a `hash` (a local LRU store; no proxy needed). When you need precision,27call `headroom_retrieve(hash)` and you get the complete original back. This is28CCR — Compress, Cache, Retrieve. Think of `compress` as putting the bulky29original in a drawer and keeping a labelled index card in your pocket: the card30is enough to reason and plan with, and the drawer is one reach away when a31detail on the card turns out to be too thin.3233This is *why* compression is safe to use liberally for the cases below — you're34never making an irreversible decision.3536## When to compress (and when not to)3738The honest constraint: this setup is **MCP-only** (no proxy). A tool result is39already in your context the moment the tool returns — compressing it *after*40the fact does **not** reclaim that context. So don't compress things just41because they're big and already sitting in front of you. Compress when the42content is about to **travel or be re-read**, where shrinking it pays off:4344- **Subagent handoffs** — before pasting a large file, log, or search result45 into a `Task` prompt, compress it and pass `compressed` + the `hash`. Tell the46 subagent it can call `headroom_retrieve` with that hash if it needs the full47 thing. This keeps the subagent's prompt lean without hiding anything.48- **Content you'll re-read across many steps** — a big config, schema dump, or49 spec you keep coming back to in a long pipeline. Compress once, carry the50 compressed view + hash, expand only the slice you need each time (use the51 `query` arg).52- **Deliberate stashing** — when you're about to write a large intermediate53 blob into a notes/scratch file you'll load again later.5455**Don't bother compressing** when: the content is small (under ~50 lines / a56few hundred tokens — the overhead isn't worth it), you only need it once and57right now, or you need every byte exact for the immediate next step (just use58it directly).5960## Reading the result6162`headroom_compress` returns JSON. The fields you act on:6364- `compressed` — the shrunk text. Its tail carries a recovery marker like:65 `[201 items compressed to 91. Retrieve more: hash=3bfd8310c71ccb929e852bca]`66 That `201 → 91` is your **elision signal**: 110 items were dropped from the67 view. The bigger that gap, the more was left out, and the more likely a68 detail you need lives in the dropped part.69- `hash` — the handle for retrieval. Keep it with the compressed text wherever70 the compressed text goes. If you forward `compressed` into a subagent prompt71 or a file, forward the `hash` right beside it, or retrieval becomes72 impossible.73- `savings_percent` / `transforms` — savings and which strategy fired (e.g.74 `router:log:...`, `router:text:...`). Useful for a quick sanity check; not75 something you usually need to act on.7677## When (and how) to retrieve — "something was missed"7879Retrieve when the compressed view isn't enough. Concrete triggers:8081- The marker shows a meaningful elision (`N → M` with a large gap) **and** the82 task needs detail from the dropped region.83- You're about to **quote, copy, or act on an exact value** (a number, path,84 ID, stack frame, secret) — the compressed view may have mangled or dropped85 it. Retrieve and read it from the original.86- A compressed answer feels **suspiciously thin or doesn't add up** — fewer87 results than expected, a field you know should exist is absent, counts don't88 match.89- A subagent (or future you) is handed only the compressed view and hits a wall90 needing specifics.9192How:9394- **Whole original (the reliable default):** `headroom_retrieve(hash)` →95 reads `original_content`. Use this whenever you need certainty.96- **Just a slice (optional optimization):** pass a `query` —97 `headroom_retrieve(hash, query="ECONNREFUSED")` runs BM25 over the cached98 original and returns matches under a **`results` array** (each item is99 `{type, text}`), *not* `original_content`. Read `results[].text`, not100 `original_content`, on query calls.101102 Important: query matching is fuzzy and imperfect — a multi-word or103 structured query can come back with `count: 0` **even when the data is104 there** (single keywords match far more reliably than phrases). Treat an105 empty `results` as "my query didn't match," **not** "the content is gone."106 When in doubt, drop the query and do a full `headroom_retrieve(hash)`. The107 full retrieve is the source of truth; `query` is just a way to keep context108 lean when you have one clear keyword.109110A retrieve that returns `source: "local"` came from this session's store. If a111hash returns an `error` ("Content not found"), it likely expired (the store112has a TTL) or came from a different process — re-compress the source if you113still have it.114115## Worked example + verifying savings116117A step-by-step subagent-handoff example (compress → pass hash → retrieve) and118how to confirm real token savings with `headroom_stats` live in119`references/worked-example.md` — read it the first time you run this pattern or120when checking that compression is actually paying off.121122## Automatic compression (optional, advanced)123124Everything above is *on-demand* — you choose what to compress. Headroom can125also run as a **proxy** that auto-compresses all tool output inline (the source126of its headline savings), with real tradeoffs around prompt caching and being127in the API critical path. If the user wants always-on compression rather than128deliberate handoffs, read `references/proxy-mode.md` for how to enable it and129what to watch for.130131## Quick check on the tools132133If `mcp__headroom__*` tools aren't available, the MCP server isn't loaded —134confirm `headroom` shows in `/mcp` (it's registered in `~/.claude.json`). It135only appears after a Claude Code restart following installation. Without it,136fall back to normal handling (summarize manually) rather than guessing.137138Critically: if the tools are missing, **say so plainly** — never invent a139`hash`, a `savings_percent`, or any other figure these tools would have140returned. A fabricated hash is worse than no compression: it looks retrievable141but nothing is stored behind it. Report the real situation and proceed without142the tool.