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: >-4 Shrink large text before it travels or gets re-read, using the headroom MCP5 (mcp__headroom__headroom_compress / _retrieve / _stats). Use it BEFORE6 pasting a 200+ line blob (logs, files, search results, JSON) into a subagent7 Task prompt, stashing content you'll re-read later, or re-carrying big output8 through a pipeline — even if the user never says "compress." Every compress9 returns a hash headroom_retrieve expands, so use it in reverse too: handed a10 `hash=` marker, recover exact details from the original. NOT gzip/zip,11 image/video compression, making code concise, or database column compression.12---1314# Headroom context compression1516Headroom is a local MCP server that compresses text and caches the original so17you can get it back on demand. Three tools:1819| Tool | Input | Returns |20|------|-------|---------|21| `mcp__headroom__headroom_compress` | `content` (string) | JSON: `compressed`, `hash`, `original_tokens`, `compressed_tokens`, `tokens_saved`, `savings_percent`, `transforms`, `note` |22| `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) |23| `mcp__headroom__headroom_stats` | — | session totals: compressions, tokens saved, cost saved |2425## The one idea that matters: lossy, but reversible2627Compression is **lossy**. It dedupes repeated lines, drops low-signal tokens,28reorders, and elides whole runs of similar content. A compressed log will be29missing lines; a compressed JSON will be missing fields. **Do not treat the30compressed text as ground truth for anything exact** — a specific number, an31ID, an error code, a secret, a precise line you need to quote.3233But the original is never gone. Every compression caches the full original34under a `hash` (a local LRU store; no proxy needed). When you need precision,35call `headroom_retrieve(hash)` and you get the complete original back. This is36CCR — Compress, Cache, Retrieve. Think of `compress` as putting the bulky37original in a drawer and keeping a labelled index card in your pocket: the card38is enough to reason and plan with, and the drawer is one reach away when a39detail on the card turns out to be too thin.4041This is *why* compression is safe to use liberally for the cases below — you're42never making an irreversible decision.4344## When to compress (and when not to)4546The honest constraint: this setup is **MCP-only** (no proxy). A tool result is47already in your context the moment the tool returns — compressing it *after*48the fact does **not** reclaim that context. So don't compress things just49because they're big and already sitting in front of you. Compress when the50content is about to **travel or be re-read**, where shrinking it pays off:5152- **Subagent handoffs** — before pasting a large file, log, or search result53 into a `Task` prompt, compress it and pass `compressed` + the `hash`. Tell the54 subagent it can call `headroom_retrieve` with that hash if it needs the full55 thing. This keeps the subagent's prompt lean without hiding anything.56- **Content you'll re-read across many steps** — a big config, schema dump, or57 spec you keep coming back to in a long pipeline. Compress once, carry the58 compressed view + hash, expand only the slice you need each time (use the59 `query` arg).60- **Deliberate stashing** — when you're about to write a large intermediate61 blob into a notes/scratch file you'll load again later.6263**Don't bother compressing** when: the content is small (under ~50 lines / a64few hundred tokens — the overhead isn't worth it), you only need it once and65right now, or you need every byte exact for the immediate next step (just use66it directly).6768## Reading the result6970`headroom_compress` returns JSON. The fields you act on:7172- `compressed` — the shrunk text. Its tail carries a recovery marker like:73 `[201 items compressed to 91. Retrieve more: hash=3bfd8310c71ccb929e852bca]`74 That `201 → 91` is your **elision signal**: 110 items were dropped from the75 view. The bigger that gap, the more was left out, and the more likely a76 detail you need lives in the dropped part.77- `hash` — the handle for retrieval. Keep it with the compressed text wherever78 the compressed text goes. If you forward `compressed` into a subagent prompt79 or a file, forward the `hash` right beside it, or retrieval becomes80 impossible.81- `savings_percent` / `transforms` — savings and which strategy fired (e.g.82 `router:log:...`, `router:text:...`). Useful for a quick sanity check; not83 something you usually need to act on.8485## When (and how) to retrieve — "something was missed"8687Retrieve when the compressed view isn't enough. Concrete triggers:8889- The marker shows a meaningful elision (`N → M` with a large gap) **and** the90 task needs detail from the dropped region.91- You're about to **quote, copy, or act on an exact value** (a number, path,92 ID, stack frame, secret) — the compressed view may have mangled or dropped93 it. Retrieve and read it from the original.94- A compressed answer feels **suspiciously thin or doesn't add up** — fewer95 results than expected, a field you know should exist is absent, counts don't96 match.97- A subagent (or future you) is handed only the compressed view and hits a wall98 needing specifics.99100How:101102- **Whole original (the reliable default):** `headroom_retrieve(hash)` →103 reads `original_content`. Use this whenever you need certainty.104- **Just a slice (optional optimization):** pass a `query` —105 `headroom_retrieve(hash, query="ECONNREFUSED")` runs BM25 over the cached106 original and returns matches under a **`results` array** (each item is107 `{type, text}`), *not* `original_content`. Read `results[].text`, not108 `original_content`, on query calls.109110 Important: query matching is fuzzy and imperfect — a multi-word or111 structured query can come back with `count: 0` **even when the data is112 there** (single keywords match far more reliably than phrases). Treat an113 empty `results` as "my query didn't match," **not** "the content is gone."114 When in doubt, drop the query and do a full `headroom_retrieve(hash)`. The115 full retrieve is the source of truth; `query` is just a way to keep context116 lean when you have one clear keyword.117118A retrieve that returns `source: "local"` came from this session's store. If a119hash returns an `error` ("Content not found"), it likely expired (the store120has a TTL) or came from a different process — re-compress the source if you121still have it.122123## Worked example + verifying savings124125A step-by-step subagent-handoff example (compress → pass hash → retrieve) and126how to confirm real token savings with `headroom_stats` live in127`references/worked-example.md` — read it the first time you run this pattern or128when checking that compression is actually paying off.129130## Automatic compression (optional, advanced)131132Everything above is *on-demand* — you choose what to compress. Headroom can133also run as a **proxy** that auto-compresses all tool output inline (the source134of its headline savings), with real tradeoffs around prompt caching and being135in the API critical path. If the user wants always-on compression rather than136deliberate handoffs, read `references/proxy-mode.md` for how to enable it and137what to watch for.138139## Quick check on the tools140141If `mcp__headroom__*` tools aren't available, the MCP server isn't loaded —142confirm `headroom` shows in `/mcp` (it's registered in `~/.claude.json`). It143only appears after a Claude Code restart following installation. Without it,144fall back to normal handling (summarize manually) rather than guessing.145146Critically: if the tools are missing, **say so plainly** — never invent a147`hash`, a `savings_percent`, or any other figure these tools would have148returned. A fabricated hash is worse than no compression: it looks retrievable149but nothing is stored behind it. Report the real situation and proceed without150the tool.