DEV Community

Steven Gonsalvez
Steven Gonsalvez

Posted on • Originally published at stevengonsalvez.com

The Token Optimisation Playbook

Video walkthrough

Watch the video walkthrough on the original post

Most of your context window is spent on output nobody reads, and most of your premium spend goes on work a cheaper model does just as well. A cargo test run is 25,000 tokens of green dots. A 4,000-line git log gets read by Opus to find one commit. The model pays for all of it, on every turn, and the bill compounds.

There are only two fights worth having. Compress what reaches the model, so each turn carries less. And route who does the work, so your best model spends its tokens on judgement, not grunt. Everything below is one of those two, plus a long tail of smaller levers that each shave a bit off every turn.

This is the capstone. The series so far:

  • Part 1, Token Optimisation 101. The basics: how the context window re-bills you on every turn, capping conversations before they bloat, routing models by task, and killing the idle features that quietly cost you.
  • Part 2, Progressive subagents. Don't spawn eight subagents at the top ceiling every time. Score the work first and scale the agent count to the actual job, so usage tracks effort instead of sitting maxed.

This post pulls all of that together, plus everything I've picked up since.

On this page

01 · Observability first: you can't optimise what you can't see {#observability}

sketchnote: see the tokens - statusline, burndown, savings, live sessions, OTEL, grafana, cache hit, measure first

Every lever below is guesswork until you can see where your tokens actually go. Knowing is the whole game. Most people have no instrumentation at all, so they "feel" expensive and tune the wrong thing. Get the observability in first, then optimise against real numbers instead of vibes.

agents-in-a-box bakes the whole stack in. It reads the session JSONL that Claude Code, Codex, Gemini and Copilot already write, so there is nothing to instrument by hand.

1. A statusline that puts the session in your prompt. Model, effort, context fill, the 5-hour burn window, the weekly window, session cost, and live cache columns (TOK, UNC, CHR, OUT) with a green / amber / red legend. Green is cache hit above 70%, red is an uncached spike, the THR bar flags cache invalidation. You see a runaway turn the moment it happens, not on next month's bill.

agents-in-a-box statusline showing context %, burn windows, cost and live cache columns

2. Plan-window status, so you time the heavy work. How much of your 5-hour and weekly plan window you've burned, per provider. Optimise or guzzle deliberately: save the big fan-out for when the window has just reset, don't hit the ceiling mid-task.

status bar showing claude and codex 5-hour and weekly plan-window usage

3. A full burndown to explore. Cost, calls, sessions, cache hit, broken down by project, branch, model, activity, core tools, even MCP servers. Optimization recommendations and an agent leaderboard sit next to budget alerts. This is where you catch a malfunctioning loop, a runaway session, or an expensive model doing cheap work. The Savings tab tells you what each compressor actually saved (Headroom and RTK measured live, Caveman modelled), so you optimise against measured numbers, not headline claims.

burndown TUI: cost, cache hit, breakdowns by project, branch, model, tools, budget alerts

token savings tab: Headroom live, RTK installed, Caveman modelled, net measured

4. Live usage across every session at once. Quota windows, per-session context fill, tokens, memory and turn count for everything running right now. Catch the session sitting at 96% context before it falls over.

abtop live view: every running session with context %, tokens, memory and turn count

5. OTEL straight into Grafana. Wire the built-in OpenTelemetry export and you get proper Claude Code and Codex dashboards: cost rate by model, tokens by type, cost by skill, turn latency p95. The same numbers, in the tool your team already watches.

Grafana Claude Code usage and cost dashboard

Grafana Codex CLI usage and telemetry dashboard

6. Compression configured per session. Flip Headroom and RTK on or off at launch, per session, so token-heavy runs get compression and latency-sensitive ones don't. The knobs from the next section, wired into the session itself.

new-session screen with Headroom and RTK toggles

Once you can see it, you know which problem you actually have: a tool-output problem, a cache problem, or an expensive-model-on-cheap-work problem. They have different fixes, and the rest of this post is those fixes.

02 · Compress what reaches the model {#compress}

sketchnote: compress the context - rtk at the shell, headroom on the wire, failures only, schema not values, cache align, keep a hatch

Two tools sit at the front of this fight, from opposite ends. The agent is in the middle: rtk guards the way IN, headroom guards the way OUT.

   ┌───────┐   ┌─────┐   ┌───────┐   ┌──────────┐   ┌───────┐
   │ shell │──▶│ rtk │──▶│ agent │──▶│ headroom │──▶│ model │
   └───────┘   └─────┘   └───────┘   └──────────┘   └───────┘
    tool         trims    (your        squeezes       the
    output       it       context)     the request    LLM

   · rtk       on the way IN:  trims tool output before it hits your context
   · headroom  on the way OUT: compresses the request before the model reads it
Enter fullscreen mode Exit fullscreen mode

02.1 · rtk - trim at the shell {#rtk}

rtk is a Rust binary that sits on your shell calls and filters the output before it reaches the model. No model calls, ~5-15ms.

  • Runs the command, then suppresses the noise: progress bars, success spam, verbose logging.
  • So git status and git log come back as a short summary, cargo test and npm test as failures only, and big JSON as its keys and types instead of every row.
  • Their table drops a 30-minute session from ~118k tokens to ~24k.
  • The catch (rtk says so itself): the hook only fires on Bash calls, so native Read, Grep and Glob bypass it. Force shell reads or you miss the biggest source of bloat, the agent reading files.

02.2 · headroom - compress on the wire {#headroom}

headroom sits between agent and model API, compressing tool output, logs, files and RAG chunks in the request itself.

  • CacheAligner lifts volatile tokens (dates, UUIDs) out of your prompt prefix, so the cache keeps hitting.
  • CCR caches the original under a hash, so the model can pull it back if it needs it.
  • Honest number, to their credit: the "60-95% fewer tokens" headline is real but only on tool-heavy sessions. Median across 50,000+ sessions was 4.8%.

They're not mutually exclusive. rtk at the shell, headroom on the wire, is a reasonable stack. I built an interactive feature map of how each works, how intrusive each mode is, and what each one misses:


OPEN THE RTK vs HEADROOM FEATURE MAP

how each works, intrusiveness, cons, when to reach for which

02.3 · ponytail - write less code {#ponytail}

ponytail compresses what the agent writes, not what it reads. It forces the laziest solution that works (YAGNI, stdlib first, one line over fifty).

  • Less code out means fewer output tokens, smaller diffs, and less to read back next turn.
  • Honest caveat: I only just started running it, so I can't put a clean number on the saving yet.
  • Mechanism is sound, the measurement is still pending.

02.4 · The moves underneath {#moves}

rtk and headroom are really just packaging. Underneath, both do the same handful of moves, and these are worth knowing on their own, because you can apply them by hand (in a hook, a wrapper script, or just how you prompt) without either tool installed:

  • Failures only. Tests, lint, builds: keep stderr and the summary, drop the passing lines.
  • Schema, not values. For big JSON, send keys and types, not 500 rows.
  • Stats, not detail. git status becomes "3 modified, 1 untracked".
  • Cache alignment. Keep volatile tokens out of your stable prefix. This is free and almost nobody does it.
  • Retrieval over deletion. Compress hard but keep an escape hatch so the model can pull the original instead of re-running the command.
  • Progressive disclosure. Hand the model signatures, let it ask for the body.

03 · Route who does the work {#route}

sketchnote: route the work - premium = judgement, cheap = grunt, delegate, advisor, model per task, signals not loops

Compression makes each turn lighter. Routing makes sure the expensive turns are the only ones you pay premium for. A premium main agent holds the context and the judgement, and hands the grunt work down to cheaper models.

03.1 · Premium delegates, cheap executes {#delegate}

The premium model should not read the barrage itself. It hands a narrow job to a cheap-model subagent and gets back the answer, not the noise.

  • Default: direct subagents, no infra. The lead spawns one, gets a structured result back.
  • Need isolation or persistence: a tmux agent via the coding-agent skill, or a small goal in a fresh session that runs and returns.

03.2 · Advisor: cheap drives, premium consults {#advisor}

A lesser model does the work and asks a stronger one only on the hard calls (sonnet to opus, gpt-5.4-mini to gpt-5.5). Claude Code ships this natively as /advisor, and the /oracle skill does the same thing, a stronger model on demand. Adversarial review and final checks go through the same door. You pay premium for the 10% that needs it.

03.3 · Model-per-task {#model-per-task}

Match the model and the effort to the job.

  • UI: design first (Google Stitch, Figma, Dribbble), generate on a cheap model with Impeccable and UI/UX Pro Max, then one adversarial pass with your prime model.
  • Boilerplate, UI, API wiring, DB glue: cheap model, low effort. It is grind, not judgement. Save the reasoning for the calls that actually branch.

03.4 · Handoffs {#handoffs}

Not a fixed taxonomy, just the common shapes. The rule under all of them: the agent in the loop never eats the barrage.

  • git archaeology, log triage, large-file extraction (cheap reads the noise → premium gets the finding)
  • run the tests, the bisect, the benchmark (cheap runs → premium gets pass/fail and failures only)
  • first-draft UI, scaffolding, docs, tests (cheap generates → premium reviews and fixes)
  • the genuinely hard call (cheap drives → premium advises via /oracle)

03.5 · Loops vs crons {#loops-crons}

Every tick of an agent loop is a full model turn, so most repeated work should not be a loop. Use a cron where you can. If you need a loop, have it react to signals a cron emits, not poll a corpus. Run code, not the agent: the agent reacts to signals, code produces them.

04 · The long tail of levers {#levers}

sketchnote: the long tail - caveman, handover, fewer MCPs, scoped scans, right validator, memory system, cap output

Each is small on its own and compounds across every session.

04.1 · Speak caveman {#caveman}

Make the agent talk terse. Drop the filler, keep the technical bits, watch the output tokens fall. More here.

04.2 · Hand over, don't wait to compress {#handover}

You don't have to fill a million-token window before you compact. The prefix that gets prepended each turn keeps going uncached, so you re-pay for the same tokens every turn. Hand over to a fresh session well before the window is full. Output also gets worse past about 60% full, so it's cheaper and sharper.

04.3 · Ditch most MCPs {#mcps}

They dump pages of JSON and HTML straight into context. A CLI does the same job and lets you filter: --json or JMESPath paired with jq, or rtk in front. Why MCP is mostly the wrong abstraction.

04.4 · Scope the scans and the instructions {#scope}

Point the agent at the relevant directory, not the whole monorepo. And split that giant CLAUDE.md: the agent reads AGENTS.md at folder level, so keep instructions next to the code they describe.

04.5 · Pick the right browser validator {#browser}

For dynamic agentic testing, Playwright is the wrong tool: scripts are generated on the fly, never stored, so the agent keeps re-producing them and re-burning tokens. browser-harness, a coordinate/CDP harness, is a bit better: you preconfigure the site navigation once so the nav code isn't rewritten each run. Best on this axis is Stagehand, which caches the resolved action and replays it at sub-100ms, zero LLM cost, self-healing when the DOM shifts. Full comparison.

04.6 · Scale subagents progressively {#subagents}

Don't spawn eight at the top ceiling every time. Score the work, let the score decide how many to spawn. How.

04.7 · Watch vision tokens {#vision}

Screenshots are expensive. Prefer DOM text and structured reads, and only screenshot when you actually need to verify pixels.

04.8 · Process media with a cheap model {#media}

Getting the data out of rich media is where the tokens go, not the data itself. A rich PDF can burn 80% of the tokens on the extraction alone, the actual content only the other 20%, and images and video are the same story. Two moves. First, do the mechanical part with no model at all: ffmpeg and ImageMagick transcode, sample frames, pull the audio track, resize and crop for zero LLM tokens. My media-processing skill wires that up. That preprocessing does more than save tokens, it lifts the quality a cheap model can reach. Crop to the region that matters, zoom in and sharpen it, and a weaker model (even Haiku) reads fine detail it would otherwise blur past, closing most of the gap to Opus on image work. Second, route whatever reading is left to a lesser model. Haiku, Sonnet and gpt-5.4-mini handle most PDFs, images, scanned docs and sampled video frames fine. The cheap ones (Haiku, gpt-5.4-mini) run at roughly a tenth to a fifth of the premium tier's price; Sonnet costs more but still reads media well below Opus rates. Then pass your premium model only the clean extract.

04.9 · Fetch pages as markdown, not raw HTML {#markdown}

Same idea, for the web. A plain WebFetch dumps a page's full HTML (nav, scripts, boilerplate) into context. markdown.new or the Jina reader hand back the same page as clean markdown, a fraction of the tokens, and the model reads it better too. Reach for those before you let the agent swallow raw HTML.

04.10 · Cap what comes back {#cap}

Budget what a subagent or tool hands back, or the unbounded return just dumps the barrage straight back into the main context and defeats the delegation. The how is prompt-level: tell the subagent what to return and how much (the top findings, a structured summary, failures only, never the raw dump), and wrap noisy tools to truncate before the output lands. The verdict comes home, not the mess.

04.11 · Build your own small skills {#skills}

A tiny skill solves ease and tokens at once. My standup skill prints status as a table, a fraction of the tokens of the agent narrating it in prose.

04.12 · Invest in a memory system {#memory}

Most waste is the agent re-learning the same things: tool calls it corrects over several turns, fixes you redo, instructions it keeps missing. A memory system injects the right indexed memory at the right moment. I use ainb-reflect-memory.

04.13 · Dodge peak hours {#peak}

This one doesn't save tokens, it saves your rate window. Since March 2026 Anthropic drains the 5-hour rolling window faster during peak, 5:00 to 11:00 AM Pacific (14:00 to 20:00 CET) on weekdays. Your weekly limit is unchanged, only the distribution, so shifting heavy runs off peak buys you fewer mid-task stalls.

04.14 · Turn thinking down {#thinking}

Reasoning isn't free context, it's billed output, and it's the priciest tap you leave running. On xhigh a single turn can top 30k tokens of pure reasoning, every turn, none of it cached across a plain loop, so you re-pay it each time it thinks. And output is the expensive side of the meter:

  • Output costs 5x input on Claude (Opus $5 in / $25 out per MTok, Sonnet $3 / $15, Haiku $1 / $5) and 6x on GPT-5 ($5 / $30, mini $0.75 / $4.50). Now put 30k of output-priced thinking on that, on every turn.
  • Reasoning tokens bill as output and, across a normal loop, aren't reused turn to turn, so the thinking is re-billed each turn it fires. (The newest Claude models can cache thinking blocks as input in tool-use continuations, which softens it, but don't design around it.)
  • The knob is non-linear. Rough ballpark from my own runs, not a published number: a job that sits near 10% of a session's allowance at medium can climb past 40% at xhigh for the same prompt, because the reasoning loop deepens faster than the effort dial suggests.
  • So default to medium or high and spend xhigh deliberately, on the hard 20% where the answer is worth the burn, not as an always-on setting.

05 · Keep loops and goals from running away {#loops}

sketchnote: keep the loop in check - same error bail, timeouts not a cap, max-turns, max-budget, pin the model, goal over loop

The most expensive bug in agent work is the loop that doesn't know it's stuck. Someone left Claude Code running overnight on a crashing script. It retried all night, escalated to Opus, and had eaten the budget by morning.

If you own the loop (a bash while, a Ralph-style runner), detect the repeat: hash the failure and bail on the Kth identical error. Same crash, same output, forever, is just paying to fail.

If Claude Code owns it (/loop, /goal), the timeouts aren't the guardrail they look like. The bash, API and subagent timeouts each kill one stuck call, but none of them stops the agent retrying the same failing thing over and over, every attempt comfortably under its ceiling.

The caps that actually bound a run, --max-turns and --max-budget-usd, are print-mode (claude -p) only. Interactive gets neither. So run anything unattended headless, capped, with the model pinned so it can't escalate to Opus:

# headless is the only mode with caps
export ANTHROPIC_DEFAULT_OPUS_MODEL="claude-sonnet-4-5"   # 'opus' now resolves to Sonnet
export CLAUDE_CODE_DISABLE_CRON=1                          # no stray scheduled loops

claude -p "$(cat task.md)" --model sonnet --max-turns 15 --max-budget-usd 20
Enter fullscreen mode Exit fullscreen mode

What none of it catches: no session-wide iteration cap, no circuit breaker for a turn that just burns tokens thinking, and interactive has no cost ceiling at all. Prefer /goal over /loop when you can, its condition gets re-checked every turn. You're stacking a few bounded systems, not one master safety switch. Three of those systems you build yourself.

Cap the iterations, not just the turn. The timeouts bound one call; nothing bounds how many times the loop goes round. If speed isn't the point, run it sparse: an explicit turn cap (--max-turns 25 in print mode) is a hard stop the loop can't talk its way past, and fewer, chunkier turns cost less than a hundred frantic ones.

Wire a burn-stop. Interactive has no cost ceiling, so build one. The statusline already shows session cost; a small watcher that reads it and kills the process when it crosses your dollar line is the circuit breaker Claude Code doesn't ship. Killing it costs you nothing: resume picks up where it stopped, and a PreCompact hook that force-commits every change first means nothing in flight is lost.

Give a goal a way to fail. A completion promise tells the agent when it's done; it also needs a failure promise. Spell out the abort conditions in the goal: bail on an unrecoverable error, or after the Kth identical failure. Ralph-style loops especially, the ones that run until "done", will happily run until broke if "can't finish" isn't also an exit.

06 · Tried and parked: codebase graphs {#parked}

A couple of tools promise to map your whole codebase into a graph so the agent navigates it better: graphify and codegraph. I tried both and parked them.

On a normal codebase, say under ~500k LOC, the gain was thin. Claude and Codex already lean on AST and LSP to find their way around, so the graph mostly re-tells them what they can already resolve. It pays off on genuinely uber-large monorepos, but even then I'd rather scope structurally and start at the app or folder level than carry one big graph. And keeping that graph current is its own headache: a real DevX and integrity problem, where a stale graph quietly lies to the agent. I didn't see real gains. It does look pretty, mind you.

Where they do earn their keep is cataloguing a codebase for reference or research, a one-off pre-index where some staleness is fine. As a live navigation layer for day-to-day agent work, I left it.

07 · So what {#so-what}

sketchnote: it all compounds - measure, compress, route, long tail, every turn, every day, tokens = RAM

Tokens are turning into RAM, a commodity nobody optimises once it's cheap, and the people making the models have little reason to bend that curve the right way. It suits the business for you to use more.

So it falls to you. Measure with the burndown, compress with rtk and headroom where the work is tool-heavy, route the volume to cheap models and keep judgement on the premium one, and work the long tail. None of it is glamorous. All of it compounds, on every turn, every session, every day.

If you want the wider backdrop on why the free tiers keep tightening, the free AI coding trilemma sets the scene.

Top comments (0)