← /blog
· ACE Engineering#coding-agents #claude-code #cost #context #cache #scorecard #measurement #research #managed-api-stack

Empirical Analysis of 13,000+ AI Coding Agent Sessions: Cost and Efficiency Lessons

Empirical breakdown of 12,902 requests across 4.17B tokens and $3,035 spend: evaluating prompt caching efficiency, context duplication (90.5%), and transcript telemetry.

The corpus: 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.


1. Traffic shape: input dominates

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 — 322,890 tokens in and 831 out per request.

Stacked bar showing the $3,035 bill split into cache reads 67.3%, cache writes 23.2%, output tokens 9.2% and fresh input 0.2%

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 is 0.2%.

Three consequences follow arithmetically:

  • Prompt compression has a 0.2% ceiling. A compressor that eliminated all fresh input cuts the bill by 0.2%; one that touches the cached prefix busts the cache and raises cost.
  • Output-token optimisation addresses 9.2%. Shorter responses, max_tokens caps and cheaper output tiers all divide that share; 90.8% sits in context handling.
  • Prompt caching has already banked a 7.1× saving — $18,393 of avoided cost — before any additional lever applies.

2. Cost is context size × turns

Grouped horizontal bars: requests and cost share by context bucket. The 500k–1M bucket is 24.6% of requests and 46.6% of cost, at $0.449 per request

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 is uniformly large: the p25 request already carries 111k tokens.

Sessions are distributed differently. Mean cost per session is 64.58againstan64.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 sits in front of 89% of the money.

12,902 requests produced 820 handbacks to the user: 15.7 API requests per instruction, at $3.70 per handback. A per-turn payload optimisation is therefore divided by ~16 relative to a context-size optimisation, because the payload is re-sent on each of those requests.

3. Volume has no day-to-day regularity

Column chart of daily token volume across 37 days, from idle days to a 412M peak

Day over day, the median change in token volume is +0.1%, with an interquartile range of −58.9% to +104.6%. The swing from week 1 to week 3 is 17.5×. Five of 37 calendar days are idle.

A 2× day sits inside the interquartile range, so any alert keyed on a day-over-day or week-over-week delta fires within normal variation. Cumulative measures — rolling spend against a period cap — do not have this property.

Tokens and commits are loosely coupled. 07-14 spent 396for5commits;0716spent396 for 5 commits; 07-16 spent47 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 generated, 395 tokens of context were read back in.


4. Where the tokens sit

The unit is token-turns — tokens × turns resident — because content in the context is re-read on every subsequent request, which is what cache-read billing is proportional to.

Ranked horizontal bars of resident token-turns by tool: Bash 45.95%, Read 39.59%, Edit 4.35%, then a tail under 4%

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 915 bytes per result — high-frequency and small — yet it is the largest resident tool, ahead of Read, and the only one with no size bound on a single result: one verbose test run or directory listing enters the context and is re-read on every subsequent turn.

Tool results are 24.2% of prompt-token volume. The other 75.8% is system prompt, tool definitions and message history, none of which a transcript records. Every lever below reaches a quarter of the volume.

One live session was captured at the request-body level to measure that 75.8%. In that session tool definitions were ~80% of the assembled prompt — 64,029 bytes — against 12% system prompt and 9–11% messages. 27 tools were defined and 1 was used: 95.4% of the definition bytes were never touched. Definitions render first, so they sit at the head of the prefix and are re-read every turn. This is measurable only from request bodies, not from transcripts.


5. Four levers, measured

Four tail-side hygiene levers were specified — de-duplicate repeat reads, supersede stale results, age out old content, truncate Bash output — then backtested individually against the corpus.

Ranked bars of lever value with risk labels: age-out at 100 turns <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>304.66</mn><mi>h</mi><mi>i</mi><mi>g</mi><mi>h</mi><mi>r</mi><mi>i</mi><mi>s</mi><mi>k</mi><mo separator="true">,</mo><mi>a</mi><mi>g</mi><mi>e</mi><mo>−</mo><mi>o</mi><mi>u</mi><mi>t</mi><mi>a</mi><mi>t</mi><mn>400</mn><mi>t</mi><mi>u</mi><mi>r</mi><mi>n</mi><mi>s</mi></mrow><annotation encoding="application/x-tex">304.66 high risk, age-out at 400 turns</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord">304.66</span><span class="mord mathnormal">hi</span><span class="mord mathnormal" style="margin-right:0.0359em;">g</span><span class="mord mathnormal">h</span><span class="mord mathnormal" style="margin-right:0.0278em;">r</span><span class="mord mathnormal">i</span><span class="mord mathnormal">s</span><span class="mord mathnormal" style="margin-right:0.0315em;">k</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">a</span><span class="mord mathnormal" style="margin-right:0.0359em;">g</span><span class="mord mathnormal">e</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:0.6444em;"></span><span class="mord mathnormal">o</span><span class="mord mathnormal">u</span><span class="mord mathnormal">t</span><span class="mord mathnormal">a</span><span class="mord mathnormal">t</span><span class="mord">400</span><span class="mord mathnormal">t</span><span class="mord mathnormal">u</span><span class="mord mathnormal" style="margin-right:0.0278em;">r</span><span class="mord mathnormal">n</span><span class="mord mathnormal">s</span></span></span></span>122.57 medium, Bash truncate <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>54.94</mn><mi>l</mi><mi>o</mi><mi>w</mi><mo separator="true">,</mo><mi>s</mi><mi>u</mi><mi>p</mi><mi>e</mi><mi>r</mi><mi>s</mi><mi>e</mi><mi>d</mi><mi>e</mi></mrow><annotation encoding="application/x-tex">54.94 low, supersede</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord">54.94</span><span class="mord mathnormal" style="margin-right:0.0197em;">l</span><span class="mord mathnormal">o</span><span class="mord mathnormal" style="margin-right:0.0269em;">w</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">s</span><span class="mord mathnormal">u</span><span class="mord mathnormal">p</span><span class="mord mathnormal" style="margin-right:0.0278em;">er</span><span class="mord mathnormal">se</span><span class="mord mathnormal">d</span><span class="mord mathnormal">e</span></span></span></span>9.50 low, read de-dup $0.33 none

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

