# Headroom Context Compression

> 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.

- Skill: `artsmc-claude-dev-agents/headroom-context-compression` (Agent Skill, multi-file: 9 files)
- Install (CLI): `npx skillmds@latest add artsmc-claude-dev-agents/headroom-context-compression`
- Raw SKILL.md: https://api.skillmd.com/api/skills/artsmc-claude-dev-agents/headroom-context-compression/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: artsmc (https://skillmd.com/u/artsmc-claude-dev-agents)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/artsmc-claude-dev-agents/headroom-context-compression

---


# 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.

