One strict YAML struct serves both hosts (the proxy loads a file; the AuthBridge
plugin hands its config: subtree to the same loader). A preset expands to a
default pipeline; explicit fields override it.
The document has six top-level fields (from the Config struct in
config/config.go):
| Field | Type | Role |
|---|---|---|
preset |
string | Named default pipeline (see Presets). |
pipeline |
[]string |
Ordered component names — controls order + enablement. Overrides the preset's pipeline when present. |
components:<name> |
map | Each component's typed config block, handed to its constructor verbatim. |
store |
object | State store options — see store below. |
mode |
string | Operating mode: sync (default) | observe. See Operating modes. |
observe |
object | Observe-mode tuning; ignored in sync mode. |
| Field | Default | Purpose |
|---|---|---|
enabled |
true |
Toggles the state store. false wires a store.Nop: nothing is stashed, so offloads become one-way and must run marker_mode: off. |
ttl_seconds |
10000 |
Entry lifetime, and it slides — a Get refreshes the deadline, so an entry replayed every turn never ages out. Raised from 1800 because Terminal-Bench tasks average ~1975 s of wall clock and run to 4 h, so the old default expired live frozen decisions mid-task. |
max_entries |
1000 |
LRU cap. Frozen-decision keys (cg:frz:, cg:res:, cg:len:) are pinned — exempt from LRU eviction, because losing one is cache-destructive rather than merely a miss. The pin is capped at half max_entries, and eviction reclaims expired entries first (pinned included). |
max_sessions |
100 |
Cap on per-session sticky-id sets. |
The pinned prefixes are a code-level property of the key layout, supplied by their owners via
store.Options.PinPrefixes — not a YAML knob.
| Value | Behavior |
|---|---|
sync (default) |
Compact inline; the caller waits. Byte-identical to the behavior before modes existed. |
observe |
Forward the request untouched and report what compaction would have saved, under potential_* / projected_* keys. The request path never runs the pipeline and skips expand.Inject too (a tool declaration is a modification), so byte-identity is structural. |
Always explicit — nothing infers it from the rest of the configuration.
!!! note "An async mode is designed but not shipped"
A third mode deferring compaction off the request path is implemented on a separate
branch and deliberately held pending a benchmark arm establishing a benefit. sync and
observe are the only values the loader accepts.
| Field | Default | Purpose |
|---|---|---|
max_queue |
256 |
Bound on the off-path measurement queue. A full queue drops (counted as dropped) and never blocks the request path. |
workers |
1 |
Drain goroutines. One keeps a single measurement's cheap-model call in flight per process, which keeps that spend and gateway rate limits predictable. |
!!! warning "Strict: unknown keys are rejected"
The YAML loader runs with KnownFields(true), so a typo'd key fails loudly
at load time rather than being silently ignored.
preset: balanced
pipeline: [format, dedup, failed_run, cmdfilter, cachesplit] # order + enable
components:
collapse: { max_tokens: 2000, head_lines: 20, tail_lines: 20 }
smartcrush: { min_items: 5, keep_first: 3, keep_last: 2 }
cmdfilter: { min_size: 500 }
store: { ttl_seconds: 10000, max_entries: 1000 }
mode: sync # sync | observeA component registers its constructor + config type via init(), so adding one
makes it YAML-configurable with no core edit. See Components
for every component's config block.
| Flag / env | Default | Purpose |
|---|---|---|
--preset / PRESET |
codesmart |
Pipeline preset when no --config. |
--config / CONFIG |
— | YAML config file (overrides preset). |
LISTEN_ADDR |
:4000 |
Listen address. |
--openai-upstream / OPENAI_UPSTREAM |
https://api.openai.com |
OpenAI upstream base. |
--anthropic-upstream / ANTHROPIC_UPSTREAM |
https://api.anthropic.com |
Anthropic upstream base. |
OPENAI_API_KEY / ANTHROPIC_API_KEY |
— | Real key injected on forward (gateway mode); empty = pass client auth through. |
CHEAP_MODEL (+ CHEAP_MODEL_BASE / _KEY / _AUTH / _PROVIDER) |
— | Dedicated cheap model for the LLM components (extract_llm, summarize) — the model.source: config client. Without it they no-op. |
FORCE_MODEL |
— | Overwrite the request model (eval-containers uses EVAL_MODEL). |
INJECT_EXPAND |
auto |
Whether the context_guru_expand tool is advertised: auto (only when the request already declares tools and the store persists) | always | never. |
CACHE_MODE |
auto |
Cache-aware compaction: auto (on when the agent sets its own breakpoints) | on | off. |
MODEL_INFO_URL / MODEL_INFO |
LiteLLM map | Source for context-window sizes (used by the fractional triggers). MODEL_INFO=off disables the lookup; fractions are then ignored and absolutes apply. |
--store / STORE |
on | Enable/disable the state store; --store=false disables offload reversibility. Wins over the file's store: block. |
--mode / MODE |
sync |
Operating mode: sync | observe. Wins over the file's mode:. |
extract_llm's economic gate only calls the LLM when the
expected saving exceeds the expected cost, so it needs the real price of a call. The cost is
computed from observed token usage × these rates — never a hard-coded per-call constant.
Defaults are claude-haiku-4-5 list rates; override them to match your contract.
| Env | Default | Purpose |
|---|---|---|
CHEAP_MODEL_PRICE_IN |
1.00 |
Extraction-model input price, dollars per million tokens. |
CHEAP_MODEL_PRICE_OUT |
5.00 |
Output price per MTok. |
CHEAP_MODEL_PRICE_CACHE_WRITE |
1.25 |
Cache-write price per MTok (1.25× input). |
CHEAP_MODEL_PRICE_CACHE_READ |
0.10 |
Cache-read price per MTok (0.1× input). |
An unparseable or absent value silently keeps the default — pricing must never fail a request.
!!! note "extract_llm is off by default on caching backends"
Independently of pricing, the component declines to run on prompt-caching traffic unless
allow_on_caching_backend: true is set — measured net-negative there. See
extract_llm.
The dashboard is off by default. Enabling it adds /dashboard/ and
/api/*; nothing else about the proxy changes.
| Flag / env | Default | Purpose |
|---|---|---|
--dashboard / DASHBOARD |
off | Enable the persistent dashboard (embedded UI + JSON/SSE API). |
--dashboard-db / DASHBOARD_DB |
./context-guru-dashboard.db |
SQLite path. :memory: keeps history in RAM only (the no-persistence mode). An unwritable path falls back to in-memory with a warning rather than failing to start. |
--dashboard-retention / DASHBOARD_RETENTION |
168h (7 days) |
Drop rows older than this. 0 disables the age rule. |
--dashboard-max-bytes / DASHBOARD_MAX_BYTES |
536870912 (512 MiB) |
Cap the database size, dropping the oldest requests first. 0 disables the size rule. |
--dashboard-content / DASHBOARD_CONTENT |
false |
Capture before/after message text for the diff view. Opt-in: it stores arbitrary agent output on disk, scrubbed of known credential shapes and size-capped before storage — but content cannot be allowlisted the way headers and config keys are, so the safe default is off. |
--dashboard-content-cap / DASHBOARD_CONTENT_CAP |
16384 |
Maximum bytes stored per captured before/after blob. |
--dashboard-queue / DASHBOARD_QUEUE |
4096 |
Capture-channel depth. A full channel drops events (counted, and shown in the UI) rather than delaying a request. |
--dashboard-trusted-cidrs / DASHBOARD_TRUSTED_CIDRS |
— | Comma-separated CIDRs allowed to view per-request content and the effective config. Loopback always is; aggregates are open to everyone. |
--dashboard-bench-dirs / DASHBOARD_BENCH_DIRS |
— | Comma-separated directories of benchmark runs (each with summary.json + rows-*.json) to ingest at startup. Re-ingesting replaces a run rather than duplicating it. |
!!! note "Retention is bounded by age AND size" Age alone cannot bound a burst of traffic; size alone silently erases a quiet week. The age rule runs first, then the size rule drops the oldest remaining requests until the file fits.
!!! warning "There is deliberately no 'disable observability in production' switch" For a tool whose value is observability, that would be backwards. What is gated is per-request content and the effective configuration — not the metrics.
DASHBOARD=true \
DASHBOARD_DB=/var/lib/context-guru/dashboard.db \
DASHBOARD_RETENTION=720h \
DASHBOARD_MAX_BYTES=2147483648 \
DASHBOARD_TRUSTED_CIDRS=10.0.0.0/8,192.168.0.0/16 \
context-guru-proxy --preset codesmart| Env | Effect |
|---|---|
CONTEXT_GURU_DEBUG=1 |
Logs each tool output's token count + first line. |
CONTEXT_GURU_DUMP=<file> |
Appends a before → after JSON record per rewritten message. The dashboard captures the same material into a queryable store with a diff view. |
CONTEXT_GURU_CAPTURE=<file> |
Appends each pristine inbound request as one JSONL record, for offline replay through /compact. |