agent-context-economy
Make a vertical agent cheaper and more accurate at once by treating its context as a memory
hierarchy and optimizing the hot path — proving each change with a gated A/B, not vibes.
When to use this
Trigger if:
- You own the agent's system prompt, tools, and result-handling (not just a chat client).
- The agent runs multi-round tool loops, or is high-traffic enough that per-conversation cost matters.
- You suspect context bloat: huge tool schemas, raw tool dumps, a sprawling tool list.
Don't bother if: it's a one-shot prompt, or you don't control the agent's context/tooling (then
see output-compression skills instead — this is about the agent's input economy, not its output).
Core idea
A good vertical agent is a faithful compression of its task distribution. With the model fixed,
accuracy and cost are functions of context quality: bloat buries the signal and costs tokens;
missing context forces guessing. So structure the context like a CPU cache:
- L1 — always resident (system prompt, the bread-and-butter tool I/O): tiny, instant, paid every
request. Spend disproportionate effort making it dense.
- L2 — on demand (curated specs, full tool schemas): one cheap discovery step to load.
- L3 — escape hatch (the raw catalog): reachable in a bounded number of steps, never resident.
Most agent frameworks already do some of this. The wins are usually on the hot path the
framework left raw. Audit first — don't rebuild what exists.
The levers, ranked by leverage
1. Cache the static tool-definition prefix (usually the biggest, and lossless)
The full tool schemas are often the single largest block re-sent on every round, and many
providers/SDKs do NOT cache them by default (only the system prompt). Put a prompt-cache breakpoint
on the tool-definitions block so it becomes a near-free cache READ every round after the first.
- It's lossless — no behavior change — so it can ship default-on once measured.
- Providers cap the number of cache breakpoints (e.g. 4). Rebalance: spend a breakpoint on the
large, stable tool block and DROP it from the most volatile section (per-run memories/dynamic
context that invalidates every request — caching it only ever writes, never reads).
- Measured impact in one production deployment: −45% end-to-end cost (−71% in a controlled
identical-payload loop), zero quality change. The tell is cache-WRITE tokens collapsing.
2. Compress large tool RESULTS at insertion time (lossless, domain-aware)
Big tool outputs (tabular query results, list/search responses, large JSON) are often inserted into
context RAW and only truncated reactively when the window is nearly full. Instead, compress them the
moment they enter context, per tool, losslessly:
- Attach the header/schema row once; alias repeated structure (e.g. a column legend instead of
repeating keys on every row).
- Sample rows with an explicit "… N more rows" tally rather than dumping all.
- Keep it reversible: a legend the model can read, not lossy truncation.
- This only pays where large results are actually fed to the model. Target those tools/agents;
on agents that mostly do single-object reads it barely fires (measure, don't assume).
3. Lint write-tool outputs (correctness, cheap)
When the agent performs a write (publishes, drafts, files), append a categorized review to the
result — "did X; N fine; 1 needs review" — flagging unresolved template vars, placeholder text,
suspicious/off-domain links, empty required fields, invalid-output markers. A built-in linter on the
agent's own edits catches a whole class of plausible-but-wrong outputs for a few tokens.
4. Right-size tool count (sharper, sometimes cheaper)
More tools = more schema surface = more ways to pick wrong. Fewer resident tools measurably improves
quality (in one experiment, +~9 quality points with a trimmed set). BUT: do lever #1 first — once
the tool prefix is cached, the cost argument for trimming weakens, and trimming becomes mainly a
quality/accuracy lever (and a capability tradeoff — you can't use a tool you removed). Treat it as a
quality experiment, not a cost cut.
The method: off-by-default + eval-gated promotion
Never ship an optimization on belief. For each lever:
- Build it additive and OFF by default behind a runtime flag (a config value, not a redeploy,
so promotion is instant and reversible). Merged code is inert until proven.
- Build a before/after A/B harness that runs a fixed, representative task set under named
variants (
baseline vs optimization-on) and records per run: input/output tokens, cost,
latency, and a quality score (reuse your existing scorer/judge — don't invent one).
- Gate promotion on a measured delta, with the right success criterion per lever:
- lossless compression / caching → cost down AND quality flat (±noise) AND zero errors.
- correctness linting → quality up-or-neutral (a small token cost is acceptable).
- tool trim → quality up (it's a quality lever).
- Flip only if the gate passes. A regression — or noisy/inconclusive data — means DON'T flip.
Report the numbers and the root cause instead.
A/B hygiene (these silently corrupt results)
- Prompt-cache contamination: if prompt caching is on, tokens shunt into cache-read and
sequential variants share the warm cache — your "after" looks artificially cheap. For token
deltas, disable prompt caching and pin the model. (Exception: when CACHING itself is the thing
you're testing, run with caching ON and compare cache-read tokens / cost, not raw input tokens.)
- Sub-agent variance: if the agent can delegate to other agents, deny that during the A/B —
sub-agent runs inject huge run-to-run variance and swamp the signal.
- Sample size: small n + LLM judge variance is unreliable. Use enough fixtures (≥12–15) weighted
to the real task mix, and don't over-read a few points of quality movement.
- No side effects: evals must not perform real outbound writes — deny or mock write tools (but if
you're testing the write-linter, you need the write to execute, so MOCK it, don't exclude it).
Hard-won lessons
- Don't DEFER tools — CACHE them. Lazy-loading tool schemas for a restricted agent backfired
(tokens went UP) because schemas re-send every round anyway and deferral just adds discovery rounds
that re-send the full message history. Caching the prefix (lever #1) is the real fix.
- Target where the win actually fires. The result-compressor was inconclusive on a triage agent
that does single-object reads, but a clean win on an analytics agent that feeds large query results
to the model. The eval gate refusing the wrong-target flip is itself a correct outcome — it
steered effort to the right target.
- Negative results are wins. An optimization that measurably makes things worse, caught by the
gate before prod, saved a regression. Ship it dormant and move on.
- Audit before building. The hierarchy, caching, and an eval system may already half-exist.
Extend, don't duplicate.
If you run these experiments with autonomous agents
- Make each experiment a single self-contained pass: build → test → PR → CI → A/B → gated flip →
report. A background worker that kicks off a long step (CI watch, the A/B run) and then yields
expecting to be "woken up" will often just stop with the work unfinished — run long steps in the
foreground/blocking within one continuous pass.
- Keep the flip a config write (not a redeploy) so an autonomous run can promote — and roll back —
instantly and verifiably.
1---2name: agent-context-economy3description: Cut the token cost and sharpen the accuracy of a vertical agent you build and operate — treat its context as an L1/L2/L3 cache and attack the hot path (cache the tool-definition prefix, compress tool RESULTS at insertion, lint write outputs, right-size tool count), promoting each change only through a gated before/after A/B. Use when an agent is expensive/slow, tool schemas bloat every request, or before pushing a high-traffic agent.4---56# agent-context-economy78Make a vertical agent **cheaper and more accurate at once** by treating its context as a memory9hierarchy and optimizing the hot path — proving each change with a gated A/B, not vibes.1011## When to use this1213**Trigger if:**14- You own the agent's system prompt, tools, and result-handling (not just a chat client).15- The agent runs multi-round tool loops, or is high-traffic enough that per-conversation cost matters.16- You suspect context bloat: huge tool schemas, raw tool dumps, a sprawling tool list.1718**Don't bother if:** it's a one-shot prompt, or you don't control the agent's context/tooling (then19see output-compression skills instead — this is about the agent's *input* economy, not its output).2021## Core idea2223A good vertical agent is a **faithful compression of its task distribution**. With the model fixed,24accuracy and cost are functions of context quality: bloat buries the signal *and* costs tokens;25missing context forces guessing. So structure the context like a CPU cache:2627- **L1 — always resident** (system prompt, the bread-and-butter tool I/O): tiny, instant, paid every28 request. Spend disproportionate effort making it dense.29- **L2 — on demand** (curated specs, full tool schemas): one cheap discovery step to load.30- **L3 — escape hatch** (the raw catalog): reachable in a bounded number of steps, never resident.3132Most agent frameworks already do some of this. The wins are usually on the **hot path** the33framework left raw. Audit first — don't rebuild what exists.3435## The levers, ranked by leverage3637### 1. Cache the static tool-definition prefix (usually the biggest, and lossless)38The full tool schemas are often the **single largest block re-sent on every round**, and many39providers/SDKs do NOT cache them by default (only the system prompt). Put a prompt-cache breakpoint40on the tool-definitions block so it becomes a near-free cache READ every round after the first.4142- It's **lossless** — no behavior change — so it can ship default-on once measured.43- Providers cap the number of cache breakpoints (e.g. 4). **Rebalance:** spend a breakpoint on the44 large, *stable* tool block and DROP it from the most *volatile* section (per-run memories/dynamic45 context that invalidates every request — caching it only ever writes, never reads).46- Measured impact in one production deployment: **−45% end-to-end cost** (−71% in a controlled47 identical-payload loop), zero quality change. The tell is cache-WRITE tokens collapsing.4849### 2. Compress large tool RESULTS at insertion time (lossless, domain-aware)50Big tool outputs (tabular query results, list/search responses, large JSON) are often inserted into51context RAW and only truncated reactively when the window is nearly full. Instead, compress them the52moment they enter context, per tool, losslessly:53- Attach the header/schema row once; **alias repeated structure** (e.g. a column legend instead of54 repeating keys on every row).55- Sample rows with an explicit "… N more rows" tally rather than dumping all.56- Keep it reversible: a legend the model can read, not lossy truncation.57- This only pays where **large results are actually fed to the model.** Target those tools/agents;58 on agents that mostly do single-object reads it barely fires (measure, don't assume).5960### 3. Lint write-tool outputs (correctness, cheap)61When the agent performs a write (publishes, drafts, files), append a categorized review to the62result — "did X; N fine; 1 needs review" — flagging unresolved template vars, placeholder text,63suspicious/off-domain links, empty required fields, invalid-output markers. A built-in linter on the64agent's own edits catches a whole class of plausible-but-wrong outputs for a few tokens.6566### 4. Right-size tool count (sharper, sometimes cheaper)67More tools = more schema surface = more ways to pick wrong. Fewer resident tools measurably improves68quality (in one experiment, +~9 quality points with a trimmed set). BUT: **do lever #1 first** — once69the tool prefix is cached, the *cost* argument for trimming weakens, and trimming becomes mainly a70*quality/accuracy* lever (and a capability tradeoff — you can't use a tool you removed). Treat it as a71quality experiment, not a cost cut.7273## The method: off-by-default + eval-gated promotion7475Never ship an optimization on belief. For each lever:76771. **Build it additive and OFF by default** behind a runtime flag (a config value, not a redeploy,78 so promotion is instant and reversible). Merged code is inert until proven.792. **Build a before/after A/B harness** that runs a fixed, representative task set under named80 variants (`baseline` vs `optimization-on`) and records per run: input/output tokens, cost,81 latency, and a quality score (reuse your existing scorer/judge — don't invent one).823. **Gate promotion on a measured delta**, with the right success criterion per lever:83 - lossless compression / caching → cost down AND quality flat (±noise) AND zero errors.84 - correctness linting → quality up-or-neutral (a small token cost is acceptable).85 - tool trim → quality up (it's a quality lever).864. **Flip only if the gate passes.** A regression — or noisy/inconclusive data — means DON'T flip.87 Report the numbers and the root cause instead.8889## A/B hygiene (these silently corrupt results)9091- **Prompt-cache contamination:** if prompt caching is on, tokens shunt into cache-read and92 *sequential* variants share the warm cache — your "after" looks artificially cheap. For token93 deltas, **disable prompt caching and pin the model**. (Exception: when CACHING itself is the thing94 you're testing, run with caching ON and compare cache-read tokens / cost, not raw input tokens.)95- **Sub-agent variance:** if the agent can delegate to other agents, deny that during the A/B —96 sub-agent runs inject huge run-to-run variance and swamp the signal.97- **Sample size:** small n + LLM judge variance is unreliable. Use enough fixtures (≥12–15) weighted98 to the real task mix, and don't over-read a few points of quality movement.99- **No side effects:** evals must not perform real outbound writes — deny or mock write tools (but if100 you're testing the write-linter, you need the write to execute, so MOCK it, don't exclude it).101102## Hard-won lessons103104- **Don't DEFER tools — CACHE them.** Lazy-loading tool schemas for a restricted agent *backfired*105 (tokens went UP) because schemas re-send every round anyway and deferral just adds discovery rounds106 that re-send the full message history. Caching the prefix (lever #1) is the real fix.107- **Target where the win actually fires.** The result-compressor was inconclusive on a triage agent108 that does single-object reads, but a clean win on an analytics agent that feeds large query results109 to the model. The eval gate *refusing* the wrong-target flip is itself a correct outcome — it110 steered effort to the right target.111- **Negative results are wins.** An optimization that measurably makes things worse, caught by the112 gate before prod, saved a regression. Ship it dormant and move on.113- **Audit before building.** The hierarchy, caching, and an eval system may already half-exist.114 Extend, don't duplicate.115116## If you run these experiments with autonomous agents117118- Make each experiment a single self-contained pass: build → test → PR → CI → A/B → gated flip →119 report. A background worker that kicks off a long step (CI watch, the A/B run) and then yields120 expecting to be "woken up" will often just stop with the work unfinished — run long steps in the121 **foreground/blocking** within one continuous pass.122- Keep the flip a config write (not a redeploy) so an autonomous run can promote — and roll back —123 instantly and verifiably.