Read de-duplication: measured at $0.33

The lever was specified on one statistic: 59% of file reads are re-reads of a file already read that session. That count reproduces at 57.1% here. The redundancy inference drawn from it does not hold.

Stacked bar: of 1,154 repeat reads, 97.6% request a different line range, 2.4% the same range, 0.2% are byte-identical

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 rarely a repeat read of the same bytes. 97.6% of the re-reads are the agent requesting a different part of a file it had already opened — new content, which cannot be collapsed to a pointer. The largest figure in the original analysis, "73 reads of one file in one session", reproduces exactly and resolves to 73 different offsets into one large file. No identical range is re-read more than 4 times in any session in the corpus.

The cause was the fingerprint: a tool call was hashed on its file path alone. One session read a 1,119-line file at offset 1 limit 470, then offset 470 limit 450, then offset 920 limit 200 — three disjoint slices of a single walk. Scored as three reads of the same target, that walk contributed $2.89 of "de-duplication value".

Keying on the whole tool input and requiring the bytes to already be resident moves the lever from 36.88to36.88 to0.33. Measured across four passes: 178.10178.10 →43.27 → 37.5837.58 →0.33. At $0.33 over 36 days it does not cover the per-path staleness tracking it requires.

Bash truncation: the risk objection, tested

The lever was rated HIGH risk on the objection that Bash output is the signal — stack traces, test failures, grep hits — and a 2 KB cut removes most of a Python traceback.

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 file dumps, grep roll-ups, diffs and linter summaries. The risk sits in the other tenth and is separable two ways: retain head and tail, so a traceback's exception line and final frame both survive; or exempt anything matching the diagnostic predicate, which retains 90% of the value.

One call from turn 4 of a 1,317-turn session illustrates the residency effect: a command printing a section of a document returned 16,756 chars ≈ 4,189 tokens and stayed resident for all 1,313 remaining turns — 5,500,157 token-turns, $2.75. A 512-token cap recovers $2.41 from that one call, 7× the entire verified Read de-dup lever across 36 days.

The published ordering was inverted. Bash truncation is first by 166×; Read de-duplication is last by two orders of magnitude.

Age-out carries most of the remaining value

Age-out — 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 token-removal case rests on one parameter whose quality cost is unmeasured.

One measured instance: at turn 147 of a 1,317-turn session a whole-file Read returned 10,003 tokens. The file's name does not appear anywhere in the session after turn 198 — not in a tool call, a result, or assistant text — and the session 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 stopped referring to the file5.59fromonereadinonesession,against5.59 from one read in one session, against0.33 for the entire read de-dup lever across the corpus.

The gap between a 400-turn window (122.57)anda100turnwindow(122.57) and a 100-turn window (304.66) is $182 — larger than every other lever combined. No session in this corpus was run with content removed, so the corpus contains no measurement of what a dropped result costs in task success. That experiment gates 71–88% of the remaining value.


6. The other axis: elapsed time

Measured over 162 sessions and 1,035.9 hours of wall clock:

Ranked bars of session time: idle over 5 minutes 89.3%, model thinking 3.6%, tool execution and approval 3.5%, human composing 1.7%, model generating 1.2%, first response 0.6%

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. 89.3% of elapsed session time is idle, so optimisations aimed at the active loop address 10.7% of the clock.

Splitting idle time by whether the agent held a pending tool call when it went quiet:

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 held a tool call for an average of 64 minutes, totalling 26.2% of all idle time. The pending call was Bash 190 times, AskUserQuestion 13, ExitPlanMode 9, Edit 5, Agent 3, Read 2 — Bash is 84% of parked events. This is an upper bound, not a saving: a transcript cannot distinguish a human who left for unrelated reasons while a call happened to be pending from one who would have returned to an agent that had made progress.

Under this configuration, per-prompt approval latency is already near zero: 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. Reducing keystroke latency addresses that 0.2%; eliminating hour-long parking addresses up to 23%.

7. Declined tool calls

