TL;DR
Prompt caching stores the key-value cache a model computed for the start of a prompt and reuses it when a later request begins with the same tokens. The repeated prefix skips prefill and bills at a cached-input rate, typically 0.1x the base input price. It works on exact prefix matches only, so one changed byte near the top of the prompt costs the whole discount.
Published September 1, 2026. Updated September 7, 2026 with measured hit rates from production coding agents, engine internals, and the failure modes filed as bugs against real clients. Provider rates were fetched from each vendor's pricing page; Morph rates come from the same pricing table that bills the API.
“prompt tokens prefilled across one 30-turn coding-agent session with a 20k-token system prompt, to produce 15,000 output tokens. Prompt caching is what keeps that ratio affordable.”
Prompt caching is the reuse of a model's KV cache for a prompt prefix it has already processed. A request that begins with the same tokens as an earlier request skips prefill for the matching prefix and bills those tokens at a cached-input rate. Every major provider now sells it: 0.1x base input on Anthropic (0.025x on Claude Fable 5.1), OpenAI GPT-5.6, Gemini 3.x, and Moonshot Kimi K3, 0.032x on DeepSeek V4 off-peak, and $0.20 against $1.00 per 1M on GLM-5.3 744B at Morph. The mechanics are the same everywhere. The write surcharges, minimum prefix sizes, and retention windows are not.
What it saves
A 30-turn agent session with a 20k-token prefix sends 1,252,500 prompt tokens. At a 90% hit rate on GLM-5.3 744B it costs $0.402 instead of $1.30. On DeepSeek V4 Flash it costs $0.056 instead of $0.160. Exact replay reaches about 95% hits.
What breaks it
A changed byte in the prefix, a different worker, or a different model. Each model has its own cache, so a router that re-picks the model per turn re-prefills the whole context: 41,000 tokens at turn 15 cost $0.041 to move to GLM-5.3 744B versus $0.001 to continue warm on DeepSeek V4 Flash.
How Prompt Caching Works
A transformer answers a prompt in two phases. Prefill reads every prompt token and computes a key and a value vector per token per layer, the KV cache. Decode then produces output tokens one at a time, attending to that cache. Prefill is compute-bound: its cost grows with prompt length, and for a coding agent the prompt is the entire conversation so far. Decode is memory-bound and produces a few hundred tokens per turn. On the session above, prefill handles 1,252,500 tokens and decode handles 15,000. Prefill is where the money goes.
Prompt caching keeps the KV cache from prefill and reuses it. Three rules define what can be reused:
- Exact prefix match. Attention at position n depends on every token before n. The cache for a token is only valid if all preceding tokens are identical. A request therefore hits from token zero up to the first token that differs from the cached prefix, and never past it.
- Block alignment. Engines store the KV cache in fixed-size blocks and hash whole blocks. Morph caches on roughly 1k-token blocks. A matching prefix that ends mid-block reuses the complete blocks before it; the partial block is prefilled again. Very short prompts have no full block to reuse, which is why every provider publishes a minimum cacheable length.
- Locality. The cache is GPU memory (sometimes tiered to CPU or NVMe) on the worker that ran the prefill. A request only hits if it reaches that worker while the blocks are still resident. Retention is bounded either by a TTL or by LRU eviction under memory pressure.
Multi-turn agent loops are the ideal shape for this. Each turn re-sends the previous turns verbatim and appends one new message, so the entire prompt except the newest turn is a prefix the engine has already processed. The provider reports how much hit in the usage object; on Morph it is usage.prompt_tokens_details.cached_tokens, and the remainder of prompt_tokens bills at the input rate.
For the serving side of the same story, throughput, batching, and where the KV cache lives across GPU, CPU, and NVMe, see LLM inference. For the KV-cache memory techniques that make long prefixes cheap to hold, see grouped-query attention and FP8 quantization.
Inside the Engine: Block Hashing and Radix Trees
No vendor doc explains why the match has to be a prefix rather than a diff. The open source engines do, and their source is the clearest description of what every hosted cache is doing underneath.
vLLM splits the KV cache into fixed blocks, 16 tokens each by default, and content-addresses them with a chained hash. Block 0's hash covers its own tokens; block 1's hash covers block 0's hash plus its own tokens, and so on down the prompt. The consequence is the whole rule in one line: if block N's hash matches, blocks 0 through N-1 are guaranteed identical, so the scheduler can find the longest cached prefix by walking hashes until one misses, without comparing a single token. That is also why nothing after a miss can ever be reused, no matter how much of it is unchanged text.
SGLang stores the same idea as a radix tree. Every completed request is inserted as a path, shared prefixes collapse into one copy of each unique node, and eviction runs LRU from the leaves inward, so a parent prefix survives until all of its children are gone. Cached blocks are page-aligned, which is the mechanical source of the minimum-prefix rule every vendor publishes: at a page size of 16, a 35-token sequence stores the first 32 tokens in two complete pages and leaves the last 3 uncached. A 300-token prompt has no full page worth keeping.
Why only prefixes, when the middle of a prompt repeats too
The obvious question, asked every time this topic reaches a forum: if a phrase recurs in the middle of many prompts, why can the engine not cache that too? The answer is that a token's cached vectors encode everything before it. As one commenter put it in the Hacker News thread on the subject, the model is effectively compressing the whole preceding context into each token, so the same words appearing after different context produce entirely different vectors. There is nothing to match on.
This is a research problem, not a law. Prompt Cache, published at MLSys 2024, makes non-prefix reuse work by declaring reusable segments as prompt modules and precomputing attention states that stay positionally valid wherever the module lands in a prompt, reporting time-to-first-token improvements from 8x on GPU inference to 60x on CPU inference. The reason no major provider sells it is that it requires you to author prompts against a schema. Every shipping product, including the paged, block-hashed caches above, descends instead from PagedAttention, which made cross-request KV reuse practical without changing how you write a prompt.
Because the tree lives in one worker's memory, hit rate is decided by where a request is scheduled, not by whether caching is enabled. SGLang reports that its cache-aware scheduling policy reaches within 96% of the optimal hit rate on average, against a round-robin baseline that reaches a fraction of it, and measures hit rates from 50% to 99% across benchmark workloads depending on how much prefix the workload actually repeats. Every gateway, router, and load balancer in front of a model is making that scheduling decision on your behalf.
Why your cache is not shared with other customers
A tempting idea surfaces whenever people work this out: coding tools ship the same system prompt to every install, so a provider could cache it once globally and everyone would ride free. Providers segregate the cache per account instead, and the reason is a timing side channel. If a hit is measurably faster than a miss, and the cache spans tenants, then latency answers the question of whether anyone else sent this text recently. The Hacker News discussion works through the attack and its limits: guessing one token at a time would need the engine to store an entry after every single token, which no real implementation does because caches are block-aligned. The feasible version is coarser and still bad, confirming whether a competitor is loading a particular document or codebase into context.
The practical consequence is that a warm cache is something you create, not something you inherit. Cold start is real on every new conversation, and the only way to have a prefix already resident is to have sent it yourself, recently, to the same place.
Prompt Caching Pricing by Provider
Every number below was read from the vendor's own docs on September 1, 2026 (links in Sources). The cache read multiplier is cached input divided by base input. One representative model per vendor keeps the table readable; the same multipliers apply across each vendor's current lineup unless noted.
| Provider / model | Input | Cached input | Read multiplier | Write surcharge | Opt-in | Retention | Min prefix |
|---|---|---|---|---|---|---|---|
| Anthropic, Claude Sonnet 5 | $2.00 | $0.20 | 0.1x | 1.25x (5m TTL), 2x (1h TTL) | cache_control breakpoints | 5m default, 1h option, refreshed on hit | 1,024 tokens |
| Anthropic, Claude Fable 5.1 | $10.00 | $0.25 | 0.025x | 1.25x (5m), 2x (1h) | cache_control breakpoints | 5m default, 1h option | 512 tokens |
| OpenAI, GPT-5.6 Terra | $2.00 | $0.20 | 0.1x | None | Automatic; prompt_cache_key steers routing | 30 min after last write or reuse | 1,024 tokens |
| Google, Gemini 3.5 Flash | $1.50 | $0.15 | 0.1x | None (implicit); explicit caches bill $1.00 per 1M tokens per hour of storage | Implicit automatic on 2.5+; explicit CachedContent optional | Implicit: recent requests; explicit: your TTL | 4,096 tokens (implicit) |
| DeepSeek, deepseek-v4-flash (off-peak) | $0.22 | $0.007 | 0.032x | None | Automatic | Cleared when unused, hours to days | Full prefix match; interval not published |
| Moonshot, kimi-k3 | $3.00 | $0.30 | 0.1x | None | Automatic | Not published | Not published |
| Morph, GLM-5.3 744B (morph-glm53-744b) | $1.00 | $0.20 | 0.2x | None | Automatic; prompt_cache_key or x-session-id pins the worker | LRU, no expiry; or cache_ttl 5m to 24h, sliding | About one 1k block |
| Morph, GLM-5.3-Flash (morph-glm53flash) | $0.10 | $0.02 | 0.2x | None | Automatic; prompt_cache_key or x-session-id pins the worker | LRU, no expiry; or cache_ttl 5m to 24h, sliding | About one 1k block |
| Morph, Kimi K3 2.8T (morph-kimik3) | $2.50 | $0.29 | 0.116x | None | Automatic; prompt_cache_key or x-session-id pins the worker | LRU, no expiry; or cache_ttl 5m to 24h, sliding | About one 1k block |
| Morph, DeepSeek V4 Flash 0731 (morph-dsv4flash) | $0.1234375 | $0.03125 | 0.253x | None | Automatic; prompt_cache_key or x-session-id pins the worker | LRU, no expiry; or cache_ttl 5m to 24h, sliding | About one 1k block |
Two things stand out. First, the read multiplier has converged on 0.1x, with DeepSeek below it and Morph's DeepSeek V4 Flash and GLM-5.3 744B above it in ratio terms but far below it in absolute price: $0.03125 per 1M cached input on morph-dsv4flash is the lowest cached rate in the table after DeepSeek's own off-peak window, and Morph has no peak window (DeepSeek doubles every rate from 01:00 to 04:00 and 06:00 to 10:00 UTC on weekdays). Second, only Anthropic charges to write. A 1.25x write means a prefix must be read at least once more to break even against not caching; at 2x for the 1-hour TTL it must be read twice. Everyone else caches speculatively for free.
Per-vendor detail lives on the model pages: Anthropic API pricing, OpenAI API pricing, Gemini API pricing, DeepSeek API, and Kimi K3 API.
Anthropic Breakpoints: Four Slots and a 20-Block Window
Anthropic is the only major provider where you place the cache yourself, and the two limits that decide whether it works are easy to miss. There is a maximum of four cache_control breakpoints per request, and each breakpoint searches a bounded distance backwards. In Anthropic's wording: "The lookback window is 20 blocks. The system checks at most 20 positions per breakpoint, counting the breakpoint itself as the first. If the system finds no matching entry in that window, checking stops." A run of consecutive tool_use blocks counts as one position, and so does a run of consecutive tool_result blocks, so parallel tool calls do not single-handedly push the previous turn out of the window.
Two ways to place them. A single top-level cache_control field turns on automatic mode: the breakpoint lands on the last cacheable block and moves forward as the conversation grows, which is the right default for a chat loop. Per-block cache_control gives explicit control over exactly what gets written, which is what you want when the system prompt and the tool list should cache separately from the moving history.
Breakpoints are a budget, and clients that allocate them independently overrun it. Franklin v3.23.0 marked the system prompt (1), the last tool definition (1), and a rolling window of the last three messages (3), which is five. Anthropic rejects the request with HTTP 400: A maximum of 4 blocks with cache_control may be provided. Found 5. The first turn or two succeed and the session dies once history reaches three messages, unrecoverably: every retry replays the same history and emits the same five breakpoints. The fix is to size the rolling window against what is left, not to a constant. Reported in BlockRunAI/Franklin issue 73.
Anthropic also publishes which changes invalidate which layer, and one row of that table matters more than the rest: changing tool definitions invalidates the tools cache, the system cache, and the messages cache, all three. Toggling web search or citations rewrites the system prompt, so tools survive and everything after does not. Changing tool_choice, adding or removing an image, or dropping a thinking block invalidates message blocks only. Read input_tokens carefully when you audit this: it counts only the tokens after the last breakpoint, not everything you sent, so the total is cache_read_input_tokens plus cache_creation_input_tokens plus input_tokens.
Worked Example: A 30-Turn Coding Agent
The session: a 20,000-token system prompt plus tool definitions, byte-identical every turn. Each turn appends 1,500 tokens of user message, tool results, and assistant reply, and the model writes 500 output tokens. 30 turns. Turn n re-sends the prefix plus n minus 1 turns of history, so prompt tokens across the session sum to 1,252,500. The "90% hits" column bills 1,127,250 of those at the cached rate and 125,250 at the input rate.
| Line | No cache | 90% hits | Arithmetic |
|---|---|---|---|
| Prompt tokens | 1,252,500 | 1,252,500 | 30 x 20,000 + 1,500 x (0 + 1 + ... + 29) |
| Uncached input | $1.25 | $0.125 | 125,250 x $1.00 / 1M |
| Cached input | $0.00 | $0.225 | 1,127,250 x $0.20 / 1M |
| Output | $0.051 | $0.051 | 15,000 x $3.41 / 1M |
| Session total | $1.30 | $0.402 | 69% lower |
| Line | No cache | 90% hits | Arithmetic |
|---|---|---|---|
| Prompt tokens | 1,252,500 | 1,252,500 | same session |
| Uncached input | $0.155 | $0.015 | 125,250 x $0.1234375 / 1M |
| Cached input | $0.000 | $0.035 | 1,127,250 x $0.03125 / 1M |
| Output | $0.005 | $0.005 | 15,000 x $0.3475 / 1M |
| Session total | $0.160 | $0.056 | 65% lower |
90% is conservative for this shape. With exact replay only the first prefill (20,000 tokens) and each later turn's new 1,500 tokens miss: 63,500 of 1,252,500, a 95% hit rate. Anything below 90% on a loop like this means the prefix is changing or the requests are landing on different workers. The usage object on the final turn of an exact-replay session looks like this:
Turn 30 usage, exact replay
{
"usage": {
"prompt_tokens": 63500,
"completion_tokens": 500,
"total_tokens": 64000,
"prompt_tokens_details": { "cached_tokens": 62000 }
}
}62,000 of 63,500 prompt tokens bill at $0.03125 per 1M on morph-dsv4flash; the 1,500 new tokens bill at $0.1234375. Output is unaffected: caching never touches the decode side of the bill. If your agent's output share is large (always-on reasoning models like Kimi K3 emit long reasoning traces), caching cuts the input line and leaves the output line alone. For the full set of levers on the rest of the bill, see LLM cost optimization and Claude Code token usage.
Measured Hit Rates and Measured Savings
The arithmetic above assumes a hit rate. Here is what practitioners actually record. The most useful measurement is a controlled one: a bug report against qwen-code compared two clients driving the same Anthropic backend over comparable sessions, which isolates the client's prompt construction as the only variable.
| Client or workload | Cache read tokens | Fresh input tokens | Hit rate | Source |
|---|---|---|---|---|
| Claude Code, 182-message session | 136,640 | 677 | ~100% | qwen-code issue 5942 |
| qwen-code, 131-message session | 75,072 | 18,489 | 80% | qwen-code issue 5942 |
| qwen-code side queries | 0 | full prompt | 22 of 129 requests at 0% | qwen-code issue 5942 |
| SGLang benchmark workloads | varies | varies | 50% to 99% | SGLang / LMSYS |
| Exact-replay coding agent, modeled above | 1,189,000 | 63,500 | 95% | This page |
The gap between 100% and 80% on the same model is entirely prompt construction. That is the honest ceiling to aim at: a coding agent that replays verbatim and lands on the same worker should read essentially all of its context from cache, and anything materially below that is a bug in the client, not a property of the cache.
On the savings side, two independent evaluations agree on the range. A PricewaterhouseCoopers team ran 500 agent sessions on DeepResearch Bench with 10,000-token system prompts across four models and measured cost reductions of 79.6% on GPT-5.2, 78.5% on Claude Sonnet 4.5, 45.9% on GPT-4o, and 41.4% on Gemini 2.5 Pro, with time to first token improving 13.0%, 22.9%, 30.9%, and 6.1% respectively. LangChain reported 49% to 80% token-cost reductions across its Deep Agents eval suite, including 77% on Claude Haiku and 49% on Gemini 3.5 Flash.
The PricewaterhouseCoopers result that contradicts the usual advice: caching everything is not the optimum. Their finding is that full context caching can paradoxically increase latency, because dynamic tool calls and tool results trigger a cache write on every turn without ever producing a matching read. You pay the write and collect nothing. The strategies that won were narrower ones, caching the system prompt or explicitly excluding tool results, which is the opposite of what a framework flag labelled "enable caching" usually does.
How It Breaks in Production
Every failure below is a filed, reproducible report against a shipping client, not a hypothetical. They are the five shapes this takes.
Every layer supports caching and the composition still does not
An engineer posted the bill from a normal local coding-agent workflow running through four layers, each of which supports prompt caching: an agent client, a proxy library, AWS Bedrock, and Claude Opus 4.6. Gross usage was $37,901.73, of which credits absorbed $8,026.54. The breakdown is the argument for this entire page.
| Line | Tokens | Cost |
|---|---|---|
| Uncached input | ~6.47B | ~$35,600 |
| Cache read input | ~1.67B | ~$918 |
| Cache write input | ~101M | ~$698 |
| Output | ~25M | ~$698 |
Output was $698 of a $37,901 bill. Caching was not off, it was partial: 1.67B tokens did read from cache, against 6.47B that did not. Nothing in the chain reported a problem, because no layer is responsible for the composition. In the author's words, "prompt caching is supported" is not the same as "your actual agent stack is using prompt caching correctly", and budget alerts are not a kill switch. The only defence is to read the ratio of cached to uncached input tokens off your own requests on day one, before a month of them accumulates. Posted at Hacker News item 47933355.
All writes, no reads
The worst outcome is not a missing discount, it is paying the write surcharge on every single request. An OpenClaw bug report describes exactly that: caching configured, and every request showing a cache write of 170,602 tokens at $3 per 1M with cache reads pinned at zero. The cause was dynamic content in the system prompt, timestamps, message IDs, session metadata, and a "Current Date & Time" section, changing every turn. Measured cost went from an expected $0.05 per message to $0.50, and $35 in a day against an expected $9. On Anthropic this configuration is strictly worse than not caching at all, because the writes bill at 1.25x base input. Filed as openclaw issue 19534.
A side query that evicts the main thread
The qwen-code report contains a failure mode worth internalising, because a side query does not merely miss, it evicts. Its auxiliary calls resent the full conversation under a different system block and with no tools attached, so the prefix could never match the main agent's and returned cache_read = 0 by construction, in 22 of 129 requests. It then displaced the main conversation's cached prefix, so the next real turn missed too. One 45k-token uncached side query cost roughly three times the large cached main turn it interrupted. The fix is to run side queries behind the same system prompt and the same tool list as the main agent, appending the side instruction as a trailing message so it shares the prefix instead of competing with it. Filed as qwen-code issue 5942.
The agent's own tool call outlives the TTL
A 5-minute TTL is usually discussed as a problem for idle users. In an agent it is a problem for busy ones. A VS Code bug report puts it as a single rule: any two consecutive model calls with a time delta above 300 seconds miss the cache and pay full input price on the next call. The gap is not the human thinking, it is the agent's own npm install, test suite, or build finishing in a terminal. The report estimates a 3x to 5x cost multiplier for one mid-session gap and 8x to 15x cumulative on a long session with ten or more of them, and proposes a keepalive: a no-op request every 290 seconds while a tool runs, priced at a cached read plus one or two output tokens. Filed as vscode issue 321551. The alternative to a keepalive is a TTL that outlasts your longest tool call: Anthropic's 1-hour option at a 2x write, or on Morph a cache_ttl of 1h or longer at no write surcharge.
Your hit-rate metric is lying to you
Before concluding the cache is broken, confirm the number is real. Two open SGLang bugs describe a hit rate that reads wrong while the cache works fine: cached_tokens reporting zero when speculative decoding is enabled, and a cache_hit_rate gauge overwritten once per prefill batch, which makes it unusable under mixed traffic. Reported in sglang issue 20451 and issue 26608. Cross-check the billing line before you rewrite a prompt.
Cache-Aware Routing: Why a Naive Router Destroys the Hit Rate
An LLM router classifies a prompt and picks the cheapest model that can handle it. Applied per turn to a multi-turn session, it fights prompt caching in two ways.
- Each model has its own cache. A KV cache is a function of the weights. The prefix GLM-5.3 prefilled is useless to DeepSeek V4 Flash. The first turn on a new model prefills the whole conversation at that model's input rate.
- Within a model, the cache is per worker. A load balancer that spreads a conversation across replicas round-robin turns a 95% replay into a hit rate near 1 divided by the replica count. Session affinity, a stable mapping from conversation to worker, is what makes the automatic cache deliver.
At turn 15 of the example session the context is 41,000 tokens. Continuing on morph-dsv4flash serves those tokens from cache for $0.001. Switching to morph-glm53-744b prefills them fresh for $0.041, about 32x, before the turn produces a single output token. Switch back on turn 16 and you pay the DeepSeek input rate on the whole context again if its blocks were evicted in the meantime. A router that saves a per-token rate difference by switching can come out behind on every switch.
Route once, at the session boundary
The first turn of a session is the one moment a switch is free, because nothing is cached yet. Morph's Model Router classifies difficulty, ambiguity, and domain in one call at $0.005 per request, returns the model to call from a candidate list you pin, and then gets out of the way. Every later turn reuses the decision and carries the same session key, so both the model and the worker stay fixed.
Classify once per session, then pin model and worker
import os
import requests
from openai import OpenAI
MORPH_API_KEY = os.environ["MORPH_API_KEY"]
client = OpenAI(api_key=MORPH_API_KEY, base_url="https://api.morphllm.com/v1")
CANDIDATES = ["deepseek-v4-flash", "glm-5.2"] # two tiers, not ten
_session_model: dict[str, str] = {}
def model_for_session(session_id: str, first_turn: str) -> str:
# Route once, at the session boundary, when nothing is cached yet.
if session_id in _session_model:
return _session_model[session_id]
resp = requests.post(
"https://api.morphllm.com/v1/router/multimodel",
headers={"Authorization": f"Bearer {MORPH_API_KEY}"},
json={
"input": first_turn,
"allowed_models": CANDIDATES,
"policy": "cost_efficient",
"default_model": "glm-5.2",
},
timeout=5,
)
_session_model[session_id] = resp.json()["model"]
return _session_model[session_id]
def handle_turn(session_id: str, messages: list[dict]):
model = model_for_session(session_id, messages[-1]["content"])
return client.chat.completions.create(
model=model,
messages=messages,
extra_body={"prompt_cache_key": session_id},
)Two or three candidates is the right size. Every extra candidate is another cold cache the router can dump a session into. Re-classify only at boundaries you are already paying for: a new session, or a context compaction that rewrote the prefix anyway. Morph's Claude Code proxy ships this as a context lock: past 60k tokens of context it stops classifying and holds the session's model, because at that size prefill dominates any per-token rate difference.
Pin the worker with a session key
Caching on Morph needs no key. The key answers the other question: which worker serves the turn. Send one id per conversation as the prompt_cache_key body field or the x-session-id header, on every turn and on retries of the same turn, and each request routes to the worker that already holds its prefix. OpenCode and the Codex CLI already send prompt_cache_key; OpenRouter forwards x-session-id on behalf of its callers. The key changes placement, never billing. cache_ttl is independent: it controls how long that worker keeps the prefix, sliding, in five tiers from 5m to 24h. Invalid values are rejected with a 400.
Session key and TTL, Python (OpenAI SDK)
from openai import OpenAI
client = OpenAI(
api_key="YOUR_MORPH_API_KEY",
base_url="https://api.morphllm.com/v1",
)
# One id per conversation, generated when it starts, reused on every
# turn and on retries of the same turn. It pins placement, not billing.
SESSION_ID = "conv-8f2c1a"
response = client.chat.completions.create(
model="morph-dsv4flash",
messages=messages, # system, tools, full history, newest message last
extra_body={
"prompt_cache_key": SESSION_ID,
"cache_ttl": "1h", # sliding: every hit refreshes the clock
},
)
usage = response.usage
cached = usage.prompt_tokens_details.cached_tokens
print(f"{cached}/{usage.prompt_tokens} prompt tokens served from cache")Same key as a header, cURL
curl -X POST "https://api.morphllm.com/v1/chat/completions" \
-H "Authorization: Bearer YOUR_MORPH_API_KEY" \
-H "x-session-id: conv-8f2c1a" \
-H "Content-Type: application/json" \
-d '{
"model": "morph-glm53-744b",
"messages": [{"role": "user", "content": "..."}],
"cache_ttl": "6h"
}'One key across an app funnels every request onto one worker. That worker fills, sheds overflow with 429s, and the retries cost more than the cache saved. Generate the key when the conversation starts and scope it to that conversation. Keys are hashed server-side; the raw value is never stored.
Escalate a turn without losing the cheap prefix
Some turns in a cheap session genuinely need the stronger model. The mistake is to move the session. Instead, run the hard turn on the strong model under its own session key, append its answer to the shared history, and continue the next turn on the cheap model. Appending never invalidates a prefix: the cheap model's cached blocks still match through the last turn it served, and only the new assistant message and the next user message prefill. The strong model pays one full prefill, which is the price of the escalation, not a recurring tax. If escalations become frequent, the classifier was wrong about the session; re-route at the next compaction.
Per-turn escalation with two warm prefixes
CHEAP = "morph-dsv4flash"
STRONG = "morph-glm53-744b"
def handle_turn(session_id: str, messages: list[dict], hard: bool):
# The cheap model keeps its warm prefix under the session key.
# A hard turn runs on the strong model under its own key. Its answer
# is appended to the shared history, so the cheap model's prefix is
# still an exact match on the next turn: append never breaks a hit.
model = STRONG if hard else CHEAP
key = f"{session_id}:{model}"
reply = client.chat.completions.create(
model=model,
messages=messages,
extra_body={"prompt_cache_key": key, "cache_ttl": "1h"},
)
messages.append({"role": "assistant", "content": reply.choices[0].message.content})
return replyThe same rule governs multi-agent layouts: give each subagent its own session key and its own stable prefix rather than sharing one conversation across models. See multi-agent model routing and, for Claude Code specifically, Claude Code Router.
Gateways and Sticky Routing
A gateway that proxies your call to one of several upstream providers adds a failure mode the vendor docs never mention, because a cache lives where it was written. If a later request routes to a different provider endpoint, that endpoint cannot read it, and nothing in the response tells you the discount vanished for a routing reason rather than a prompt reason.
OpenRouter documents how it handles this and the mechanism is worth understanding even if you never use it. It recognises a conversation by hashing the first system or developer message together with the first non-system message, then pins later requests to the endpoint that served the earlier ones. That heuristic breaks on exactly the agents that need it most: any agent that rewrites its opening messages changes the hash and loses the pin. Passing an explicit session_idreplaces the derived key, and the timing difference is the point. With a session id, stickiness starts after the first successful request. Without one, stickiness starts only after a cache hit has already been detected, so turn one is a coin flip. OpenRouter's own list of miss causes is short and matches everything above: the prompt is too short, the cache expired, the opening block keeps changing, or the request moved to a different provider.
The same class of bug appears when a gateway sits between an editor and a model. A VS Code report describes Claude models routed through OpenRouter with a bring-your-own-key setup getting no prompt caching benefit at all in agent mode, with the cost showing up as ordinary input tokens. Filed as vscode issue 312939.
Morph's Model Router is a classifier, not a proxy. It answers which model to call and returns that name; your own client then calls the model directly, so no gateway is silently choosing an endpoint between your turns. That keeps the two decisions separate: the router picks the model once at the session boundary, and the prompt_cache_key or x-session-id you send on every turn picks the worker. Traffic arriving through OpenRouter is already tagged, since x-session-id is what it forwards on its callers' behalf.
Rules for a High Hit Rate
Stable prefix first
Order the prompt system prompt, then tool definitions, then history, then the newest message. Retrieved context and anything per-request goes last. A cache matches from token zero forward, so the variable part must sit at the end.
No timestamps, IDs, or names in the prefix
A clock, request ID, user name, or random example in the system prompt changes a byte in block one and ends the match at token zero. Put per-request metadata in the last user message or leave it out. One team reported moving a date-time stamp from the top of their prompt to the bottom and watching cached tokens go from roughly 30-50% to 50-70%, with no other change.
Think in 1k-token blocks
Morph caches on roughly 1k-token blocks; Anthropic and OpenAI will not cache prompts under 1,024 tokens (4,096 on Gemini 3.x and Claude Haiku 4.5). A 300-token prompt has nothing to reuse. A 20k-token prefix that grows 1.5k per turn reuses about 20 blocks on turn 2 and more on every turn after.
Replay turns verbatim, append only
Multi-turn loops hit because each turn re-sends the earlier turns unchanged. Trimming, summarizing, or reformatting an earlier message invalidates everything after it. When context must shrink, compact once at a clean boundary and treat it as a new prefix.
Deterministic tool serialization
Serialize tool definitions in a fixed order with stable JSON key ordering (sort_keys=True in Python). Two semantically identical objects serialized with different key order are different strings, so they hash differently and miss. A framework that rebuilds the tool list from a set or map each turn reorders it silently and moves the divergence point up to the tool block.
One session key per conversation
Send the same prompt_cache_key or x-session-id on every turn and every retry so the request reaches the worker that holds the prefix. Never reuse one value across unrelated traffic.
Measure, do not assume. Log cached_tokens divided by prompt_tokens per request and alert when a session's ratio drops below the replay expectation. On Anthropic the equivalent fields are cache_read_input_tokens and cache_creation_input_tokens; on DeepSeek, prompt_cache_hit_tokens and prompt_cache_miss_tokens. Claude Code exposes the same ratio in /cost.
Pitfalls
Kimi K3 needs reasoning_content replayed verbatim
Kimi K3 thinks on every turn and is trained to see its own prior reasoning. Moonshot's quickstart requires adding the complete assistant message to the next request, reasoning_content and tool_calls included, not content alone. Harnesses that strip the thinking to save tokens degrade the model and, because the stripped history differs from what the model saw, still miss the cache. Replay it and the prefix grows, which is exactly what $0.29 per 1M cached input on morph-kimik3 (against $2.50 uncached) is for. Details on the Kimi K3 API page.
Temperature does not break the cache
A misconception common enough that practitioners report arguing about it with people who should know better: sampling parameters do not affect cache hits. Temperature, top_p, and seed act during decode, choosing among the distribution the model produces. The cache is built during prefill, before any of them apply. Change the temperature between two otherwise identical requests and the second still hits. The parameters that do invalidate are the ones that change the serialized prompt, which on Anthropic means tool definitions, tool_choice, images, and thinking configuration. The test is simple: if it alters the bytes the model reads, it breaks the prefix; if it only alters how the next token is picked, it cannot.
cached_tokens is quantized, so it never equals your prompt length
A recurring confusion on OpenAI's developer forum: people compare cached_tokens against prompt_tokens, see a shortfall of a few hundred, and go looking for a prefix bug that is not there. Caching begins at 1,024 tokens and then advances in 128-token increments, so on a 6,260-token prompt the ceiling is 1,024 plus 40 times 128, which is 6,144 cached tokens. The remaining 116 are the unaligned tail, and they will always be there. The same forum thread is where the causal rule gets stated most plainly by practitioners: remove one word early in a prompt and everything after it is evicted, because attention at a position depends on every token before it.
Tool definitions that drift
Tool schemas usually sit right after the system prompt, so a change there invalidates nearly everything. Common causes: an MCP server that re-registers tools with a new description on reconnect, a framework that dedupes tools through a set and loses order, or a dynamic tool loader that injects a per-turn subset. If tools must vary per turn, place the variable subset after the stable ones.
Streaming retries
A stream that drops mid-generation is usually retried with the same request. That retry is a cache hit only if it reaches the same worker, so send the same session key on the retry. Do not append the partial assistant output to the history before retrying: the truncated message becomes part of the prefix on every later turn and differs from what any provider cached.
Editing history
Rewriting an earlier message, re-ranking retrieved chunks, or moving a summary to the top of the prompt changes the prefix at that point. Everything after it misses. This is expected; caching is prefix-based. Append, and when you must rewrite, do it once at a compaction boundary so the new prefix caches fresh and stays stable.
TTL expiry and eviction
Anthropic's default cache lives 5 minutes; a user who reads a long response and replies six minutes later pays a cache write again. OpenAI holds 30 minutes on GPT-5.6. Morph defaults to LRU with no fixed expiry and lets you set a sliding cache_ttl up to 24h for sessions that go idle between turns. Past the TTL the prefix stops hitting entirely, and re-sending it caches it again at the normal input rate; there is no write surcharge on Morph.
FAQ
What is prompt caching?
Prompt caching reuses the key-value (KV) cache a model computed for the beginning of an earlier prompt. When a new request starts with the same tokens in the same order, the serving engine skips prefill for the matching prefix and only computes the tokens after the divergence point. The provider bills the reused tokens at a cached-input rate: 0.1x the base input price on Anthropic (0.025x on Claude Fable 5.1), OpenAI GPT-5.6, Gemini 3.x, and Moonshot Kimi K3; $0.007 vs $0.22 per 1M on DeepSeek V4 Flash off-peak; and $0.20 vs $1.00 per 1M on GLM-5.3 744B at Morph.
How much does prompt caching save?
It depends on the hit rate and the cached-to-input ratio. A 30-turn coding agent with a 20k-token system prompt and tools, growing 1,500 tokens per turn, sends 1,252,500 prompt tokens in total. At a 90% hit rate on GLM-5.3 744B at Morph the session costs $0.402 instead of $1.30, a 69% reduction. On DeepSeek V4 Flash at Morph it costs $0.056 instead of $0.160. Exact multi-turn replay pushes the hit rate to about 95%, because only the first prefill and each turn's new tokens miss.
Is prompt caching automatic?
On OpenAI (GPT-5.6 and later), Google Gemini 2.5 and newer (implicit caching), DeepSeek, Moonshot Kimi, and Morph, yes: matching prefixes are detected and billed at the cached rate with no request changes. Anthropic requires explicit cache_control breakpoints on the content blocks you want cached and charges a write surcharge of 1.25x base input for the 5-minute TTL or 2x for the 1-hour TTL. Gemini also offers explicit CachedContent objects billed per token-hour of storage.
Why is my cache hit rate zero?
The prefix changes between requests. Diff two consecutive prompts byte for byte; the first differing token ends the cacheable prefix. The usual culprits are a timestamp, request ID, or user name in the system prompt, tool definitions serialized in a different order, retrieved context inserted before the conversation instead of after it, or prompts shorter than the provider's minimum (1,024 tokens on OpenAI GPT-5.6 and Anthropic Sonnet 5, 4,096 on Gemini 3.x and Claude Haiku 4.5, about one 1k block on Morph).
Does switching models mid-conversation break the cache?
Yes. Every model has its own KV cache, so the first turn on a new model prefills the entire conversation at that model's full input rate. At turn 15 of the example session the context is 41,000 tokens: continuing on DeepSeek V4 Flash at Morph bills those tokens at the cached rate for $0.001, while moving them to GLM-5.3 744B prefills them fresh for $0.041. Route at session boundaries, hold the model once context is expensive, and escalate single turns without abandoning the cheap model's warm prefix.
What is a session key for prompt caching?
A per-conversation identifier that tells the serving layer which worker to send the request to. Caching is automatic, but a cached prefix lives in the memory of the worker that prefilled it; a follow-up that lands on a different worker re-prefills everything. On Morph, send the same value in the prompt_cache_key body field or the x-session-id header on every turn of a conversation, including retries. OpenAI's prompt_cache_key does the same job for its cache routing.
How long does a cached prompt last?
Anthropic: 5 minutes by default, 1 hour with the extended TTL, refreshed on each hit. OpenAI GPT-5.6: 30 minutes after the most recent write or reuse. DeepSeek: cleared when unused, usually within hours to days. Morph: LRU with no fixed expiry by default, or a sliding cache_ttl of 5m, 30m, 1h, 6h, or 24h set per request. Gemini explicit caches are billed per token-hour of storage for as long as you keep them.
How does prompt caching reduce inference cost?
It removes prefill work from the bill. Reused prefix tokens skip the forward pass that computes their key and value vectors, so the provider charges a cached rate instead of the input rate, typically 0.1x. Two independent evaluations put the real-world reduction in the same band: a PricewaterhouseCoopers study of 500 agent sessions on DeepResearch Bench measured 41.4% to 79.6% lower cost across four models, and LangChain measured 49% to 80% across its Deep Agents eval suite. Output tokens are unaffected, so the saving shrinks as your output share grows.
How do I use Anthropic prompt caching?
Add cache_control to the request. A single top-level cache_control field turns on automatic mode, where the breakpoint lands on the last cacheable block and advances as the conversation grows. Per-block cache_control gives explicit placement, with a hard maximum of four breakpoints per request and a lookback window of 20 block positions per breakpoint. Verify it worked by reading cache_read_input_tokens and cache_creation_input_tokens in the usage object, remembering that input_tokens counts only the tokens after the last breakpoint.
How does prompt caching work on OpenRouter, and how do I enable it?
Caching itself is the upstream provider's, so what OpenRouter adds is keeping your requests on the endpoint that holds the cache. It recognises a conversation by hashing the first system or developer message with the first non-system message, then routes later requests to the same provider. Pass an explicit session_id to replace that derived key: with it, sticky routing starts after the first successful request, and without it stickiness only begins once a cache hit has already been observed. Models that need explicit breakpoints still need cache_control passed through in the request body.
Which AI gateway handles prompt caching best?
Judge a gateway on one question: can a conversation be pinned to the endpoint that holds its prefix, from the first request, using a key you control. A gateway that infers the pin by hashing your opening messages loses it the moment an agent rewrites them, which is why explicit session identifiers exist. The alternative is not to proxy at all. Morph's Model Router returns the name of the model to call and your client calls it directly, so nothing reroutes between turns and the prompt_cache_key or x-session-id you send is the only thing deciding placement.
What is the best prompt caching method?
Cache the stable prefix, not everything. The counterintuitive result from the PricewaterhouseCoopers evaluation is that caching the full context can raise latency, because dynamic tool calls and tool results trigger a cache write every turn that never earns a matching read. Their best-performing strategies cached the system prompt or explicitly excluded tool results. Order the prompt system prompt, tools, history, newest message, replay earlier turns verbatim, and send one session key per conversation.
Does temperature affect prompt caching?
No. Temperature, top_p, and seed apply during decode, when the model samples from the distribution it has already produced. The cache is built during prefill, before any sampling happens, so two requests with identical prompts and different temperatures hit the same cached prefix. What does invalidate a cache is anything that changes the serialized prompt itself: on Anthropic that includes tool definitions, tool_choice, images, and thinking configuration.
Is my prompt cache shared with other users?
No. Providers scope the cache per account, so a popular system prompt shipped by a coding tool is cached separately for every customer using it. The reason is a timing side channel: if cache hits are faster than misses and the cache spanned tenants, response latency would reveal whether someone else had recently sent a given piece of text, which is enough to detect what a competitor is loading into context. The practical consequence is that a warm prefix is one you sent yourself, recently, to the same place.
Automatic prompt caching on every open model
No breakpoints, no write surcharge. GLM-5.3 744B at $0.20 per 1M cached input, DeepSeek V4 Flash 0731 at $0.03125, with a session key that keeps every turn on the worker that holds its prefix and a cache_ttl you set per request.
Sources
- Anthropic: Prompt caching (1.25x 5-minute write, 2x 1-hour write, 0.1x read, 0.025x on Fable 5.1; per-model minimums of 512, 1,024, and 4,096 tokens; Sonnet 5 $2 / $2.50 / $4 / $0.20)
- OpenAI: Prompt caching (enabled by default; 1,024-token minimum on GPT-5.6 and later; 0.1x cached reads; 30-minute retention; prompt_cache_key influences routing)
- OpenAI: API pricing (GPT-5.6 Terra $2.00 input, $0.20 cached input, $12.00 output per 1M)
- Google: Gemini API context caching (implicit caching on by default for 2.5 and newer; 4,096-token minimum on 3.x models; explicit CachedContent)
- Google: Gemini API pricing (Gemini 3.5 Flash $1.50 input, $0.15 cached, $1.00 per 1M tokens per hour storage; 3.1 Pro Preview $2.00 / $0.20 / $4.50 per hour)
- DeepSeek: Models and pricing (deepseek-v4-flash $0.007 cache hit, $0.22 cache miss, $0.66 output per 1M off-peak; peak double, 01:00 to 04:00 and 06:00 to 10:00 UTC weekdays)
- DeepSeek: Context caching (enabled by default; full prefix match; prompt_cache_hit_tokens and prompt_cache_miss_tokens; cleared when unused within hours to days)
- Moonshot: Kimi K3 pricing (kimi-k3 $0.30 cache hit, $3.00 cache miss, $15.00 output per 1M; 1,048,576-token context)
- Moonshot: Kimi K3 quickstart (multi-turn rule: add the complete assistant message to the next request, not content alone)
- Morph: Prompt caching (automatic prefix caching, cached rates, ~1k-token blocks, prompt_cache_key and x-session-id, cache_ttl tiers, usage.prompt_tokens_details.cached_tokens)
- Morph: Model Router (/v1/router/multimodel and /v1/router/classify, $0.005 per request, cache-breaking events, 60k-token context lock)
- Lumer et al., PricewaterhouseCoopers: Don't Break the Cache, an Evaluation of Prompt Caching for Long-Horizon Agentic Tasks (arXiv:2601.06007) (500 agent sessions, DeepResearch Bench, 10k-token system prompts; cost reductions 79.6% GPT-5.2, 78.5% Claude Sonnet 4.5, 45.9% GPT-4o, 41.4% Gemini 2.5 Pro; TTFT 13.0%, 22.9%, 30.9%, 6.1%; full context caching can increase latency)
- qwen-code issue 5942: avoidable prompt-cache misses inflate cost (Claude Code 136,640 cache read against 677 fresh input at ~100%, qwen-code 75,072 against 18,489 at 80% on the same backend; side queries returned cache_read = 0 on 22 of 129 requests and evicted the main prefix)
- openclaw issue 19534: Anthropic prompt caching broken, cache read always 0 (cache write of 170,602 tokens at $3 per 1M on every request; $0.50 per message against an expected $0.05; $35 in a day against an expected $9; cause was timestamps and session metadata in the system prompt)
- vscode issue 321551: prompt cache expires during active agent sessions (any two consecutive model calls more than 300 seconds apart miss; 3x to 5x for one gap, 8x to 15x cumulative; keepalive ping every 290 seconds proposed)
- vscode issue 312939: OpenRouter BYOK Claude models do not benefit from prompt caching in agent mode
- BlockRunAI/Franklin issue 73: five cache_control breakpoints against a maximum of four (HTTP 400 "A maximum of 4 blocks with cache_control may be provided. Found 5." once history reaches three messages; unrecoverable because retries replay the same history)
- OpenRouter: prompt caching and sticky routing (routing key derived by hashing the first system or developer message with the first non-system message; session_id makes stickiness start after the first successful request rather than after a cache hit; four causes of misses)
- LangChain: prompt caching in Deep Agents (49% to 80% token-cost reduction across the eval suite, including 77% on Claude Haiku and 49% on Gemini 3.5 Flash; explicit breakpoints where supported, implicit caching where not)
- Sankalp: How prompt caching works (vLLM 16-token blocks, chained block hashing where block N's hash covers block N-1's hash, longest-prefix walk; deterministic JSON serialization with sort_keys)
- vLLM: Automatic prefix caching design (block hash chaining and the guarantee that a match at block N implies blocks 0 through N-1 are identical)
- LMSYS: Fast and expressive LLM inference with RadixAttention and SGLang (radix tree of shared prefixes, LRU eviction from the leaves, cache-aware scheduling within 96% of the optimal hit rate, 50% to 99% hit rate across benchmarks)
- sglang issue 20451: cached_tokens reports 0 with speculative decoding and issue 26608: cache_hit_rate gauge overwritten per prefill batch
- OpenAI developer community: How does prompt caching work (caching starts at 1,024 tokens and advances in 128-token increments, so a 6,260-token prompt caches at most 6,144 tokens)
- Hacker News: $38k AWS Bedrock bill caused by a simple prompt caching miss (gross $37,901.73 with $8,026.54 in credits; ~6.47B uncached input tokens at ~$35.6k against ~1.67B cache read at ~$918, ~101M cache write at ~$698, and ~25M output at ~$698, through a four-layer stack where every layer supports caching)
- Hacker News: discussion of ngrok's prompt caching explainer (moving a date-time stamp from the top of a prompt to the bottom raised cached tokens from roughly 30-50% to 50-70%; sampling parameters such as temperature do not affect caching; why per-tenant cache isolation defends against a timing side channel; why mid-prompt segments cannot be matched)
- Gim et al.: Prompt Cache, Modular Attention Reuse for Low-Latency Inference (MLSys 2024) (non-prefix reuse via prompt modules with positionally valid precomputed attention states; time-to-first-token improvements of 8x on GPU and 60x on CPU inference)
- Kwon et al.: Efficient Memory Management for Large Language Model Serving with PagedAttention (paging and virtualizing the KV cache, the mechanism every shipping prefix cache descends from)
- r/ClaudeAI: How does prompt caching technically work (an inference engineer's explanation that attention values depend only on a token and the tokens before it, so they are fixed for a given prefix and can be kept instead of discarded)