I measured my token spend, then got the conclusion wrong
TL;DR
- I measured 14 days of my own AI coding agent sessions. 97.3% of input tokens were the same context re-sent turn after turn, and 33 sessions out of 682 accounted for 86.8% of the modelled cost.
- I concluded the fix was to end sessions sooner. Clean story, mostly wrong: the two biggest sessions held 54% of all turns, so they cost the most because they held half the work, not because their context was pathological.
- Ending sessions sooner is worth roughly 20-25% per turn, not 86.8%, and starting a fresh session has a cost of its own.
- Output is 0.3% of tokens but ~12% of cost, because it's priced ~50× a cache read. I had called it a rounding error.
- My mistakes: I treated where spend was concentrated as waste and as what I could save, then recommended the fix before working out when it pays off.
Tokens, context and cache in two minutes
A turn is one round trip to the model, effectively one API call: the agent sends what the model needs to see, the model sends back a reply. Everything is counted in tokens, the chunks of text a model reads and writes, and priced per token.
What the agent sends each turn is the context: the fixed instructions it starts with (the preamble), plus everything the conversation has accumulated since, replies, files it read and tool results included. The whole context is sent again every turn, and paid for again, and it grows as the session goes on.
flowchart LR
C["Context so far: preamble, messages, files, tool results"] -->|"sent again every turn"| M["Model"]
M -->|"output tokens"| R["Reply"]
R -->|"added to the context"| C
A session is one conversation with the agent. Starting a fresh session begins again from the preamble, with a smaller context that has to be written to the cache again.
Most of each turn's context is the same as last turn's, and the provider serves it from a cache. Input tokens come in three kinds:
- Cache reads: context re-sent from the cache.
- Cache writes: content stored into the cache, as when a fresh session starts.
- Fresh input: input that is neither read from nor written to the cache.
The output is what the model writes back. Prices are multiples of the base input price. The ratios I use, plug in your own:
| Token kind | Price | Compared with a cache read |
|---|---|---|
| Cache read | 0.1× base input | |
| Cache write | 1.25× base input | ~12.5× |
| Output | 5× base input | ~50× |
So output is priced ~50× a cache read, which turns out to matter.
What 14 days of my transcripts showed
Billing basis, since it changes what "cost" means: I'm on a subscription, so none of this produces an invoice. Every cost figure here is modelled at API list price, which I'd argue is the right lens anyway, it's what the same work would cost on the API.
14-day window, my own local transcripts (the logs the agent keeps of each session), 682 sessions:
| Measure | Value |
|---|---|
| Input tokens that are cache reads | 97.3% |
| Average context re-paid per turn | 241,517 tokens |
| Sessions with peak context above 300k | 33 sessions = 86.8% of cost |
| The two largest sessions (19,538 and 9,533 turns) | 60% of the total |
| Output tokens, whole window | 37.5M |
| Cache reads, whole window | 13.1B |
97.3% of input is not new content. It's the same context, re-sent, turn after turn. (Peak context is the largest context a session reached.)
The conclusion I drew: end sessions sooner
Output is 0.29% of all tokens. Cache reads are 97.3%. So the bill is context re-payment, a handful of sessions dominate it, and the lever is obvious: end sessions sooner, don't let context accumulate, and any tool promising to compress output is chasing a rounding error. The fix, I concluded, was session hygiene: end the session when the topic changes.
Clean story. Mostly wrong.
The measurement is still worth publishing. So is the mistake, because it's the kind that survives peer review by sounding obviously right.
The division that breaks it
Then someone did a division I hadn't done, and the conclusion fell over.
The two big sessions hold half the turns
Total turns implied by my own figures: 13.1B cache reads ÷ 241,517 average context = ~54,240 turns.
The two marathon sessions are 19,538 + 9,533 = 29,071 turns. That's 53.6% of every turn in the window.
| Two marathon sessions | Everything else | |
|---|---|---|
| Turns | 29,071 | 25,169 |
| Cache reads | 7.86B | 5.24B |
| Implied average context per turn | 7.86B ÷ 29,071 = ~270k | 5.24B ÷ 25,169 = ~208k |
They account for 60% of cost while containing 54% of the work. Which means they are barely more expensive per turn than everything else.
What ending a session actually changes
So the honest version:
- Those sessions don't dominate because their context is pathological. They dominate because they contain half the work.
- "End the session" moves per-turn context from ~270k to ~208k. That's roughly 20-25% per turn, not 86.8%.
- The turns don't disappear when you split the session. The work still has to be done. You re-pay a smaller context each time, that's all.
20-25% is still real, and I'd still take it. But it's an ordinary optimisation, not the dramatic finding I thought I had. The 86.8% figure describes where spend is concentrated, and I read it as how much is recoverable. Those are not the same quantity and nothing in the data connected them.
Peak context is not average context
I also wrote that those sessions were "pinned near the 1M ceiling", the most context a session can hold. A ~270k implied average says otherwise. Peak context and average context are different numbers and I conflated them.
The fix isn't free either
Starting a fresh session re-writes the preamble and re-establishes context at the cache write tier, ~12.5× the price of a cache read.
That other 2.7% of input tokens I waved away as noise is cache writes plus fresh input. On 13.46B input tokens, 2.7% is ~363M tokens at 12.5× the read rate, which lands somewhere around a quarter to a third of the cache-read cost. Not noise.
So "end the session" has a break-even: below some accumulated context, splitting costs more in re-established cache than it saves in re-read context. I haven't computed mine. It's computable from data already on my disk, and I should have done it before recommending the behaviour to anyone.
Concentration is not waste
The bigger hole, and the one I'd want someone to point at in my own review.
86.8% of spend sitting in 4.8% of sessions is only a problem if those sessions weren't producing 86.8% of the value. They contained 54% of all turns. That's where over half the work happened.
I never separated "concentrated" from "wasteful". The entire prescription assumes they're the same thing. They might be. Long sessions do drift, carry irrelevant context, and re-read files nobody needs. But that's an argument I'd have to make with evidence about what those sessions produced, and I made it with evidence about what they cost.
Three levers, sized
| Lever | The numbers | Verdict |
|---|---|---|
| Ending sessions sooner | per-turn context ~270k -> ~208k | roughly 20-25% less context per turn, minus new cache writes; break-even not computed |
| Compressing output | 0.3% of tokens, ~12% of cost | aimed at ~10-12% of the bill, not a rounding error |
| Trimming the static preamble | 12,953 -> 10,075 tokens, against a 241k average context | noise |
Output compression is aimed at ~10-12% of the bill, not a rounding error. I said both "output is a rounding error" and "output compression targets ~10% of the bill" eight lines apart, which reads as a contradiction. Both are true in different units: output is 0.3% of tokens and ~12% of cost, because it's priced ~50× a cache read. Mark the unit or you'll confuse yourself, as I did.
The intuitive levers really are small. I trimmed my static preamble from 12,953 to 10,075 tokens. Against a 241k average context that's noise. I kept the change because shorter instructions are better instructions, not for the money.
Measure it yourself
Measure before you install anything. That advice generalises even though my conclusion didn't. What doesn't generalise is my distribution: 682 sessions in 14 days with two containing half the turns is one person's working style, and a fairly extreme one. Someone running many short scripted invocations in CI has the inverse shape, low per-turn context and high session count, and for them output compression matters considerably more.
The provider-reported usage is already on your disk. Claude Code writes it to ~/.claude/projects/**/*.jsonl.
Things I got wrong the first time, so you don't
- It's one entry per message, not per turn: user messages, assistant messages, tool results. Count entries as turns and every derived number is off.
- Exclude sidechain entries or you double-count subagent usage (helper agents a session starts) against the parent session.
- Define "turn". Given the arithmetic above, a turn is effectively one API call. That's probably not what you assumed.
- The four fields you want, on assistant messages:
input_tokens,output_tokens,cache_read_input_tokens,cache_creation_input_tokens(the cache writes).
The steps
- Glob the files.
- Filter out sidechain entries.
- Sum the four fields per assistant message.
- Multiply by your price table.
- Group by
sessionId. - Sort.
Twenty lines of Python.
Three questions for the result
Two questions to answer, and one to answer after them:
- Are cache reads dominating your input? If yes, your bill is context re-payment.
- Do a handful of sessions dominate those reads? If yes, look closer.
- Do those sessions also contain a proportional share of your turns? If yes, you've found where the work is, not where the waste is. That's the question I skipped.
Before you install a "token saver" tool
Same rules as any dependency, they just get skipped more often because the pitch is about saving you money.
- What it costs to run. If a skill (an add-on for the agent) injects a thousand-odd input tokens per turn to compress your output, at tens of thousands of turns it's net zero, and its own docs will usually tell you so.
- The licence. A lot of these are source-available rather than OSI open source: you can read the code, but the licence isn't an OSI-approved open-source one. That's a decision, not a detail.
- What it sits in front of. Be careful with anything that terminates your provider OAuth traffic locally (it puts itself in the middle of your signed-in connection to the model provider), or does lossy compression (elided function bodies, truncated files) in front of a code-editing agent. Feeding an agent a lossy view of the code to save tokens is a trade I'm not making, and that's a correctness question before it's a cost one.
- Its self-reported savings. Either measure the saving independently (compare transcript sizes with and without, over a fixed task set) or don't quote a number.
I previously cited a "60.8% saved" figure from a CLI proxy I run. That's the tool grading its own homework, and my own notes record that the same proxy mangles certain command flags and reports false results. Citing it approvingly in an article about verifying claims was exactly the inconsistency a hostile reader should catch.
If you want to make a specific criticism of a specific tool, name it and link the lines that support the claim. Describing an identifiable project without naming it is the worst of both worlds: obvious to anyone who cares, unfalsifiable for everyone else, and the maintainer can't answer.
What I learned
- Concentrated is not the same as wasteful, or as recoverable. Before cutting the sessions that cost the most, check whether they also hold a proportional share of the turns. My two largest held 54% of them for 60% of the cost: the 86.8% showed where spend was concentrated, not how much I could save.
- Price the fix, not just the problem. A fresh session re-pays its context at the cache write rate, so ending sessions has a break-even I should have computed before recommending it. Cost isn't the only axis either: bigger contexts are also slower, which my pricing of the split never counted (see the November 2026 update).
- Mark the unit. Output is 0.3% of tokens and ~12% of cost. Peak context and average context are different numbers. I confused myself with the first pair and conflated the second.
- The intuitive levers really are small; the model tier is a lever of a different size. Trimming my preamble was noise. The model comparison comes with large caveats, and I can't rank it against the session split (see the November 2026 update).
- Measure before you install anything, and don't take a tool's word for its own savings. My distribution is one person's, and a fairly extreme one.
Update, November 2026
Two measurements that change this piece. I had the first before publishing and left it out.
Bigger contexts are also slower. In August I measured latency on a different window, about 20k assistant turns bucketed by context size. Median wait per turn rises with context:
| Context size | Median wait per turn |
|---|---|
| 0-100k tokens | 4.9 s |
| 100-200k | 14.0 s |
| 300-400k | 19.8 s |
| 800-900k | 30.1 s |
Re-reading a cached context is cheap, not instant. "The fix isn't free either" priced the split in cache tiers only and never counted the wall-clock that goes with a larger context. What the move from ~270k to ~208k is worth, I can't say: both sit in the 200-300k bucket, which that measurement doesn't report. That window's median context was 380k, so none of these medians plug into the cost figures above.
The model is a lever I left out. I wrote "The intuitive levers really are small", about trimming my preamble. That still holds. What I didn't consider is that the model tier is a lever of a different size. In September the same diff was reviewed in CI by Sonnet 4.6, then by Opus 5:
| Reviewer model | Cost | Turns |
|---|---|---|
| Sonnet 4.6 | $0.27 | 10 |
| Opus 5 | $1.31 | 24 |
That's 4.8×. On that repository a typical (median) Opus review cost about 2.2× a typical Sonnet 4.6 one; my notes don't record how many reviews that covers. That's per CI run, a different workload from the interactive sessions above, so I can't rank it against the 20-25%, which was a cut in per-turn context, not in the total bill.
The caveats are not small:
- Part of the 4.8× was my prompt, not the model. The review instructions told it to check something on disk that never exists on the CI runner. The smaller model skipped the step in silence; the larger one tried to make it true, failed to install dependencies, then ran lint and tests nobody asked for.
- The 2.2× may carry some of the same overshoot; my notes don't say.
- The cheaper review wasn't the same review. The smaller model passed that diff, the larger one blocked it on a correct finding. The new helper matched on bare strings where a closed type (a type that lists every allowed value) already existed, so a new value would make it silently return nothing, with no failing test.