Query-aware context compression for Claude Code, without the toolchain.
Reducer shrinks the context Claude Code sends to the model — prior conversation turns and large tool outputs — down to the parts that are actually relevant to what you're currently asking, before they cost you tokens. It's pure JavaScript: no Rust, no Python, no ONNX runtime, no model download. The default scoring mode makes zero network calls.
Tagline: token savings without the toolchain.
Two real projects already do context compression for coding agents: SuperCompress and Headroom. Headroom in particular is excellent and far more feature-complete than this project — reversible compression, cross-agent memory, a trained ML model, 15+ agent integrations. Reducer doesn't try to out-feature it.
The wedge is install friction. Headroom's own README documents Rust
toolchain fallbacks, an ONNX runtime download, Python version pinning,
and corporate SSL-proxy workarounds. That's real overhead for people who
just want fewer tokens. Reducer's entire install is one npm install
plus one setup command — no compiled dependency, anywhere, ever. Exact
commands below.
Published on npm as reducer-cloud — not as reducer (that name
was already taken by an unrelated package before this project started).
The CLI command itself is still the short reducer, once installed.
npm install -g reducer-cloud
reducer setupPrefer not to install globally? npx works too, but you have to name
the actual published package — npx reducer ... would silently run
someone else's package:
npx reducer-cloud setupreducer setup finds your Claude Code settings file, backs it up, and
registers Reducer's UserPromptSubmit and PostToolUse hooks. It never
prompts, merges into your existing settings rather than overwriting
them, and writes nothing beyond that one JSON file plus a timestamped
backup next to it.
Hooks take effect the next time you start a Claude Code session (a running session won't pick them up until it's restarted, or until Claude Code's own settings file watcher reloads them — see the docs for your version).
Undo at any time:
reducer restoreThis surgically removes only Reducer's hook entries — anything else you changed in that settings file since installing is left alone.
For contributing, or if you're reading this before v0.1.0 is live on npm:
git clone https://github.com/rafipopz98/cloud.reduce.git
cd cloud.reduce
npm install
npx reducer setup(npx reducer resolves correctly here because it's finding this
checkout's own bin/reducer.js locally — the naming caveat above only
applies to fetching from the public registry.)
reducer setup # register the hooks (user-wide by default)
# start a new Claude Code session, use it normally
reducer stats # see tokens saved and latency so far
reducer restore # undo, any timeScope options, on both setup and restore:
reducer setup --project # this project's .claude/settings.json only
reducer setup --dry-run # preview the change without writing
reducer setup --config=PATH # patch an explicit settings file| Command | What it does |
|---|---|
reducer setup |
Registers the Claude Code hooks (backs up first) |
reducer restore |
Removes them again |
reducer stats |
Prints local usage totals: tokens saved, latency, event count |
reducer bench |
Benchmarks compress() latency on your machine |
Two hooks, both calling the compression engine directly in-process — no proxy, no local server, no network hop:
UserPromptSubmitfires before Claude sees your new prompt. It can't rewrite the prompt itself (that's a Claude Code hook API constraint, not a Reducer limitation), so it compresses the prior conversation against your new prompt and writes a relevance-ranked digest to~/.reducer/inbox/latest.md, pointing Claude at it viaadditionalContext.PostToolUsefires after a tool call succeeds and can rewrite what Claude sees, viaupdatedToolOutput. This is where most of the real savings come from — largeRead/Bash/Grepoutputs get compressed in place before they ever reach the model.
Compression itself: split the text into chunks (sentences, code blocks, and JSON blocks kept intact — never split mid-structure), score each chunk's relevance to your query, then greedily keep the highest-scoring chunks within a token budget.
Heuristic mode (default) scores by keyword overlap with your query,
recency, and a role/recency floor for system prompts and the last few
turns — zero API calls, zero cost. Embedding mode is opt-in
(mode: "embedding" plus an API key) for higher-quality scoring when
you want it; it's never required.
Everything below is measured on this project's own test data, not borrowed from either reference project's marketing.
~59% average token reduction, measured across 8 real local Claude
Code session transcripts on the development machine (test/compress.test.js,
heuristic mode) — individual runs ranged 58.5%–59.8%, tightly clustered.
A separate set of 4 hand-written synthetic fixtures ranged wider
(39%–63%, mean ~54%), which is expected — they're much smaller inputs
where a handful of tokens shifts the percentage a lot.
That ~59% figure held up on real production tool-output data, not just conversation text. During PostToolUse hook integration, two live cases were captured and independently reproduced and verified against this project's actual live session data:
- A
grep-style Bash output (21 file matches foruserId: string, 712 tokens) queried against a real question about why that field wasn'tObjectId→ 61.2% reduction, with the actually-relevant matches surviving in the output. - An
Edit-tool response containing a full TypeScript model file → 60.0% reduction.
(Those two cases initially compressed to 0 kept tokens — a real bug
where a single indivisible chunk larger than the token budget got
dropped entirely instead of truncated. Fixed in src/chunk.js and
src/select.js; see git history for the full writeup.)
Measured with reducer bench — heuristic mode, 200 timed iterations
per size tier after 20 discarded warmup runs, real prose from this
repo's own plan.md/docs/research-notes.md as input:
| Input size | Median | p95 | Max |
|---|---|---|---|
| ~160 tokens | 0.24ms | 0.27ms | 2.67ms |
| ~2,200 tokens | 3.06ms | 3.47ms | 3.80ms |
| ~14,000 tokens | 20.14ms | 54.96ms | 152.26ms |
Single-digit milliseconds for typical tool outputs, low-double-digit
milliseconds even at ~14k tokens (roughly a full 40-turn conversation
window). The large-input tail (p95/max) is real, not smoothed over —
likely GC pressure under 200 back-to-back large-string tokenization
passes in a tight loop; a single real invocation from a hook won't see
that pattern. Reproduce with reducer bench on your own machine.
Measured on the development machine:
| Step | Time |
|---|---|
npm install (cold cache, clean-machine simulation) |
8.5s |
npm install (warm cache) |
1.2s |
reducer setup (node bin/reducer.js setup, from source) |
0.34s |
| Total, cold start → hooks registered | ~8.8s |
This is a timing comparison, not a compression-quality comparison —
Headroom is the more feature-complete engine. The fair comparison is
install friction: Headroom's own README documents a Rust toolchain
fallback path, an ONNX runtime download, Python version pinning across
3.10–3.13, and corporate-SSL-proxy workarounds as part of getting it
running. Reducer's install is npm install + one command, entirely pure
JavaScript, and was not separately timed against a live Headroom install
on this machine — the claim being made is the absence of those extra
steps, documented in Headroom's own README, not a stopwatch comparison
of two installs run side by side.
const { compress } = require('reducer-cloud'); // or require('./src/compress') from source
const result = await compress({
context: [{ role: 'user', content: '...' }], // string or array of turns
query: 'what the user is currently asking',
targetTokens: 2000,
mode: 'heuristic', // default — no network, no API key needed
});Embedding mode:
await compress({
context,
query,
targetTokens: 2000,
mode: 'embedding',
apiKey: process.env.REDUCER_EMBEDDINGS_API_KEY, // or set the env var directly
});For hosted/non-hook use:
node src/server.js
curl -X POST http://localhost:3000/compress \
-H "Content-Type: application/json" \
-d '{"context":[{"role":"user","content":"..."}],"query":"...","targetTokens":2000}'Pre-release. Working end to end (hooks fire in real Claude Code
sessions, verified live — not just simulated). Package metadata,
LICENSE, and the files allowlist are publish-ready; v0.1.0 is not
yet live on npm.
MIT — see LICENSE.