CASE STUDY 03 — The same workload is worth 19.2% or 4.1%, depending on which cache tier you bought

Quiet Overrun, operated by Local Good Work LLC · 2026-07-29, revised 2026-07-30 · case study v1.2.3 · findings library v1.8.3 · engine cost_model.py, run unmodified. Same pipeline as CASE-STUDY-01: ingest → engine → report, nothing bespoke.

The finding. On one hour of Moonshot AI's Kimi production traffic — the only public production trace that ships prefix-block hashes — measured prefix reuse is 57.1% of tool/agent input tokens and 37.4% of conversation input tokens. What that reuse is worth is not a number. It is a range, and the range is set by a procurement decision most teams make without measuring:

Tool/agent workload5-minute cache tier1-hour cache tier
Achievable hit rate (LRU replay, same capacity)0.52850.5591
Cache write multiple on that tier1.25×2.00×
Break-even hit rate the tier requires0.21740.5263
Savings19.19% of spend4.09% of spend

Same traffic. Same reuse. Same measurement. 19.2% or 4.1% — a 4.7× difference decided entirely by which retention tier the buyer is on. The longer tier retains more (hit rate rises from 0.528 to 0.559) but charges 2.00× to write instead of 1.25×, and on this traffic the write premium overwhelms the retention gain.

This is a sensitivity result, not a point estimate. That is the useful form: a reader who does not know which tier they are on now knows the question to ask, and roughly what the answer is worth.

1. The proof underneath: break-even hit rate

Prompt caching is not free. You pay a premium to write a block and a discount to read it, so caching only pays when the hit rate clears

h_break-even = (w − 1) / (w − r)

where w is the cache-write multiple and r the cache-read multiple of base input price. On the cited card (r = 0.1):

  • 5-minute tier (w = 1.25): break-even 0.2174. Tool/agent measures 0.5285 — comfortably clear.
  • 1-hour tier (w = 2.00): break-even 0.5263. Tool/agent measures 0.5591 — clear by only 3.3 percentage points. Conversation measures 0.3385 — it does not clear break-even at all, and the engine correctly sizes CA-01 at $0 there, leaving only the duplicate-call finding.

That is why the conversational workload's two tiers read 3.49% and 0.66%: on the extended tier, caching this traffic loses money, and the finding's own falsifier says so before a consultant can talk anyone into it.

A workload sitting near break-even is the single most valuable thing this measurement can tell a buyer, and it is invisible without both the hit rate and the tier's write multiple in the same calculation.

2. Both tiers, both expiry semantics, per trace

refresh = a cache read extends the entry's life (the vendor's "hits & refreshes" wording). hard = expiry runs from write time regardless of reads — the pessimistic edge of the band. Everything else is identical.

tool/agent — 23,608 requests, 3537s, mean 8596 in / 182 out tokens

TierTTLwriteh_targetbreak-evensavings (refresh)savings (hard)$/1M requests
5-minute cache (default tier)5 min1.25×0.52850.217419.19%17.81%$3,649
1-hour cache (extended tier)60 min2.00×0.55910.52634.09%4.09%$778

conversation — 12,031 requests, 3537s, mean 12035 in / 343 out tokens

TierTTLwriteh_targetbreak-evensavings (refresh)savings (hard)$/1M requests
5-minute cache (default tier)5 min1.25×0.29310.21743.49%2.29%$960
1-hour cache (extended tier)60 min2.00×0.33850.52630.66%0.66%$182

Priced cost before any finding: $19,013.82 per 1M tool/agent requests, $27,496.31 per 1M conversation requests (cited card, this token mix).

3. Why the tier matters so much here: reuse distance

A 5-minute cache cannot serve a prefix last seen 40 minutes ago. How much reuse falls outside the window is a property of the workload, and the two traces differ sharply:

tool/agentconversation
Reuse events226,316105,710
Median gap between reuses0 s114 s
90th percentile gap243 s519 s
Share of reuse beyond 5 minutes7.4%21.5%

