Prompt caching is usually explained as a latency feature that also saves money. On an agentic workload that has it backwards. It is a cost feature that also happens to be faster, and the size of the effect is not small enough to treat as an optimisation.
Here is the arithmetic, and then what it looked like against 9.75 billion real cache-read tokens.
The break-even is smaller than you think
Three prices matter, all expressed as multiples of the model's normal input rate:
| Token class | Multiple | Effect |
|---|---|---|
| Uncached input | 1.00× | baseline |
| Cache write 5-minute TTL | 1.25× | +0.25× once |
| Cache read | 0.10× | −0.90× every time |
On the one-hour TTL the write costs 2× instead, moving break-even to 2.22 reads — still under three.
You pay a quarter extra once, then save nine tenths repeatedly. Divide 1.25 by 0.90 and break-even arrives at 1.39 reads. Since you cannot read a fraction of a prefix, the practical rule is:
If a cached prefix will be read twice, cache it. There is no threshold to agonise over.
What it did to a real bill
Over 34 days my workload produced 170,837,424 cache-write tokens and 9,753,346,376 cache-read tokens. A ratio of 57 to 1 — every prefix written was read fifty-seven times on average, forty times past break-even.
| Scenario | Cost | vs. cached |
|---|---|---|
| With caching what it actually was | $6,465.11 | — |
| Without caching every token at full input rate | $49,791.80 | 7.7× |
| Saved | $43,326.69 | 87% |
The cache-write premium across the whole period totalled about $208. It bought $43,535 of avoided read cost.
The write premium — the thing that makes people hesitate — was $208 across 34 days. Rounding error against what it saved.
The failure mode nobody warns you about
Caching is a prefix match. Any byte that changes anywhere in the prefix invalidates everything after it. Which means it can silently stop working, and your bill quietly grows by up to 8× while nothing errors.
The usual culprits are mundane:
- A timestamp or
datetime.now()interpolated into the system prompt - A request ID or UUID near the front of the context
- JSON serialised without sorted keys, so field order varies between runs
- Tools added, removed or reordered mid-conversation — tools render first, so this invalidates everything
- Switching model mid-session; caches are model-scoped
Each one turns a 0.1× read back into a 1.0× input charge, invisibly.
Verify it in one line
- Check
usage.cache_read_input_tokenson repeated calls. Non-zero and growing means it is working. - Zero across repeated identical requests means something in your prefix is changing. Diff two rendered prompts byte by byte to find it.
- Remember total prompt size is input + cache-write + cache-read. A small
input_tokensis a good sign, not a small prompt. - Don't assume caching is on because you set the parameter — a prefix under the minimum cacheable length silently won't cache, with no error
For a workload like mine, an undetected cache regression would have cost roughly $1,400 a day at list rates. It is worth a monitor.
Method & disclosure
From the same dataset as the 34-day cost study — 381 session transcripts, 23,157 billable calls, 18 June to 3 August 2026, de-duplicated by message ID. Cache-read and cache-write token counts are actual, taken from per-message usage records.
Costs are calculated: published per-model list rates applied to measured tokens, cache reads at 0.1× input and writes at 1.25× on the five-minute TTL. "Without caching" prices every cached token at the full input rate. Multipliers are published rates, not measured ones.
No commercial relationship with any vendor named. No affiliate arrangement on anything in this piece.