Skip to content

Latest commit

 

History

20 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

lean-context

MIT License Zero Python deps

Input-side token compression for Claude Code. Where rolling-context manages tokens already in your context, lean-context stops waste before it enters — and the two cooperate when both are installed. Everything is lossless-or-recoverable: the model either sees the real bytes, or an honest pointer/diff/tee to bytes it can get back with one more call. Every transform fails open.

What it does

1. Read dedup-by-reference

An identical re-read of an unchanged file (same path/offset/limit, same content, same agent, within 20 min) is replaced by an orienting note (age, size, digest, outline) pointing at your earlier read. Escape valve: the next identical Read returns full content.

2. Differential reads (changed files → just the diff)

The big one. When you read a file you read before and it changed, lean-context sends only the changed hunks as a unified diff with real line numbers — everything else is "unchanged from what you already have." Read → edit → read-to-verify loops, which normally resend the whole file, now resend ~the diff. Only fires when your earlier read was served in full and is provably still in context (guarded by compaction + rolling-context signals); wholesale rewrites fall back to a full read; escape valve always applies.

3b. Structural maps for prose and markup

The same engine that maps code maps markdown, HTML/Vue/Astro/SVG and CSS/SCSS: headings, landmark tags and selectors are kept with real line numbers, the prose or declarations between them are elided with counts. Measured on a real corpus, non-code files are a quarter of all first-read bytes and markdown is the single largest non-code extension, so this is not a side case.

Intent scales the threshold rather than switching mapping off. Treating "error-hunting" as a veto looked safe but cost most of the opportunity, because that vocabulary ("error", "failed", "debug") shows up in ordinary narration far more often than real debugging does. While the agent looks like it is investigating, only files past a much larger floor are mapped — where a full read would dominate the context anyway — and the escape valve still returns full content on the next identical Read.

3. Project codemap (a skill the model invokes)

codemap maps an entire directory in one call — folder tree, file-type counts, every code file's signatures with real line numbers, bodies elided. Measured 84% char reduction on a real 51-file repo. For orientation; the skill tells the model to Read real files before editing. Also fires automatically on large single-file code reads while exploring (never while error-hunting).

4. Command output compression — real rtk + built-in routes

Three tiers, best first:

  • rtk (the real binary). rtk is a maintained Rust tool with ~50 per-command output filters. lean-context uses it the safe way: rtk pipe --filter <name> reshapes the already-captured output — your command runs completely untouched, we only shrink what enters context. Covers pytest, vitest, tsc, git log/diff/status, cargo/go test, mypy, ruff, prettier, grep, and more. Measured: a 300-test pytest run 8,679 → 322 chars (96%). rtk is lazy-downloaded (checksum-verified) on first session only if you opt in with LEAN_CONTEXT_RTK_DOWNLOAD=1, or point LEAN_CONTEXT_RTK at an existing binary.
  • Built-in routes (no binary needed): dotnet build, test runners, and npm/pnpm/yarn/pip install — keep errors/warnings/summary, drop the spam, dedupe repeated diagnostics.
  • Generic collapse: any command's runs of identical consecutive lines collapse with explicit counts.

All command compression is backed by a tee file: the full output is written to disk and its path is in the header, so nothing aggressive is ever unrecoverable — one Read gets it all back.

Opt-in max coverage: set LEAN_CONTEXT_RTK_REWRITE=1 to also rewrite supported simple commands to their rtk equivalent before they run (git statusrtk git status), covering rtk's full ~50-command set born-compressed. Off by default because it changes the command that actually executes.

5. Grep compression

Identical matches within a file collapse with counts; per-file caps. Skipped entirely for error-hunting patterns (every occurrence may matter).