Agentic traffic reuses its prefix immediately — median gap 0 seconds — so a short cache captures nearly all of it. Conversational traffic reuses across minutes, so 21.5% of its reuse falls outside a 5-minute window. This is why agent workloads are the natural caching target and chat workloads are not, and it is measurable from timestamps alone.

4. The reuse measurement — three definitions, and the one that matters

  • A — block-occurrence share: reused block occurrences ÷ all block occurrences. A = Σ_r Σ_i [hash seen before] / Σ_r |hash_ids_r|
  • B — token-weighted share: input tokens in previously-seen blocks ÷ all input tokens, where block i carries min(512, input_length − 512·i) tokens.
  • C — contiguous-prefix share: as B, but a block counts only if every block before it in the same request also hit. This is what a real prefix cache can serve: KV state is positional, so a match at position 5 is worthless if position 4 missed.
TraceABC (contiguity enforced)Paper (reported)
tool/agent55.25%57.06%57.06%~59%
conversation36.64%37.36%37.36%~40%

C ≡ B exactly, and here is why

Enforcing contiguity changes nothing: C equals B to floating-point precision on both traces. The reason is checkable in one pass of the ingest — zero hits occur after a miss within the same request: 0 of 226,316 hits (tool/agent) and 0 of 105,710 (conversation). The released hash_ids are cumulative prefix hashes, so any reuse is by construction a clean prefix.

That matters because it removes the strongest objection to this method. Block- independent counting could over-credit a cache by counting mid-request matches no positional cache could serve. On this data it provably does not. We publish B because it is what a cache prices on, and C because it is what a cache can serve — and they are the same number.

Against the paper's reported figures (~59% / ~40%): B closes most of the gap that occurrence-counting leaves open. The residual ~2 points is not resolved from the released artifact — the paper measured its production system over data we cannot access; the release is a one-hour sample. We publish what the committed script computes on pinned bytes and do not adjust toward the reported number.

5. Cache pricing is not one number — the card changes the answer

Each row pairs a card with the hit rate measured at its own documented retention window, taken from the ingest's retention probe — pairing a 1-hour hit rate with a 5-minute write price is precisely the error this study was corrected for in v1.1.0. The OpenAI rows are split by model generation because both the write price and the window changed at GPT-5.6. Every multiple and window below was re-read from the vendor's own page on 2026-07-29 (v1.2.0 — the previous OpenAI row was wrong; see changelog).

Cache cardread / write multiplebreak-even hwindowh (tool / conv)tool/agent savingsconversation savingsnote
Anthropic 5-minute cache0.10× / 1.25×0.217300 s0.528 / 0.29319.19%3.49%cited card, default tier
Anthropic 1-hour cache0.10× / 2.00×0.5263600 s0.559 / 0.3394.09%0.66%cited card, extended tier
OpenAI automatic, GPT-5.6+0.10× / 1.25×0.2171800 s0.559 / 0.33921.00%5.19%1.25x write; at least 30 minutes
OpenAI automatic, before GPT-5.60.10× / 1.00×0.000300/600 s0.528–0.559 / 0.293–0.33925.22%–26.63%9.23%–10.56%no additional fee; 5-10 min idle

Three things a reader should take from this table:

  1. The reuse ratios and hit-rate curves are card-independent. The savings percentage is not. Conversation swings from 0.66% to 10.56% of spend across published cards on identical traffic. Any vendor quoting a single "caching saves X%" figure without naming a card and a generation is quoting nothing.
  1. The cited card is the conservative choice, not a flattering one. Against a no-premium card at the same 0.10× read, Anthropic's 5-minute tier is worse for every hit rate below h = 1.000 — that is, at every hit rate short of a perfect cache. As a share of the input-token bill:

`` Anthropic 5-minute 1 − (0.10h + 1.25(1−h)) = 1.15h − 0.25 no write premium 1 − (0.10h + 1.00(1−h)) = 0.90h equal when 1.15h − 0.25 = 0.90h → 0.25h = 0.25 → h = 1 ``

So pricing this study on OpenAI's pre-5.6 card would have produced a larger savings figure at every hit rate we measured. We kept the smaller one.

v1.1.0 published a different crossover here, because it fed this same equation a misread OpenAI card. The arithmetic was never wrong; the card was. The superseded figures are in the changelog and deliberately not restated here — a live document that repeats its own retired numbers is a document a reader can quote back wrongly. The build now derives the crossover from the card list in closed form and asserts that the closed form still reproduces the old published value when fed the old card, so the correction is provably to the data and not to the mathematics.

  1. Identical multiples, different window — and the window saturates. The current GPT-5.6+ card is 0.10× / 1.25×: the same line as Anthropic's 5-minute tier, same break-even, no crossover to compute. The only difference is retention — "at least 30 minutes" against five. That is worth +3.1 points of hit rate on the tool/agent trace (52.8% → 55.9%) and +4.5 points on the conversation trace (29.3% → 33.9%), lifting tool/agent savings from 19.19% to 21.00% of spend.

And no more than that. The measured hit rate is identical at 600 s, 1,800 s and 3,600 s — to the last digit, on both traces — because from ten minutes upward the replay is exactly the untimed, capacity-only replay at the stated 32,000-block reference capacity (0.5591 tool/agent, 0.3385 conversation; asserted in the build, not eyeballed). Past ten minutes this cache is capacity-bound, not time-bound. Buying a longer window buys nothing here; buying more capacity might. That is the opposite of the intuition that a longer TTL is strictly better value, and it is only visible because the window and the price were separated.

On DeepSeek. v1.1.0 carried a "DeepSeek-style" row at 0.10× / 1.00×. With OpenAI's read multiple corrected to 0.10×, that row became numerically identical to the pre-5.6 OpenAI row, so it has been removed rather than double-counted. DeepSeek's own published card is in fact more aggressive than either vendor here — cache-hit input at 0.02× of cache-miss input on V4-Flash and 0.0083× on V4-Pro, so the read multiple is not even constant across their own card — but their pricing page documents no cache-write charge at all. An absence of a documented charge is not a documented zero, and this study will not price a card on an absence.

6. Unit economics only — a deliberate constraint

Both traces span 58.9 minutes. We report cost per request, cost per 1M requests, and savings as a percentage of the priced window — never a monthly or annual figure. Scaling one hour to a month multiplies by ~720 and assumes flat demand; our own CASE-STUDY-01 measured 121 days of real traffic whose daily demand ranged from 0.7% of median to 11× median. A monthly number here would contradict the published finding sitting next to it.

The engine's canonical window is monthly, so the standard renderer prints "/mo" labels. With N set to 1,000,000 requests as the unit, every such figure reads exactly "$ per 1M requests at this mix." No annualised figure appears in this artifact or its screenshots.

7. The two findings that sized, and where they overlap

CA-01 (prompt caching) and AG-03 (duplicate calls) are the only findings this schema can size. Their engine credits at the default tier (5-minute cache (default tier), refresh):

tool/agentconversation
CA-01$3,493.50$780.85
AG-03$155.81$179.19
Composed total$3,649.30$960.04

Disclosed overlap. Input tokens belonging to exact duplicate requests (0.91% of tool/agent input tokens, 0.66% of conversation) are counted both inside CA-01's f_prefix (they are reused blocks) and inside AG-03's pct_duplicate_calls. The two findings sit in different overlap groups (input_price vs call_volume), so the engine's partition panel does not flag them. Multiplicative composition prevents additive inflation — the composed total is below the naive sum — and AG-03 is only 4.3% of the tool/agent result. We disclose it rather than leave a reader to find it.

8. Everything the audit declined to size — engine output, verbatim

45 of 54 selected findings held at $0 by the measurement invariant, each naming the variables it would need. Accounting: 54 = 2 sized + 45 unmeasured + 1 excluded (RS-02) + 6 enablers (IN-08, MS-02, QR-01, QR-02, QR-05, RS-03).

FindingTitleMissing variables
AG-01Unbounded or oversized agent loop depthL_current, L_target
AG-02Quadratic context growth across loop iterationsctx_target_ratio
AG-04Polling agents running with no event to respond toS_polling, pct_null_result
AG-05No confidence gate — every request takes the full expensive pathcheap_path_cost_ratio, pct_spend_easy
AG-06Multi-agent decomposition where a single agent performs equallypct_orchestration_overhead
CA-04No response cache on repeated or near-repeated queriesf_cacheable_queries, hit_rate_response
CA-05Embeddings regenerated for unchanged contentS_embed, pct_changed
IN-01GPU endpoints held warm with no scale-to-zero on non-latency-critical workU_current, U_target, f_idle_eligible
IN-02Provisioned for peak, running at troughf_static_provisioned, target_capacity_ratio
IN-03Steady baseline running entirely on on-demand pricingbaseline_uncovered, commitment_discount
IN-04Accelerator class mismatched to the workload shapecost_per_request_ratio, f_serving
IN-05Zombie endpoints — provisioned resources serving nothingf_zombie
IN-06Interruption-tolerant work running at on-demand pricesS_interruptible, interruption_overhead, spot_discount
IN-07Self-hosted serving without continuous batching, leaving throughput unusedS_serving, throughput_ratio
IN-09Steady API token baseline running entirely on pay-as-you-goapi_commit_discount, baseline_api_uncovered
IN-10Non-production traffic billed on the production budget at production pricesf_nonprod, pct_nonprod_avoidable
IN-11Observability and vector-database platforms billing on defaults nobody sizedS_observability, S_vectordb, pct_obs_reducible, pct_vecdb_reducible
IN-12Multi-provider shadow, comparison, and always-on failover traffic never turned offf_shadow, pct_shadow_reducible
IN-13Weight precision never evaluated — serving at training precision by defaultS_serving_quantizable, q_throughput_ratio
IN-14KV cache memory left unoptimised — concurrency limited by how the cache is allocated, not by the GPUS_serving_kv, kv_concurrency_ratio
IN-15Speculative decoding never benchmarked on this workloadS_serving_decode, spec_throughput_ratio
IN-16Committed AI capacity deployed but not consumedU_deployment, U_deployment_target, f_committed_capacity
IN-17Capacity reservation exceeding what is deployed — excess lost every periodf_committed_capacity, f_reservation_excess
MS-01Frontier model serving a task a small model matchesd_route, f_routable
MS-03Reasoning mode enabled on requests that do not benefit from itf_reason, f_reason_needed
MS-04Pinned to a superseded model generation at superseded pricesd_upgrade, f_on_old_model
MS-05Fine-tuning trade never priced — long prompts doing a tuned model's job, or a tuned model outliving its economicsS_ft_amortized, f_prompt_replaceable, ft_price_ratio
QR-03Truncation and parse failures absorbed silently downstreamf_retry_eliminable, pct_retry_dup
QR-04Underprovisioned model on the highest-value request classf_high_value_class, uplift_price_ratio
QR-06Guardrail and moderation calls sized and scoped like product callsS_moderation, pct_moderation_reducible
RG-01Retrieval top-k set far above what the answer requiresf_retrieved, k_reduction
RG-02No reranking, so retrieval weakness is compensated with context volumeS_rerank, f_retrieved, rerank_reduction
RG-03Chunk overlap inflating both index size and retrieved tokensf_retrieved, pct_duplicate_context
RG-04Whole documents injected where a passage would answerf_document_context, passage_ratio
RG-05Retrieval index rebuilt in full rather than incrementallyS_rebuild, rebuild_target_ratio
RS-01Latency-tolerant traffic running on the interactive tierf_batchable
RS-04Retry storms and retries on non-retryable errorspct_wasted_retries
RS-05Tool and function definitions resent in full, uncached, on every callf_tool_block, pct_tool_block_removable
RS-06Generations billed but never deliveredpct_undelivered, pct_undelivered_avoidable
RS-07Unconstrained output length — no schema, no length instructionf_reason, pct_output_reducible
RS-08Requests crossing a long-context price tier that context work would keep below itf_tier_premium, pct_tier_reducible
RS-09Vision requests tokenised at maximum detail where the task needs a fraction of itf_image_tokens, pct_image_reducible
RS-10System-prompt bloat — accreted instructions nobody has ablatedf_sysprompt_deadweight
RS-11Multi-sample generation (n>1, best-of, self-consistency) never re-justifiedf_sampling_overhead, pct_sampling_reducible
RS-12Conversation history resent in full on every turnf_history, history_target_ratio

