Context economy
Koryph's throughput is bounded by subscription quota windows. Every token an agent spends on Bash dumps, gate logs, or bead-workflow tutorials is a token it cannot spend on the actual work — and one fewer token of wave capacity available for the next dispatch. This chapter describes the native levers koryph uses to keep context lean.
Note: All features on this page operate inside the session and never rewrite Anthropic API request bodies. They are safe for subscription billing.
Token telemetry — koryph metrics tokens
Before optimizing, measure. koryph metrics tokens reads the ledger and
renders per-bead and per-tier token composition, cache-hit ratio, and a
tokens-per-bead trend.
koryph metrics tokens # table for all projects
koryph metrics tokens --project koryph # one project
koryph metrics tokens --json # machine-readable
Reading the table. Three sections are shown:
- Project summary — total beads with token data, mean tokens/bead,
cache-hit ratio (
cache_read / (cache_read + input)). - Per-tier breakdown — same columns split by model tier (haiku / sonnet / opus / fable). Useful for auditing whether secondary spawns are landing on the right tier.
- Per-bead detail — one row per closed bead, newest first, showing input / cache_creation / cache_read / output token counts and the cache-hit ratio for that bead.
The cache-hit ratio is the key health metric. A healthy koryph fleet typically runs at ≥ 90 % cache reads. A sudden drop in the ratio usually indicates a context-bust: something caused the cached prompt prefix to be rewritten, turning cheap cache reads into expensive cache writes. The engine's I7 cache-ratio tripwire logs a warning when this happens on any session with material token volume.
Older ledger entries without token fields (dispatched before koryph-77r.1 landed) appear as zero and are excluded from the breakdown. Run the command periodically after a few waves; the data accumulates automatically.
The cache-ratio tripwire
The engine evaluates a separate ratio for each attempt:
cache_read / (input + cache_read + cache_creation). When this falls below
the hard-coded floor of 0.50 on a session with at least 20 000 total
tokens (input + cache_read + cache_creation), the engine emits one
slog.Warn log record. This is observability only — it never changes
dispatch behavior, does not write to the ledger, and is not surfaced in
koryph metrics tokens. Check your structured log output (e.g.
koryph run --log-level warn) to catch these events.
The volume gate (20 000 tokens) prevents false positives on the first turn of any dispatch, which has no established cache prefix yet. The per-attempt evaluation (not accumulated totals) means a healthy early turn cannot mask a later attempt's prefix collapse.
Note: The tripwire ratio (
cache_read / (input + cache_read + cache_creation)) differs from theCacheHitRatiometric inkoryph metrics tokens(cache_read / (cache_read + input)), which omitscache_creationfrom the denominator. Both capture the same qualitative signal; the difference is small in practice.
Trimming the fixed prompt prefix — agent_mcp
Every dispatched agent opens with a fixed context prefix — system prompt, tool schemas, and any ambient MCP-server instructions the machine has configured — that is re-read on every turn of the session. koryph implementer personas use only file and Bash tools, so a full MCP suite in that prefix is dead weight: it inflates the per-turn cache-read bill (see Token telemetry) without ever being called.
Set the registry field agent_mcp to "strict" (in
~/.koryph/registry.d/<project-id>.json) to pass --strict-mcp-config on the
implementer dispatch, so the agent loads no ambient MCP servers:
{ "agent_mcp": "strict" }
The default (""/"inherit") is unchanged behavior — dispatch argv is
byte-identical — so turning this on is an explicit, per-project opt-in. Leave it
unset for any project whose agents genuinely need an MCP server. See the
registry record fields.
Quiet gate — make gate-agent
make gate is the human-facing green gate. For agents, use make gate-agent
instead.
gate-agent runs the identical checks in the identical order with the
identical fail-fast semantics. The difference is its output:
- Each stage's full raw output (stdout + stderr, untruncated) is teed to
$GATE_LOG_DIR/gate-<stage>.log. - stdout emits one
PASSorFAILline per stage. OnFAIL, a short tail (≤ 40 lines) of that stage's log is printed inline, plus a path to the full log. - The script exits 0 iff all stages pass — the same zero/non-zero contract as
make gate.
make gate-agent # runs checks; full output at $GATE_LOG_DIR
GATE_LOG_DIR=/tmp/g make gate-agent # override log dir
Inside a koryph dispatch, $GATE_LOG_DIR resolves to $KORYPH_PHASE_DIR
(the phase directory for the current bead run). Full stage logs are available
there via Read if you need to diagnose a failure.
The package-test stage deliberately runs in a neutral environment: dispatch
metadata is removed and KORYPH_HOME plus KORYPH_BD_BIN point at disposable
fixtures. The wrapper retains $KORYPH_PHASE_DIR, so gate logs still land with
the dispatch artifacts. If it cannot prepare either fixture, the stage fails
before package tests begin rather than falling back to the operator's Beads
configuration.
Lint output is additionally quieted in gate-agent (via make
lint-agent) by passing --output.text.print-issued-lines=false to
golangci-lint. Findings are identical; the inline source-snippet bytes are
suppressed. Pass verdict equivalence — same issues, same exit code — is tested
in scripts/gate_agent_test.go.
Agents: prefer
make gate-agentovermake gate. The summarizer pattern means failures are still surfaced inline; full context is oneReadaway via the log path.
Wrappers and file-spill
The principle: run the command, write its complete untruncated output to a
file under the phase directory, emit a brief summary plus a full output:
<path> line to stdout.
This is koryph's native equivalent of headroom-ai's context-compressive
reversibility (CCR). No proxy, no injected tools, no TTL store, no API
interception — just a file-spill recoverable via the agent's own Read tool.
make gate-agent is the primary shipped wrapper. The pattern it follows:
==> fmt-check: PASS
==> build: PASS
==> vet: PASS
==> test: FAIL (exit 1)
----- tail: /path/to/phase/gate-test.log -----
--- FAIL: TestFoo (0.12s)
foo_test.go:42: got bar, want baz
----- end tail -----
full output: /path/to/phase
Recovering the full output
If the summary is insufficient, the full untruncated log is at the path shown:
# In an agent — read the spilled log directly:
# Read /path/shown/in/full-output/line/gate-test.log
The phase-directory logs are retained by internal/gc for the same duration
as the rest of the bead run artifacts (default: 7 days after bead close).
Design invariant: summarizers never hide failures
Error lines are reproduced verbatim. A failing stage always results in a
non-zero exit. The wrapper cannot turn a failing gate into a reported PASS.
This is tested by scripts/gate_agent_test.go's seeded-failure test, which
verifies that a gofmt violation surfaces identically in make gate and
make gate-agent.
Output caps
Claude Code exposes two tool-output size knobs. Koryph injects conservative
defaults through account.ChildEnvSpec — a single point applied uniformly
to all four spawn sites (main dispatch, reviewer, stage, epic reviewer):
| Env var | Koryph default | Effect |
|---|---|---|
BASH_MAX_OUTPUT_LENGTH |
400 000 chars (~400 KB) | When a Bash tool_result exceeds this, Claude Code itself spills the full output to a temp file and hands the agent a path + short preview — Claude Code's own native file-spill (CCR). |
MAX_MCP_OUTPUT_TOKENS |
50 000 tokens | MCP tool results are truncated at this token count. |
The defaults are deliberately conservative: large enough that ordinary command
output — including make gate-agent's summary and 40-line failure tail — is
never touched; low enough to bound a pathological unbounded dump (e.g. an
agent inadvertently cat-ing a multi-GB file).
Overriding for a specific project
Set bash_max_output_length and max_mcp_output_tokens under the project's
registry record (or leave unset to inherit the package defaults):
{
"project": "my-project",
"bash_max_output_length": 200000,
"max_mcp_output_tokens": 25000
}
A negative value in ChildEnvSpec omits the env var entirely, reverting to
Claude Code's own (effectively unbounded) behaviour. This is an explicit
opt-out escape hatch for projects with unusual tooling.
Verbose-command guard
The engine's PreToolUse Bash guard nudges (deny-with-message) the worst
patterns for token inflation:
| Pattern | Guidance issued |
|---|---|
go test ./... -v |
Use make gate-agent; full -v output is spilled to the phase log |
golangci-lint run (without quiet flags) |
Use make lint-agent (same findings, no inline source snippets) |
The guard points at the quiet target rather than silently blocking; agents can always re-run the raw command when they need the full output.
bd-prime slimming
bd prime --hook-json injects the full bead-workflow context into every
Claude Code session via the SessionStart hook. Full context (~20 KB) is
appropriate for main-dispatch agents working the bead queue. It is unnecessary
for secondary spawns (reviewers, stage workers, epic reviewers), which have no
bead-workflow responsibilities.
The hooks/koryph-prime.sh wrapper (shipped as part of koryph rules
install) implements dispatch-aware profile selection keyed on
$KORYPH_SPAWN_KIND:
$KORYPH_SPAWN_KIND |
Profile injected |
|---|---|
| unset (interactive / operator session) | Full bd prime --hook-json output, byte-identical |
main (primary dispatch agent) |
Full bd prime --hook-json output, byte-identical |
review, stage, epicreview |
Slim profile (< 500 bytes): spawn kind + phase dir pointer; no bead-workflow tutorial |
The slim profile is a small JSON-shaped hook payload that identifies the spawn kind and points at the phase directory. It omits all bead-workflow tutorial content, reducing per-session prefix bytes for secondary spawns.
Byte accounting. The wrapper logs the injected byte count to
$KORYPH_DIR/prime-size.log (never to stdout, so it does not pollute the
session context). Each line records the spawn kind, the profile mode used, and
the byte size. Use this to audit how much prefix each spawn type is consuming.
Recovering bead context from a secondary spawn
Secondary spawns that need bead context can still retrieve it:
bd show $KORYPH_BEAD_ID # bead detail, including the plan
bd prime # full workflow context (without --hook-json)
$KORYPH_BEAD_ID is set by the engine on every dispatch.
Agent guidance summary
| Practice | Why |
|---|---|
Run make gate-agent, not make gate |
One PASS/FAIL per stage; full logs in phase dir |
Read spilled log paths, don't re-run with -v |
The full output is already there |
Use make lint-agent instead of golangci-lint run |
Same findings; no inline source-snippet bytes |
Check koryph metrics tokens after a wave |
Catch cache-ratio collapses early |
Avoid go test ./... -v in agent Bash calls |
The PreToolUse guard will nudge you to the quiet target |