5b. PowerShell, web, path lists and subagent reports

  • PowerShell gets everything Bash gets. It was previously absent from the hook matcher, so on Windows — where most commands go through that tool — a large share of shell output was never even seen.
  • WebFetch returns a page rendered to markdown, so it gets the markdown outline treatment; WebSearch gets repetition collapse. Both tee-backed.
  • Glob path lists fold by directory: the shared stem is written once and every filename listed under it, so each path is still recoverable by concatenation.
  • Subagent reports and task output get repetition collapse only. A subagent's final report is often the only record of work the main agent never saw, so nothing there is restructured or summarized.

6. MCP tool-output compression

MCP servers routinely return large JSON or HTML. lean-context shrinks it deterministically and losslessly-or-recoverably: minify pretty-printed JSON (whole-output or inside ```json fences — still valid JSON, exact values preserved), drop <script>/`<style>`/comment blocks the model never needs, and collapse runs of identical lines. Tee-backed, so the full untouched output is one Read away. Structured data is never paraphrased. Measured live: a script/style-heavy HTML payload from a real MCP tool, 22,711 → ~750 chars (97%). Mixed outputs containing images are left untouched.

Context awareness

  • Per-agent scoping — state keyed by transcript_path; simultaneous subagents never share dedup/diff knowledge.
  • Compaction — a PreCompact hook clears "already seen" knowledge and stored diff bases before compaction runs; SessionEnd too.
  • rolling-context — polls the proxy's /lean/status (v1.9.0+); any read recorded before the last compression injection is never turned into a reference or diff base — full content is served instead.

Install

/plugin marketplace add https://github.com/NodeNestor/nestor-plugins
/plugin install lean-context

Requires Python 3.7+ (stdlib only). To enable the real-rtk tier, either install rtk yourself and it's auto-detected, or set LEAN_CONTEXT_RTK_DOWNLOAD=1 to let the SessionStart hook fetch the checksum-verified binary into the plugin's data dir.

Observe the savings

/lean-context:gain

Turning it off (and back on)

/lean-context:off              # this conversation only
/lean-context:on               # ...and back
/lean-context:off --global     # every session on this machine
/lean-context:status           # what is in effect, and why

No restart, no config edit. Everything passes through untouched while off, and /lean-context:gain says so rather than just reporting zeros. This is the switch to reach for when a tool result looks wrong and you want to rule lean-context out in one command — which matters more than the savings do.

Per-session is the default scope because one window doing careful review wants raw tool output while the others still want the savings. The toggle is keyed on session id, not transcript, so subagents inherit the setting of the session that spawned them.

Making it opt-in

/lean-context:default off      # new sessions start uncompressed
/lean-context:on               # ...turn it on where you want it

Useful if you would rather reach for compression deliberately than have it always on. Sessions that already toggled themselves keep their own setting.

Precedence, most specific first:

Scope Set by Beats
LEAN_CONTEXT_DISABLE=1 settings.json env everything
machine-wide --global session and default
this session /lean-context:on|off the default
default /lean-context:default

The env var and the global flag deliberately win over a session opting in, so there is always one switch that stops everything. LEAN_CONTEXT_DISABLE=1 needs a Claude Code restart, so it suits permanent opt-out rather than a mid-session check.

Configuration

/lean-context:config                      show every setting and where it came from
/lean-context:config preset aggressive    floors near zero — more coverage
/lean-context:config preset conservative  higher floors — fewer, larger interventions
/lean-context:config bash_min_chars 500   change one setting
/lean-context:config --project ...        write ./.lean-context.json instead
/lean-context:config --reset              clear it

Resolution order, lowest priority first: defaults → preset → ~/.lean-context/config.json./.lean-context.jsonLEAN_CONTEXT_* environment. The config printout names the source of every value, so a setting that "won't change" is never a mystery. No restart — changes apply to the next tool call.

On the floors. They are a cheap pre-filter, not the safety mechanism. The hook process runs on every matched tool call whether or not a floor is met, so a floor skips a few microseconds of in-process work rather than a process spawn. min_saving_ratio is what actually protects you: any transform failing to save that fraction is discarded and the original passes through untouched. Lowering floors therefore buys coverage very cheaply. Swept on a read-heavy sample:

structural-map floor saved Read transforms fired
12k (old default) 27.8% 29
6k (default) 32.8% 46
3k 34.3% 60
3k + every other floor at 500 34.7% 61

6k is the knee — most of the value, without mapping files small enough that the map is barely smaller than the file. Removing the floors entirely buys 0.4 points on top, which is why aggressive exists but is not the default.

Env var Default Meaning
LEAN_CONTEXT_PRESET balanced conservative | balanced | aggressive
LEAN_CONTEXT_MIN_SAVING_RATIO 0.20 the real guard: discard transforms saving less
LEAN_CONTEXT_DISABLE 1 disables everything
LEAN_CONTEXT_DIFF 1 0 disables differential reads
LEAN_CONTEXT_CODEMAP 1 0 disables auto single-file codemap
LEAN_CONTEXT_RTK_PIPE 1 0 disables the rtk pipe tier
LEAN_CONTEXT_RTK_DOWNLOAD 1 lets SessionStart download rtk
LEAN_CONTEXT_RTK_REWRITE 1 enables PreToolUse command rewriting (re-executes via rtk)
LEAN_CONTEXT_RTK explicit path to an rtk binary
LEAN_CONTEXT_BASH_ROUTES 1 0 disables built-in command routes
LEAN_CONTEXT_MCP 1 0 disables MCP output compression
LEAN_CONTEXT_WEB 1 0 disables WebFetch/WebSearch compression
LEAN_CONTEXT_DEDUP_WINDOW 1200 seconds a read stays dedup/diff-able
LEAN_CONTEXT_GREP_PER_FILE_CAP 25 max grep matches kept per file
LEAN_CONTEXT_BASH_MIN_CHARS 1500 shell output floor
LEAN_CONTEXT_GREP_MIN_CHARS 2000 grep output floor
LEAN_CONTEXT_MCP_MIN_CHARS 3000 MCP output floor
LEAN_CONTEXT_WEB_MIN_CHARS 3000 WebFetch/WebSearch floor
LEAN_CONTEXT_GLOB_MIN_CHARS 2000 Glob path-list floor
LEAN_CONTEXT_REPORT_MIN_CHARS 4000 subagent report floor
LEAN_CONTEXT_CODEMAP_MIN_CHARS 12000 map floor while exploring
LEAN_CONTEXT_CODEMAP_DEBUG_MIN_CHARS 40000 map floor while error-hunting

Every floor is a cheap pre-filter, not the safety mechanism. MIN_SAVING_RATIO is: any transform that fails to save 20% is thrown away and the original passes through, so lowering a floor costs a little work, never correctness.

How it works

Hooks: PostToolUse (Read/Grep/Bash/PowerShell/WebFetch/WebSearch/Glob/Agent/TaskOutput/MCP transforms), PreToolUse (opt-in rtk rewrite), PreCompact/SessionEnd (invalidation), SessionStart (opt-in rtk bootstrap). Replacements are rebuilt in each tool's original output shape (Claude Code validates updatedToolOutput against the tool's schema). State is per-agent JSON + content blobs under ${CLAUDE_PLUGIN_DATA}, pruned after 48h. Pure-stdlib Python; the only optional external piece is the rtk binary, which is never required.

Test

python test/test_dispatch.py

Covers dedup + escape valve, differential reads (small change → diff, wholesale → full), codemap intent gating, all three command tiers (mock rtk, built-in routes, collapse), rtk PreToolUse rewrite, grep, PreCompact/SessionEnd + rolling-context invalidation, shape preservation, and every disable switch.

License

MIT (the rtk binary is separately licensed by its authors, Apache-2.0, and downloaded only on opt-in).

About

Input-side token compression for Claude Code: read dedup-by-reference, codemap exploration reads, duplicate collapse. Context-aware companion to rolling-context.

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages