What 13,000 coding-agent requests actually cost
Thirty-six days of one developer's Claude Code sessions, measured request by request: 4.2B prompt tokens, $3,035 at list price, and 90.5% of it input that had already been sent. Seven revisions later, the honest saving is 4.6% — and every revision came from measuring a proxy instead of the thing.
What 13,000 coding-agent requests actually cost
Everyone building on coding agents has the same intuition about where the money goes, and the intuition is wrong in a specific, expensive way. This post is what happened when we stopped reasoning about it and read the transcripts.
The corpus: one developer, one machine, 36 days. 121 transcripts — 47 developer sessions plus the 74 subagent runs they spawned — 12,902 API requests, 4.17 billion prompt tokens, $3,035.09 valued at Anthropic list price. Every figure below is reproducible by re-running the scripts against the same committed dataset.
Two warnings before any number, because both change what the numbers mean.
Nobody paid $3,035. The developer was on a $20/month subscription for the entire window. Every dollar here is a list-price valuation — what this exact workload would bill at API rates. It is the right number for a team on BYOK or API billing, and the wrong number to describe what happened to this account. The API-equivalent run rate is roughly $2.5k/month against $20 actually paid.
It also means this corpus was generated under zero marginal cost. Nothing in it was shaped by price; sessions ran to the full 1M context because there was no reason not to. Treat the context-growth figures as an upper bound. That is also precisely why it is useful: an engineering org on API billing gets exactly this — developers with no cost signal, because the bill lands on a budget they never see.
One developer is one developer. The cost structure is a property of how agentic coding works and should generalise. The magnitudes — model mix, session lengths, absolute totals — are this fleet's and should not be quoted as industry figures.
1. The workload is not a generator. It is a re-reader.
Start with the shape of the traffic, because everything else follows from it.
| Direction | Tokens | Share of input |
|---|---|---|
| Fresh input | 1,067,321 | 0.03% |
| Cache read | 4,096,550,551 | 98.33% |
| Cache write | 68,326,182 | 1.64% |
| Input total | 4,165,944,054 | |
| Output | 10,721,571 | — |
Input outweighs output 389 : 1. Per request that is 322,890 tokens in and 831 out.
Priced, that becomes the only chart that really matters:
90.5% of spend is input that was already sent before — re-read from cache, or written into it. Output is 9.2%. Fresh input, the only thing any prompt compressor can ever touch, is 0.2%.
Three product conclusions fall out immediately, and two of them kill work we had planned:
- Prompt compression is not "low value", it is arithmetically pointless. A perfect compressor that eliminated all fresh input would cut the bill by 0.2%. Any compressor that touches the cached prefix instead makes things dramatically worse, because it busts the cache. Remove it from the coding path entirely — not "off by default", not wired at all.
- Output-token optimisation is a rounding error. Shortening responses, capping
max_tokens, tiering for cheaper output — all of it chases 9.2% while 90.8% sits in context handling. - Prompt caching has already banked a 7.1× saving ($18,393 of avoided cost), and that is Anthropic's, not ours to sell. Anything we build starts after that.
2. Cost is context size × turns, and there is no cheap quartile
| Context / request | Requests | % of requests | Cost | % of cost | $/request |
|---|---|---|---|---|---|
| 0–100k | 2,835 | 22.1% | $205 | 6.7% | $0.072 |
| 100–250k | 3,206 | 25.0% | $488 | 16.1% | $0.152 |
| 250–500k | 3,640 | 28.4% | $926 | 30.5% | $0.255 |
| 500k–1M | 3,151 | 24.6% | $1,416 | 46.6% | $0.449 |
24.6% of requests carry more than 500k of context and account for 46.6% of all spend. Peak contexts reach 999,488 tokens — sessions run at the full 1M window, and every subsequent request re-reads all of it.
The distribution matters as much as the total. The p25 request already carries 111k tokens. There is no cheap quartile to optimise around, no long tail to trim. Context is uniformly enormous.
Sessions are the opposite shape. Mean cost per session is $64.58 against an $8.16 median, and the top 9 of 47 sessions (19%) account for 76.1% of all spend. A budget cap that never fires below $50 leaves 72% of sessions untouched and still sits in front of 89% of the money.
One instruction is sixteen API requests
12,902 requests produced 820 handbacks to the user. That is 15.7 API requests per thing the developer actually asked for, at $3.70 per handback.
This is why per-turn payload optimisations lose to context-size optimisations. The payload is re-sent sixteen times per instruction.
3. The work has no rhythm, and that rules out most alerting
Day over day, the median change in token volume is +0.1%, with an interquartile range of −58.9% to +104.6%. Volume routinely doubles or halves between consecutive days. Weeks are no steadier — the swing from week 1 to week 3 is 17.5×. Five of 37 calendar days are idle.
A 2× day is the median week's experience, not a signal. Every anomaly detector, capacity plan or budget alert built on a day-over-day or week-over-week delta will fire constantly and mean nothing. Budgets for this workload have to be cumulative — rolling spend against a period cap — never rate-of-change.
Tokens and commits are also only loosely coupled. 07-14 spent $396 for 5 commits; 07-16 spent $47 for 15; 07-09 produced 106 commits on 33M tokens. Across the window: 11.1M tokens per commit, of which 28k are the model's own output. For every token the model generated, 395 tokens of context were read back in. That ratio, not the price of any model, is the whole problem in one number.
4. Where the tokens actually sit
If context size is the cost function, the next question is what is in the context. The unit here is token-turns — tokens × turns resident — because content in the context is re-read on every subsequent request, which is exactly what cache-read billing is proportional to.
| Tool | Resident token-turns | Share |
|---|---|---|
Bash |
487,265,664 | 45.95% |
Read |
419,811,300 | 39.59% |
Edit |
46,134,368 | 4.35% |
chrome/computer |
38,729,141 | 3.65% |
ExitPlanMode |
18,816,234 | 1.77% |
Agent |
15,594,672 | 1.47% |
Write |
9,319,374 | 0.88% |
AskUserQuestion |
8,636,594 | 0.81% |
Bash is 51.6% of tool calls and averages only 915 bytes per result — it is high-frequency and
small. But by occupancy it is still the largest resident tool, ahead of Read, and it is the one
with no natural size bound. A single pytest -v or find dump enters the context and is
re-read on every subsequent turn for the rest of the session.
And here is the ceiling on all of it: tool results are only 24.2% of prompt-token volume. The other 75.8% is system prompt, tool definitions and message history — none of which a transcript can see. Every lever below reaches a quarter of the problem.
We got one look at that 75.8%, by running a live session through a capture proxy. The result was not what we predicted: in that session tool definitions were ~80% of the assembled prompt — 64,029 bytes of them — against 12% system prompt and 9–11% messages. 27 tools were defined; 1 was used. 95.4% of the definition bytes were never touched, and they render first, so they sit at the head of the prefix and are re-read every single turn. That is a large, zero-quality-cost lever nobody was looking at, and it only became visible when we stopped reading transcripts and started reading request bodies.
5. Four levers, measured — and two of them died
We named four tail-side hygiene levers: de-duplicate repeat reads, supersede stale results, age
out old content, truncate Bash output. Then we measured each one against the corpus instead of
arguing about it.
| Rank | Lever? | % of prompt tokens | $ | % of billed | Risk |
|---|---|---|---|---|---|
| 1 | age-out @100 turns | 17.19% | $304.66 | 9.53% | HIGH, unmeasured |
| 1c | age-out @400 turns | 7.45% | $122.57 | 3.84% | MEDIUM |
| 2 | Bash truncate @512 tok |
2.90% | $54.94 | 1.72% | LOW with head+tail |
| 3 | supersede | 0.67% | $9.50 | 0.30% | LOW |
| 4 | read de-dup (verified) | 0.03% | $0.33 | 0.01% | NONE — and worth nothing |
The lever that fell by 540×
Read de-duplication was supposed to be the safe, obvious, ship-first lever. The case for it was
a single statistic: 59% of file reads are re-reads of a file already read that session. That
count is correct. This corpus puts it at 57.1%.
The inference drawn from it does not follow.
Of 2,022 Read calls with a path |
||
|---|---|---|
| First read of that path | 868 | 42.9% |
| Repeat read of an already-read path | 1,154 | 57.1% |
| → same line range (identical tool input) | 28 | 2.4% of repeats |
| → different line range (pagination, adjacent slice) | 1,126 | 97.6% of repeats |
| → byte-identical to an earlier read | 2 | 0.2% of repeats |
A repeat read of a file is almost never a repeat read of the same bytes. 97.6% of the "re-reads" justifying this lever are the agent walking a different part of a file it already opened — new content, carrying new information, impossible to collapse to a pointer.
The most striking number in the original analysis was "73 reads of one file in one session". It reproduces exactly, and it dissolves on inspection: those were 73 different offsets into one large file. No identical range is re-read more than 4 times in any session in the corpus.
What broke the earlier measurement was mundane. Our dataset fingerprinted a tool call by hashing
file_path alone. Session 4b5ae4dd reads proxy.py at offset 1 limit 470, then offset 470 limit 450, then offset 920 limit 200 — three disjoint slices, one walk down a 1,119-line file.
Scored as three reads of the same target, that single walk contributed $2.89 of "de-duplication
value".
Key on the whole tool input, and require the bytes to already be resident, and the lever goes from $36.88 to $0.33. Its measured value across four passes: $178.10 → $43.27 → $37.58 → $0.33.
Recommendation: drop it, don't defer it. At $0.33 over 36 days it does not pay for the per-path staleness tracking it requires, and the tracking is the part that has to be correct.
The lever we had rated too risky to ship
Bash truncation was rated HIGH risk on a reasonable-sounding objection: "Bash output is the
signal — stack traces, test failures, grep hits. A 2 KB cut removes most of a Python traceback."
True about tracebacks. Irrelevant to where the money is. Splitting the recovered value by whether
the output's head carries a diagnostic (Traceback, FAILED, assert, panic:, an error line):
| Cap | Dumps & listings | Diagnostics |
|---|---|---|
| 1024 tok | $19.90 (90%) | $2.15 (10%) |
| 512 tok | $49.30 (90%) | $5.63 (10%) |
| 256 tok | $87.24 (89%) | $10.29 (11%) |
Nine tenths of the value is in sed/cat dumps, grep roll-ups, git diffs and black/ruff
summaries — output the agent skimmed once and never consulted again. The risk is concentrated in
the other tenth, and it is separable two ways: keep head and tail so a traceback's exception line
and its final frame both survive, or exempt anything matching the diagnostic predicate outright and
take 90% of the value at close to zero risk.
One example makes it concrete. Session e170b28f, turn 4 of 1,317:
sed -n '125,230p' docs/requirements.md → 16,756 chars ≈ 4,189 tokens
Issued as the fourth request of the session. It stayed resident for all 1,313 remaining turns —
5,500,157 token-turns, $2.75. A 512-token cap recovers $2.41 of that one call, which is
7× the entire verified Read de-dup lever across 36 days.
The published ordering was exactly inverted. Bash truncation is first by 166×; Read
de-duplication is last by two orders of magnitude.
The lever that is now the whole thesis
Age-out — simply dropping tool results older than N turns — carries 71% of the balanced tier's value and 88% of the aggressive tier's. With de-dup and supersede corrected downward, the entire token-removal case rests on one parameter whose quality cost nobody has measured.
The cleanest example in the corpus: session e170b28f, turn 147. A whole-file Read of
simulator_studio.py returns 10,003 tokens. The string simulator_studio never appears again
after turn 198 — not in a tool call, not in a result, not in assistant text. The session then ran
for another 1,118 turns.
| Read at turn | 147 |
| Last mention of the file, anywhere | 198 |
| Idle residency | 1,118 turns |
| Dead token-turns | 11,183,354 |
| Cost of that dead residency | $5.59 |
| Full residency cost from birth | $5.85 |
96% of that read's lifetime cost accrued after the agent had provably stopped referring to the
file. One read, one session, $5.59 — against $0.33 for the entire read de-dup lever across the
whole corpus.
The gap between a 400-turn window ($122.57) and a 100-turn window ($304.66) is $182 — larger than every other lever combined. And nothing in this corpus tells you what a dropped result costs in task success, because no session was ever run with content removed. That single experiment gates 71–88% of all remaining value.
6. The other axis: where the clock goes
Cost is one measure of efficiency. Elapsed time is the one developers actually feel, and it decomposes very differently. Measured over 162 sessions and 1,035.9 hours of wall clock:
| Phase | Hours | Share |
|---|---|---|
| Idle > 5 min | 925.6 | 89.3% |
| Model thinking after a tool | 37.0 | 3.6% |
| Tool execution + approval | 36.7 | 3.5% |
| Human composing a prompt | 17.6 | 1.7% |
| Model generating | 12.5 | 1.2% |
| Model first response | 5.9 | 0.6% |
| Active (excluding idle) | 110.3 | 10.7% |
Median session span is 10.7 minutes. Nearly nine tenths of elapsed session time is idle, so any optimisation aimed at the active loop is competing for a tenth of the clock.
Splitting that idle time by whether the agent was holding a pending tool call when it went quiet is where the finding is:
| Idle > 5 min | Hours | Occurrences | Mean |
|---|---|---|---|
| Parked on a pending tool call | 242.5 | 227 | 64 min |
| Idle for any other reason | 683.1 | 310 | — |
227 times, an agent sat holding a tool call for an average of 64 minutes. That is 26.2% of all
idle time. What it was parked on: Bash 190, AskUserQuestion 13, ExitPlanMode 9, Edit 5,
Agent 3, Read 2. Bash is 84% of parked events. This is the "came back an hour later to find
the agent waiting" failure mode, and it dwarfs every active-loop figure.
It is an upper bound, not a saving — some of those stretches are a human who left for unrelated reasons while a call happened to be pending, and a transcript cannot tell that apart from a human who would have returned to an agent that had made progress.
The framing that is two orders of magnitude off
Under this configuration, per-prompt approval latency is already gone: median gap on a low-risk
call is 0.0s, and low-risk gap time totals 1.9h against 1,035.9h — 0.2% of wall clock.
acceptEdits and auto mode already collapsed it.
So "save keystroke latency by clicking yes faster" is worth ~0.2% here. "Stop the agent parking for an hour" is worth up to 23%. Same feature, two framings, two orders of magnitude apart.
7. What developers actually refuse
A declined tool call is recorded verbatim in the transcript — is_error: true carrying the string
"The tool use was rejected". That makes 14,786 calls a labelled dataset, so risk does not have
to be argued from first principles.
93 rejections in 14,786 calls — 0.63%.
Two readings matter.
The highest-rate entries are not risk refusals. ExitPlanMode at 29% and AskUserQuestion at
18% are tools whose entire purpose is to hand control back — "rejecting" one means picking a
different option or continuing to plan. They have to be excluded from any risk accounting or they
poison it.
Browser and egress tools are the genuine elevated-risk cluster — navigate 7.50%,
tabs_context 10.81%, WebFetch 2.83%, all an order of magnitude above base. These are the calls
that reach outside the machine, and a human declines them far more often. Meanwhile Read, across
2,015 calls, was never once declined.
Scored against that ground truth, a deterministic five-tier classifier that fails closed puts
3,725 calls (25.2%) in a SAFE tier. Its raw rejection rate looks bad at 0.56% — until you notice
that 18 of those 21 rejections are the control-flow tools above. Excluding them: 3 rejections in
3,640 calls — 0.082%, and all three are read-only commands (git show, cat) where the refusal
meant "wrong file, don't bother", not a safety judgement.
A deterministic SAFE tier covering a quarter of all tool calls has an effective false-approve rate of 0.08% against real human decisions. That is a measurement, not an argument.
The engineering work is the other bucket. 44.5% of calls land in UNPARSED — meaning "ask",
not "allow" — and nearly all of it is one problem: shell commands are not classifiable by their
head. cat x > y writes. echo $(rm -rf .) deletes. 4,410 calls contain a redirect or command
substitution; 2,127 are compound commands with a non-safe stage. Draining that requires real
per-segment shell parsing, and it is where the effort goes if this is ever built.
8. Seven revisions, every one downward
This is the part worth internalising, more than any individual figure.
| Date | Claim | What changed it |
|---|---|---|
| initial | 50–90% input-cost cut | asserted from how prompt caching works |
| 2026-07-27 | 25–40% | Claude Code already caches well; 6.7× is banked before we arrive |
| 2026-07-27 | 18.4% | levers backtested instead of estimated |
| 2026-07-27 | 16.6% | 55.2% of repeat reads follow an Edit — naive de-dup served stale content |
| 2026-07-27 | ~5% usage headroom | a cost saving is not a token saving |
| 2026-07-28 | 6.3% expected / 3.4% floor | corpus re-extracted per API request |
| 2026-07-29 | ~4.6% expected / 1.7% floor | levers re-measured off raw transcripts |
Six of the seven revisions were corrections to our own analysis, not new information about the world. And the pattern across all of them is identical: we measured a proxy for the thing instead of the thing.
- Transcript records as API requests. Claude Code writes one transcript record per content
block and repeats the whole
usageobject on each. A response containing[thinking, text, tool_use]is three records carrying the same prompt-token counts, three times. Per-record summing overstated prompt volume 1.95× and output 2.34×. Every absolute halved — $6,134 became $3,035. - File paths as tool targets. Covered above: a paginated walk became 73 duplicate reads.
- base64 length as image tokens. A screenshot arrives as a base64 blob. Charging
len(base64) / 4bills a 1451×840 JPEG at 27,878 tokens; Anthropic bills it at roughlyw·h/750≈ 1,625. A 17× overstatement — and it moved the entire denominator: tool results are 24.2% of prompt volume, not the 40.1% we had published.
The 2026-07-28 revision is the only one that moved a conclusion rather than a magnitude, and it moved two. Duplicate records sit milliseconds apart carrying identical context — which is exactly the signature our backtest classified as "prefix mutation". The bug was manufacturing mutation events out of its own duplicates. With them removed:
- Prefix mutation fell from 61.4% of cache loss ($828.72) to 13.4% ($87.11) — from the largest lever in the analysis to the smallest of three.
- TTL expiry rose from 21.8% to 47.6% ($207.58) — and became the largest single lever we have, at zero UX risk, because refreshing a cache entry before it expires changes nothing the model sees.
The next figure to distrust on the same grounds is the one no lever reaches: the remaining 75.8% of context volume, which is measured by subtraction rather than observed.
9. Where this leaves it
| Tier | What it contains | Cost | Tokens |
|---|---|---|---|
| FLOOR | Bash truncation + supersede |
1.7% | 2.93% |
| EXPECTED | + prefix-mutation fix | 4.6% | 2.93% |
| + TTL keep-alive | accounting only, removes zero tokens | ~11.4% | 2.93% |
| BALANCED | + age-out @400 turns | 13.4% | 8.1% |
| AGGRESSIVE | + age-out @100 + 256-tok cap | 19.8% | 18.8% |
Two columns that change the conclusion. %cost and %tokens are different units and do not
track each other. TTL keep-alive and mutation elimination are pure price conversion — the tokens
that fell out of cache still get sent, they simply stop being billed as writes (2.0× input) and
become reads (0.1×). Same volume, ~20× cheaper.
So the levers rank completely differently depending on who you are:
| Team on API billing | Developer under a token cap | |
|---|---|---|
| TTL keep-alive | largest lever | worthless — conversion, not reduction |
| Mutation fix | modest | worthless |
| Tail-side hygiene | small | the only thing that helps |
And the ordering is uncomfortable in a way we did not expect going in: the only zero-risk lever is worth more than all validated tail-side hygiene combined, and the levers that actually remove tokens are small while the large ones only move price.
The honest recommendation today is balanced-minus: ship Bash truncation at 512 tokens with
head-and-tail retention and a diagnostic exemption ($55, low risk), ship supersede alongside it
because it is nearly free ($9), and hold age-out until someone replays a session with aged
context and measures task success.
The five things blocking everything else
| # | Blocker | What it gates |
|---|---|---|
| 1 | Message history and tool definitions not measurable from a transcript | the other 75.8% of context volume |
| 2 | Cause of prefix mutation unknown | $87 / 2.7% of cost |
| 3 | Tiering quality cost unmeasured | a 5× price spread on 67.3% of spend |
| 4 | Aging risk unquantified | 71–88% of all token removal |
| 5 | Single-client corpus | whether any of this generalises |
Blockers 1 and 2 are the same experiment: one live session through a capture proxy yields both the mutating field and the assembled prompt. That is still the highest-leverage hour of work available — it is what produced the tool-definitions finding in §4, and it has already paid for itself once.
What we would tell anyone measuring their own agents
- Count API requests, not transcript records. If you are summing
usageper record, you are roughly doubling everything. Join onmessage.id. - Key tool calls on the whole input, not on the target. Otherwise pagination looks like duplication and you will build a lever that isn't there.
- Price images as images.
w·h/750, notlen(base64)/4. It is a 17× error and it moves your denominator. - Verify redundancy against the bytes. "Same argument" is not "same content"; requiring byte-identity or verbatim containment took our best lever from $37.58 to $0.33.
- Measure the clock separately from the money. They decompose completely differently, and the biggest time finding — 242 hours of agents parked on approval — is invisible in any cost metric.
- Instrument the assembled prompt, not just the transcript. Three quarters of the volume is only visible there, and in our one capture it was dominated by tool definitions nobody had thought to look at.
Every one of those is a correction we published against ourselves. The direction was always the same: down, and toward measuring the thing rather than a proxy for it.
The instrument that produced all of this now runs as a local sidecar on the developer's own machine — nothing uploaded, transcripts read but never written. We wrote about what it shows in Announcing the ACE sidecar.