A declined tool call is recorded verbatim — is_error: true carrying the string "The tool use was rejected" — which makes 14,786 calls a labelled dataset.

93 rejections in 14,786 calls — 0.63%.

Rejection rate by tool against a 0.63% base rate: tabs_context 10.81%, navigate 7.50%, WebFetch 2.83%, chrome/computer 1.44%, Bash 0.70%, Write 0.46%, Edit 0.03%, Read 0.00%

The highest-rate entries are not risk refusals. ExitPlanMode at 29% and AskUserQuestion at 18% are tools whose function is to hand control back; a rejection there means picking a different option or continuing to plan. They have to be excluded from risk accounting.

Browser and egress tools form the elevated clustertabs_context 10.81%, navigate 7.50%, WebFetch 2.83% — an order of magnitude above base rate. Read, across 2,015 calls, was never declined.

Scored against that ground truth, a deterministic five-tier classifier that fails closed places 3,725 calls (25.2%) in a SAFE tier. Its raw rejection rate is 0.56%, of which 18 of 21 rejections are the control-flow tools above. Excluding those: 3 rejections in 3,640 calls — 0.082%, all three read-only commands where the refusal indicated the wrong file rather than a safety judgement. A deterministic SAFE tier covering a quarter of all tool calls therefore has an effective false-approve rate of 0.08% against real human decisions.

44.5% of calls land in UNPARSED — "ask" rather than "allow" — because 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 and 2,127 are compound commands with a non-safe stage. Draining that bucket requires per-segment shell parsing.


8. Seven revisions, every one downward

Column chart of the expected saving as claimed on each date, falling from ~70% to 4.6% across six revisions

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 the analysis rather than new information about the workload, and each has the same form: a proxy was measured instead of the quantity itself.

  • Transcript records counted as API requests. Claude Code writes one transcript record per content block and repeats the whole usage object on each, so a response containing [thinking, text, tool_use] is three records carrying identical prompt-token counts. Per-record summing overstated prompt volume 1.95× and output 2.34×. Every absolute halved: 6,134became6,134 became3,035.
  • File paths counted as tool targets. A paginated walk registered as 73 duplicate reads.
  • base64 length counted as image tokens. Charging len(base64) / 4 bills a 1451×840 JPEG at 27,878 tokens; Anthropic bills it at roughly w·h/7501,625, a 17× overstatement. It also moved the denominator: tool results are 24.2% of prompt volume, not the 40.1% previously published.

The 2026-07-28 revision moved two conclusions rather than magnitudes. Duplicate records sit milliseconds apart carrying identical context, which is the signature the backtest classified as "prefix mutation" — the measurement was generating mutation events from its own duplicates. With them removed:

  • Prefix mutation fell from 61.4% of cache loss (828.72)to13.4828.72) to **13.4% (87.11)**.
  • TTL expiry rose from 21.8% to 47.6% ($207.58), making it the largest single lever, at zero UX risk: refreshing a cache entry before it expires changes nothing the model sees.

The remaining 75.8% of context volume is measured by subtraction rather than observed, and is therefore subject to the same class of error.


9. Composed tiers

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%

%cost and %tokens are different units and do not track each other. TTL keep-alive and mutation elimination are price conversion: the tokens that fell out of cache are still sent, but are billed as reads (0.1×) instead of writes (2.0×) — same volume, ~20× cheaper. The levers therefore rank differently by billing model:

Team on API billing Developer under a token cap
TTL keep-alive largest lever no effect — conversion, not reduction
Mutation fix modest no effect
Tail-side hygiene small the only lever that reduces tokens

The zero-risk lever is worth more than all validated tail-side hygiene combined, and the levers that remove tokens are the small ones.

Measured position today: Bash truncation at 512 tokens with head-and-tail retention and a diagnostic exemption is ~55atLOWrisk;supersedeadds 55 at LOW risk; supersede adds ~9; age-out is 122.57122.57–304.66 with an unmeasured quality cost, pending a session replay with aged context.

Open blockers

# 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 resolve from the same experiment: one live session captured at the request-body level yields both the mutating field and the assembled prompt. That capture is what produced the tool-definitions finding in §4.


Measurement notes

Each of the following is a correction published against our own earlier figures, with the size of the error it produced.

  1. Count API requests, not transcript records. Summing usage per record roughly doubles every total. Join on message.id.
  2. Key tool calls on the whole input, not on the target. Otherwise pagination registers as duplication.
  3. Price images as images: w·h/750, not len(base64)/4 — a 17× error that moves the denominator.
  4. Verify redundancy against the bytes. "Same argument" is not "same content"; requiring byte-identity or verbatim containment moved the de-dup lever from 37.58to37.58 to0.33.
  5. Measure the clock separately from the money. They decompose differently: 242 hours of agents parked on approval does not appear in any cost metric.
  6. Instrument the assembled prompt, not just the transcript. 75.8% of the volume is visible only there, and in the one capture it was dominated by tool definitions.

The instrument that produced all of this runs as a local sidecar on each developer's own machine — nothing uploaded, transcripts read but never written. Details in Announcing the ACE sidecar.


Sign up to ACE now