Components¶
Every registered component, exactly as it behaves in code. All operate on tool-output
messages (role:"tool"; for Anthropic, tool_result blocks normalized to that shape by
apply). Reformat = lossless. Offload = drops bytes, stashes the original, leaves a
<<cg:HASH>> marker recoverable via context_guru_expand / GET /expand.
Summary¶
| Component | Kind | What it drops | Recoverable | Fires on | Key config (default) |
|---|---|---|---|---|---|
format |
Reformat | nothing (compacts JSON) | n/a (lossless) | pretty-printed JSON tool output | min_tokens (50) |
toon |
Reformat | nothing (re-encodes JSON arrays as TOON) | n/a (lossless) | uniform flat JSON object-arrays | min_tokens (50) |
cacheinject |
Reformat | nothing (adds cache_control) |
n/a (lossless) | Anthropic-family requests | — |
skeleton |
Offload | function/method bodies | via expand | fenced ```lang code blocks |
min_tokens (80) |
dedup |
Offload | later byte-identical tool outputs | via expand | repeated identical outputs | min_tokens (100) |
collapse |
Offload | middle of an oversized output | via expand | any large tool output (fallback) | max_tokens (2000), head_lines (20), tail_lines (20) |
failed_run |
Offload | earlier superseded test/build runs | via expand | ≥2 run-like outputs | min_tokens (100) |
cmdfilter |
Offload | lines per declarative DSL filter | via expand | output matching a filter | filters ([]), disable_builtins (false) |
extract |
Offload | query-irrelevant lines (or an LLM-written filter) | via expand | large output + a recent query | min_tokens (300), head_lines/tail_lines (5), strategy (deterministic|code|rlm), model.source, trigger |
smartcrush |
Offload | middle items of a JSON array | via expand | JSON-array tool output | min_items (5), min_tokens (200), keep_first (3), keep_last (2) |
mask |
Offload | older tool outputs (age-based) | via expand | more than keep_recent outputs |
keep_recent (3), min_tokens (100) |
phi_evict |
Offload | lowest-scoring outputs over budget | via expand | transcript over budget_tokens |
budget_tokens (120000), weights (balanced) |
summarize |
Offload (LLM) | the middle of the transcript → one summary | via expand | long trajectories | summary_level (regular), keep_last (3), min_tokens (500), resummarize_tokens (6000), model.source, trigger |
Presets (config): off [] · safe [format, cacheinject] · balanced
[format, dedup, failed_run, cmdfilter, cacheinject] · aggressive adds smartcrush, extract ·
coding [format, skeleton, cmdfilter, cacheinject] · mcp [format, smartcrush, cacheinject] ·
agent [format, dedup, failed_run, mask, extract, cacheinject] — for long agentic sessions;
mask is the biggest lever there (~27% content-token savings, no reward loss — see RESULTS.md) ·
summarize [summarize] (run alone — it restructures the whole transcript).
LLM-based components (extract with strategy: code/rlm, and summarize) call a model, chosen by
model.source: incoming (default — reuse the proxied request's own model + key) or config (a dedicated
cheap model set via CHEAP_MODEL* env / the gateway's CheapModel). When no model is available they
degrade — extract to its deterministic projection, summarize to a no-op. See design.md.
Common gates every Offload respects: skip non-text (Rewritable) messages, skip content already
carrying a marker (no double-offload), and skip if the rewrite (marker + hint included) isn't
actually smaller.
Reformat (lossless)¶
format¶
Re-encodes a pretty-printed JSON tool output as compact JSON — same value, fewer whitespace
tokens. Only acts on tool messages whose trimmed text starts with {/[, is valid JSON, is
≥ min_tokens, and gets smaller. v1 is json-compact only (a TOON encoder is planned).
before: { "id": 1, after: {"id":1,"name":"ada","tags":["x","y"]}
"name": "ada",
"tags": [ "x", "y" ] }
- Lossiness: none — nothing stashed. Shines: verbose pretty-printed JSON/MCP payloads. Inert: already-compact JSON, non-JSON text, small outputs.
toon¶
Re-encodes a JSON array of uniform, flat objects as TOON (Token-Oriented Object Notation):
one header listing the field names once, then one comma-separated row per element. It drops the
braces, repeated keys, and quotes that dominate a JSON array's token cost. It's a Reformat (repack
in place, nothing stashed): every scalar value is preserved, with one small representational
simplification — JSON null renders as an empty cell (indistinguishable from ""). Only arrays
whose elements share one key set and hold scalar values are encoded; anything nested, ragged, or
non-array is left untouched, and the pipeline's never-worse guard reverts any case that fails to
shrink.
- Config:
min_tokens(50). Lossiness: none — nothing stashed (JSONnull→ empty cell). Shines: long homogeneous JSON arrays (the llm-d TOON config). Inert: nested/ragged/non-array output, or not smaller.
cacheinject¶
Places an Anthropic cache_control: {type: ephemeral} breakpoint on the last content block of
the message just before the newest turn (a stable prefix boundary), so the provider KV cache
hits across turns. Adds a control directive, changes no model-visible content.
- Lossiness: none. Shines: Anthropic/Bedrock/Vertex agents that don't self-cache (the
savings lever is provider-side cache hits, invisible to
/statstoken counts). Inert: non-cache-aware providers, string-content messages (can't carry a block breakpoint), a breakpoint already present./statswill list it undertop_passthroughsince it saves no content tokens — that's expected, not dead weight.
Offload (lossy, reversible)¶
skeleton¶
Parses fenced ```lang code blocks with tree-sitter and replaces function/method/constructor
bodies with a placeholder, keeping signatures, imports, types, and class bodies (so method
signatures survive). Stashes the whole original message.
flowchart LR
A["go fenced block<br/>full func bodies"] --> B{"tree-sitter parse<br/>lang known? body ≥ min_tokens?"}
B -->|no| A
B -->|yes| C["signatures + { … }<br/>+ <<cg:HASH>> marker"]
C --> D[(Store: original)]
before: func Add(a, b int) int { after: func Add(a, b int) int { … }
return a + b func Sub(a, b int) int { … }
} <<cg:9f2a…>> [full source: call context_guru_expand]
- Config:
min_tokens(80, per body). Grammars: go, python, js/ts/tsx, rust, java, c/cpp, ruby, php, c#, kotlin, swift, scala. Shines: thecodingpreset — the agent reads big source files but mostly needs the shape. Inert: no fenced blocks, unfenced file reads, unknown language, skeleton not smaller than the body.
dedup¶
Replaces a tool output byte-identical to an earlier one in the same request with a short pointer + marker. Exact match only (near-duplicate is deferred).
before: <big config dump> … (later, identical) <same big config dump>
after: <big config dump> … [identical to an earlier tool output] <<cg:1c8e…>>
- Config:
min_tokens(100). Shines: agents that re-read the same file/command output repeatedly. Inert: no exact repeats, small outputs.
collapse¶
Content-agnostic fallback for an oversized tool output nothing more specific handled: keep a
head_lines + tail_lines window, stash the full original. Runs late (after cmdfilter/format);
skips content already marked.
before: <2,000-line log>
after: <first 20 lines>
... (1960 lines omitted) <<cg:44ab…>> [full output: call context_guru_expand]
<last 20 lines>
- Config:
max_tokens(2000 threshold),head_lines(20),tail_lines(20). Shines: a catch-all last stage for huge outputs. Inert: output ≤max_tokens, or too few lines for head/tail to help.
failed_run¶
Recognizes test/build run output (regex: N passed/failed, BUILD SUCCESS/FAIL, Traceback,
FAILED, panic:, npm ERR!, pytest session banners). Keeps the most recent run in full,
collapses every earlier run to a pointer + marker — a superseded run is safely recoverable.
before: [run 1] 3 failed, 5 passed … [run 2 after fix] 8 passed
after: [superseded by a later run] <<cg:7d1c…>> [full output: …] [run 2] 8 passed
- Config:
min_tokens(100). Needs ≥2 run-like outputs. Shines: iterative fix→re-run loops. Inert: <2 runs detected, small outputs. False positives cost only an expand round-trip, never data.
cmdfilter¶
Shrinks tool output with declarative DSL filters (see below). Matches a filter on the output's
first non-empty line, applies its 8-stage pipeline, stashes the original, and appends a recovery
hint only when the filter was actually lossy. Ships builtin pytest / npm-install / make
filters.
before: pytest … 100 lines of PASSED + warnings + 1 failure
after: <failures + summary, passing noise stripped, ≤80 lines> <<cg:…>> [full output: …]
- Config:
filters(inline filter YAML docs, added with no recompile),disable_builtins.Enabledonly when ≥1 filter is loaded. Shines: noisy but structured command/log output (test runners, package managers, build tools). Inert: output whose first line matches no filter, or where filtering doesn't shrink it.
extract¶
Projects a large tool output down to what's relevant to the most recent user query. v1 ships
the deterministic strategy: keep head/tail context, every line overlapping the query's
keywords, and every line carrying an error word; collapse the gaps with …. Stashes the original.
before: <300-line API dump> (query mentions "auth timeout")
after: <head> … lines mentioning auth/timeout + any error lines … <tail>
<<cg:…>> [full output: call context_guru_expand]
The relevance signal is the full conversational context (the task in the first user turn + the most recent assistant/user turns), not just one trailing sentence — so it keeps what the agent actually needs on any agent/benchmark.
- Config:
min_tokens(300),head_lines/tail_lines(5),strategy(deterministic|code|rlm),model.source,trigger,marker_mode,rewrite. Withcode, a cheap LLM writes a Starlark filter run in a sandbox (no imports/IO, step + 2s limits). It sees the full tool output (bounded to ~32k chars) so it writes code specific to that content — deleting the exact irrelevant lines/records AND words/sentences/parts within lines — not a blind generic filter. It has regex helpers (re_sub/re_findall/re_split/re_match, RE2, pure-Go). Guarantee (default): deletion-only — the result is accepted only if it is an in-order character subsequence of the input (obtainable by deleting characters), so the model can trim anything but provably cannot fabricate, reorder, or reword; else it falls back to deterministic. JSON bodies are decoded and filtered structurally.rlmcurrently maps tocode. rewrite(opt-in, code only): drops the containment proof so the model may reword / summarize / rewrite freely (lossy, unverified — only sanity + strictly-smaller apply). Pair with a non-fullmarker_mode. Defaultfalsekeeps the verified deletion-only guarantee.- Gating + reuse (LLM strategies): a
triggerdecides whether to spend a model call —min_output_tokens(per tool output; folds in legacymin_tokens),min_request_tokens,min_messages— socodefires only on a large output in a large request, not every turn. A reduced output is cached per session by content hash, so the same output re-sent on a later turn reuses the prior compaction (no new model call, byte-identical result → prefix stays KV-cache stable). - Shines: big query-focused MCP/API outputs, logs and file reads; structured JSON where a filter
selects records precisely. Inert: no user query, output below floor, request below
trigger, projection not smaller, or (forcode) no model available → deterministic.
smartcrush¶
Statistical JSON-array compressor: parse the array, keep keep_first + keep_last items plus
any item whose raw JSON carries an error signal, drop the rest, stash the full original. Kept items
are verbatim (schema-preserving).
before: [ {…}, {…}, … 200 items … ]
after: [ item0, item1, item2, item198, item199 ] [5 of 200 items shown; full array: call …] <<cg:…>>
- Config:
min_items(5),min_tokens(200),keep_first(3),keep_last(2). Shines: long homogeneous JSON arrays (list endpoints, search hits) — themcppreset. Inert: non-array output, fewer thanmin_items, nothing to drop. v1 uses fixed anchors (headroom's Kneedle adaptive-K is a documented refinement).
mask¶
Age-based garbage collection: keep the newest keep_recent tool outputs verbatim, replace older
ones (≥ min_tokens) with a short marker + stash. Complementary to the content-based offloaders.
- Config:
keep_recent(3),min_tokens(100). Shines: long agent trajectories where old tool results are unlikely to matter. Inert: ≤keep_recenttool outputs, small outputs.
phi_evict¶
Ranks tool outputs by a lean-ctx-style context-field score and evicts the lowest until the
transcript fits budget_tokens. Never evicts the most recent tool output.
- relevance = keyword overlap with the last user message; recency = position (newest = 1); cost = share of total tool tokens; redundancy = 1 if a duplicate seen earlier.
- Config:
budget_tokens(120000),weights—balanced{R.40,H.20,C.30,D.10},aggressive(punish cost:{.30,.10,.45,.15}),conservative({.45,.20,.05,.05}). - Shines: very long transcripts that must fit a context budget. Inert: total tool tokens ≤ budget, or ≤1 tool output. The scalar-ranking essence of lean-ctx's Context Field (the full heat-diffusion/MMR/bandit machinery is a documented refinement).
summarize (LLM)¶
Compresses the middle of the trajectory into one LLM-written summary (ported from CE-Manager's
ReSum-style summarizer). Restructures the message list to [msg0, <summary system message>, last-K];
the replaced span is stashed under a marker carried in the summary message, so expand restores the
full earlier trajectory. This is the one component that changes the message count — apply.Body
rebuilds the body keeping the retained messages byte-identical.
before: [system, u1, tool, a1, tool, u2, … 30 turns …, uN-1, uN]
after: [system, "=== History Summary === … <summary> … <<cg:…>>", uN-1, uN]
The summarizer is grounded in the current task (first user turn + recent turns are passed as "summarize toward this"), not a blind digest of the middle.
- Config:
summary_level(concise|regular|highly_detailed),keep_last(3),min_tokens(500 — span floor),include_tool_calls(false → tool outputs masked in the trajectory),model.source,trigger,resummarize_tokens(6000). - Gating + reuse: a
trigger(min_request_tokens,min_messages; legacystart_from_messagefolds intomin_messages) gates the first summary so it fires only on a large/deep transcript. After that, the summary is checkpointed per session and reused verbatim (no model call, and byte-identical so the prefix stays KV-cache stable) until the un-summarized tail grows pastresummarize_tokens, when the checkpoint rolls forward with a fresh summary. This is what stops it re-summarizing every turn. - Shines: long agentic sessions where the bulk is stale middle context. Inert: transcript
below
trigger, span belowmin_tokens, or no model available (no-op). Run it alone (its own preset) — it restructures the whole transcript.
The DSL filter engine¶
components/dsl is a declarative, user-extensible text-filter engine (adapted from rtk), wrapped
by cmdfilter. Filters are authored in YAML (no recompile), matched first-by-sorted-name, and each
runs a fixed 8-stage pipeline. Because filters drop lines they are lossy, which is why the
wrapping cmdfilter component is an Offload (it stashes the original first).
flowchart LR
I[input] --> S1[1 strip_ansi] --> S2["2 replace[]"] --> S3["3 match_output[] + unless"]
S3 --> S4[4 strip / keep lines] --> S5[5 truncate_lines_at] --> S6[6 head / tail]
S6 --> S7[7 max_lines] --> S8[8 on_empty] --> O[output + Lossiness]
Filter fields (all optional except match): match (regex vs the selector = first non-empty
line), strip_ansi, replace (chained pattern→replacement, $1 backrefs), match_output
(whole-blob short-circuit: pattern/message/unless), strip_lines_matching xor
keep_lines_matching, truncate_lines_at (per-line char cap), head_lines/tail_lines,
max_lines (absolute cap with omission marker), on_empty (replacement when output is blank).
Lossiness reported back to cmdfilter (drives whether a recovery hint is appended): None
(nothing dropped / reversible reformat), Tail (a clean contiguous tail dropped), Whole
(non-contiguous or whole-blob loss).
schema_version: 1
filters:
pytest:
description: keep failures + summary, drop passing noise
match: "(pytest|=+ test session starts)"
strip_lines_matching: ["^\\s*$", " PASSED", "^\\.+$"]
max_lines: 80
on_empty: "pytest: all passed"
tests: # inline; run via dsl.RunTests (a `verify` command)
pytest:
- name: all-green
input: "pytest\n....\n"
expected: "pytest: all passed"
Documents load with schema_version: 1 and strict unknown-field rejection. Inline tests
(input → expected) run via dsl.RunTests, so a filter ships with its own regression check.