9. LRU capacity curves (at the default 5-minute tier)

tool/agent

Cache capacityHit rate (tokens)Hit rate (block occurrences)
2,000 blocks (~1M tok)35.88%34.73%
8,000 blocks (~4M tok)45.19%43.75%
32,000 blocks (~16M tok)52.85%51.17%
128,000 blocks (~65M tok)52.85%51.17%
unbounded52.85%51.17%

conversation

Cache capacityHit rate (tokens)Hit rate (block occurrences)
2,000 blocks (~1M tok)5.47%5.37%
8,000 blocks (~4M tok)18.11%17.76%
32,000 blocks (~16M tok)29.31%28.75%
128,000 blocks (~65M tok)29.31%28.75%
unbounded29.31%28.75%

Capacity is not the binding constraint here: 32,000 blocks = 16.4M tokens ≈ 2–5 GB of KV cache for GQA models of the relevant class — comfortably a single node. Retention, not capacity, is what sets the answer on this traffic, which is the opposite of the usual assumption.

10. What one hour of trace cannot show

  • No demand shape. Commitment and utilisation findings (IN-09, IN-01/02, IN-10's time signatures) need weeks of demand curve. CASE-STUDY-01 (121 days) is the complement that measures exactly that family.
  • No sessions. Agent-loop findings (AG-01/AG-02) need request grouping the release does not carry.
  • No models, latency classes, quality data, or infrastructure — the MS-, RS-01, QR-, IN-* families stay unmeasured, named in section 8.
  • No billing. Dollars are modeled from tokens at the cited card, never observed.
  • One hour is not a TTL experiment. A 1-hour trace cannot distinguish a 1-hour cache from an unbounded one, so the 1-hour column is a lower bound on that tier's retention benefit — which makes the tier gap we report conservative.

Methodology note

Provenance. github.com/kvcache-ai/Mooncake FAST25-release/traces (Qin et al., arXiv 2407.00079; Apache-2.0). Pinned SHA-256 (the ingest refuses non-matching bytes):

  • conversation_trace.jsonl — 12,031 rows — b8cbb061a85206d729d91cdc2981f43c9e0d99209dce588d3af5f7934408b9df
  • toolagent_trace.jsonl — 23,608 rows — 48a2db1a13d3bc05e6330140c64f604ba366df20d3c9e128b5c35a01c1fa5f71

Excluded file: arxiv-trace/mooncake_trace.jsonl (earlier release, same row count as toolagent, different bytes — never combine).

Block semantics. hash_ids are remapped 512-token prefix-block hashes; the final block of a request may be partial (input_length ≤ 512 × len(hash_ids) is validated on every row). Timestamps are milliseconds; both spans verified ≈58.9 min.

Cache replay. Block-granular LRU, replayed in trace order, with a stated TTL and a stated capacity, touch-on-hit, insert-on-miss, lazy expiry. Each tier is priced at the write multiple that tier actually costs on the cited card — the 5-minute replay at 1.25×, the 1-hour replay at 2.00×. An untimed replay on a 59-minute trace is a 1-hour replay; pricing that at the 5-minute multiple describes a configuration no card sells, and v1.0.0 of this case study did exactly that (corrected in v1.1.0 — see changelog). Real schedulers differ in admission policy and eviction; the capacity curve and both expiry semantics are published so any operating point can be read off. The recipe is unit-tested against hand-derived fixtures including reuse just inside and just outside the window (test_mooncake_ingest.py, 50 checks).

Pricing (stated, cited, substituted openly). Kimi is a Chinese provider; its collection-era rate card is not verifiable. We price at Anthropic's published card as of 2026-07-29, re-read 2026-08-25 (platform.claude.com): Claude Sonnet 5 at $2/1M input, $10/1M output — announced as introductory pricing through 31 August 2026 and now the standard rate; the scheduled increase to $3/$15 will not occur, so the unit-cost figures in §4 do not expire. Cache read 0.1×; cache write 1.25× (5-minute) or 2.00× (1-hour) — the same r/w constants the findings library carries as vendor-verified. Moonshot's own card would scale the dollar figures; it would not change the reuse ratios or the hit-rate curves. It would change the savings percentages, as section 5 shows — which is why that section exists. The 1-hour column re-runs the same engine with w set to 2.00 in an in-memory copy of the library; findings_library.json on disk is unmodified.

Reproduce the measurements. Every step below runs on files published with this page, plus the public traces.

git clone --depth 1 --filter=blob:none --sparse https://github.com/kvcache-ai/Mooncake
cd Mooncake && git sparse-checkout set FAST25-release/traces
python3 mooncake_ingest.py FAST25-release/traces my_measurements.json
python3 test_mooncake_ingest.py
diff my_measurements.json measurements_cs03.json     # expect no output

The ingest aborts on any trace whose SHA-256 differs from the pinned hashes above, so a clean diff verifies every measured quantity on this page: both reuse definitions, the TTL-aware replay, the retention probe, the capacity curve and the duplicate shares.

What you cannot reproduce from this page, stated plainly. Converting those measurements into savings percentages runs on cost_model.py and the findings library. Neither is published — they are the audit product, not a download. build_case_study3.py and cards_cs03.py ARE published, so the card list, the crossover closed form and its assertions can be read and checked directly. And the headline arithmetic does not need the engine: CA-01's formula is printed above, so C_in · f_prefix · max(0, 1 − (h·r + (1−h)·w)) can be evaluated by hand from the measurements file for any row of the card table. The engine's contribution beyond that is multiplicative composition across findings, which moves the plan total and not the single-finding figures this study leads on.


Sources, every vendor figure re-read from the vendor's own live page on 2026-07-29 (v1.2.0 — the previous OpenAI row was not): Mooncake traces & paper — github.com/kvcache-ai/Mooncake, Qin et al., arXiv 2407.00079 (FAST'25), Apache-2.0. Anthropic — platform.claude.com prompt-caching documentation: "5-minute cache write tokens are 1.25 times the base input tokens price", "1-hour cache write tokens are 2 times the base input tokens price", "Cache read tokens are 0.1 times the base input tokens price", "The cache is refreshed for no additional cost each time the cached content is used"; Claude Sonnet 5 at $2/$10 per MTok — announced as introductory pricing through 2026-08-31 and confirmed the standard rate on 2026-08-25, with the scheduled $3/$15 increase cancelled, so the unit-cost figures in §4 do not expire. OpenAI — developers.openai.com prompt-caching guide: "Cache writes have no additional fee on models before the GPT-5.6 family", "On GPT-5.6 models and later model families, cache writes cost 1.25× the uncached input token rate", "A cached prefix remains eligible for reuse for at least 30 minutes", "cached prefixes generally remain active for 5 to 10 minutes of inactivity, up to a maximum of one hour". The 0.10× read multiple is taken from the cached-input-to-input ratio within each row of the developers.openai.com pricing table rather than from its absolute prices, because that page carries Standard, Batch, Flex and Priority tiers; the ratio is invariant to which tier a reader is billed on, the absolute prices are not, and this study never needs them. DeepSeek — api-docs.deepseek.com pricing page (read multiples only; no cache-write charge is documented, so no DeepSeek row is priced). CASE-STUDY-01 (this repo) for the demand-shape constraint.

Everything this page was computed from

Downloaded and run, these reproduce the figures above. The measurements file is the build's own output, not a transcription.