How Claude's prompt cache works
From zero: what Claude Code sends on every message, what Anthropic keeps in its cache, what that costs, when it expires, and what keep-warm, /rewind, /branch, /fork, subagents, /model and /compact do to it. Then: how to build the same things in your own harness on the Anthropic API.
1. Words this page uses
Each word below means one thing on this whole page. Skim it now and come back when a word is unclear.
| Word | Meaning |
|---|---|
| request | One call from Claude Code (or your own program) to Anthropic's Messages API. One message of yours can cause several requests, one per tool round. |
| block | One piece of a request: the system prompt, a message of yours, a reply, a tool call, a tool result. |
| SYS | The first blocks of every Claude Code request: tool definitions, system prompt, CLAUDE.md. Drawn as one block on this page. |
| tool call / tool result | A reply in which Claude asks to run a tool ("run this Bash command") / what the tool returned, sent back in the next request. |
| token | The unit the API counts and bills, roughly ¾ of a word. |
| prefix | The first part of a request, from its first block up to some block. |
| cache entry | The stored, already-processed form of one prefix, kept on Anthropic's side for a while. |
| cache breakpoint 🚩 | A mark on a block of a request that says "store an entry ending after this block". In the API it is a cache_control field. |
| TTL | Time to live: how long an entry survives after it was last used. 5 minutes or 1 hour. |
| cache read / cache write / uncached input / output | The four kinds of tokens in every request; explained in rule 3. |
| session | One conversation in Claude Code: a list of messages it keeps on your machine. The API has no idea sessions exist (part 7). |
| side request | A request Claude Code makes outside your conversation: a subagent, a forked subagent, keep-warm's ping, compaction. |
| keep-warm | A Claude Code mod that sends one tiny request after 50 idle minutes, so your cache entries don't expire (part 6). |
| fork | Three different things share this word: /fork (a copy of your session that runs in the background), a forked subagent (/subtask), and keep-warm's $.model.fork (your last request sent again). This page always says which one. |
| checkpoint | Only /rewind's snapshot of files and conversation position. Not related to cache breakpoints. |
2. Why a cache exists at all
2.1 Claude remembers nothing between requests
Every API request stands alone: the model keeps no memory of the previous one. So to continue a conversation, Claude Code sends the whole conversation again, every time, with the new part added at the end.
Each time, the request carries everything from before plus the newest part (white outline). In a long session that is hundreds of thousands of tokens per request.
2.2 What is inside one request
A request is a list of blocks, always in this order. The order matters (rule 1).
| Block | What it holds | Changes how often |
|---|---|---|
| Tool definitions + system prompt | Claude Code's instructions and the description of every tool | Rarely: when the set of tools changes |
| Project context | CLAUDE.md, memory, rules | At session start, /clear, /compact |
| Messages, oldest first | Your messages, Claude's replies, tool calls, tool results | Grows on every request |
2.3 Why that would be slow and expensive
Before the model can answer, it must process every input token. Without a cache, a 200,000-token conversation means processing 200,000 tokens on every message, although 99% of them are the same as last time. Prompt caching lets the API skip the part it processed recently, and bill that part at a tenth of the normal price or less.
3. The rules of the cache, one at a time
Rule 1. Entries are found by the exact start of the request
An entry is found again only when a later request starts with exactly the same blocks, in the same order, byte for byte, sent to the same model. One changed character in SYS, and nothing after it can match. That is why the blocks that change least come first.
Rule 2. A cache breakpoint 🚩 says where an entry ends
The request marks the blocks after which the API should store an entry (up to 4 marks per request). Claude Code places them for you: one at the end of the latest message on every request, and others earlier, such as after the system prompt. Claude Code's docs don't list where every 🚩 goes, so this page simplifies: it draws two per request, after SYS and after the last block.
After three messages, Claude Code has sent three requests. Each one put a 🚩 after SYS and a 🚩 at its end, so the cache holds these entries. Each is a separate, whole prefix:
There is no entry "reply 1 alone" or "SYS … reply 1": no request ever put a 🚩 right after reply 1. A new request that starts SYS, msg 1, reply 1, msg 2, reply 2, msg 3, reply 3, msg 4 matches entry 4 (the longest one it starts with) and pays the cache-read price for those blocks. Entries 1 to 3 are not used separately; they are just shorter prefixes of the same blocks.
Rule 3. Every request is split into four kinds of tokens
When a request arrives, the API looks for the longest prefix that ends where a live entry ends (it searches back from each 🚩, up to 20 blocks). Then every token falls into one of these:
| Kind | Field in the API's usage report | Price vs a normal input token |
|---|---|---|
| cache read | cache_read_input_tokens | 0.1× on most models; 0.05× on Opus 5.5 and Sonnet 5.5; 0.025× on Fable 5.1 |
| cache write | cache_creation_input_tokens | 1.25× (5-minute entry) or 2× (1-hour entry) |
| uncached input | input_tokens | 1× |
| output | output_tokens | the model's output price |
On a Max plan you don't pay per token inside your plan's usage. We assume the same ratios decide how fast that usage runs out; Anthropic's docs don't say. inferred
Rule 4. A reply is not cached when it is written
The cache stores only what was sent in. A reply is output, so at first it exists only on your machine. It enters the cache on the next request, when Claude Code sends it back before that request's 🚩.
Rule 5. Entries expire; every read restarts the timer
An entry lives for its TTL after its last use. The timer starts when that request starts, not when its reply ends. Each request that reads through it restarts the timer, for free. If nothing reads it for the whole TTL, it is deleted, and the next request processes and writes everything again: the slow, expensive turn. Claude Code asks for 1 hour for your main conversation on a subscription (within its usage) and 5 minutes for side requests. With an API key, usage credits or a cloud provider, it asks for 5 minutes for both, unless you set promptCacheTtl. Below, one hour runs in 6 seconds: green ticks are requests reading the entry; then you stop.
4. Step through it: pick a scenario
Each card is one situation, written as the question it answers. Pick one, then press Next (or click the progress bar). Every step animates in order: the clock moves, the request is assembled, the API matches it against the cache, the usage comes out, the cache updates, and your conversations update.
A model built from the rules above. Token sizes are made up but realistic. Block widths are not to scale. The 20-block search limit is left out; it never matters here. The cost line uses the standard prices: read 0.1×, write 2× for a 1-hour entry or 1.25× for a 5-minute one. On Opus 5.5 and Sonnet 5.5 a read costs 0.05×.
5. Reading the numbers Claude Code shows you
Every response reports the four counts from rule 3. The context-meter mod adds them up per message:
◆ ctx 7% · sent 214k in 3 requests (99% cached) · out 1.4k · $0.0270
| Part | Meaning |
|---|---|
ctx 7% | The size of the conversation now, as a share of the model's context window. |
sent 214k in 3 requests | read + write + uncached, summed over this message's requests. Each request re-sent the whole ~71k conversation, so "sent" is far bigger than the conversation. |
(99% cached) | cache read ÷ sent. |
out 1.4k | Output tokens. |
$0.0270 | What the message cost at API prices. |
A cache bust (counted at the right of the status line) is a request that read nothing. A subagent's first request usually counts as one: it starts its own conversation with a different SYS, so it can't read your entries (scenario "A normal subagent").
6. The keep-warm ping, in detail
After 50 idle minutes the keep-warm mod calls $.model.fork. Claude Code's mod API types describe it as: the main conversation's last request again (same model, system prompt, tools, messages) with one extra message after it, and "its own tail never cached". Scenario "Away 61 minutes, with keep-warm" animates it.
- Everything up to message 2 is identical to your last request, so it is a cache read, and that read restarts the timers of the entries along that chain.
- The ping is processed but not stored.
- "pong" is output, never stored, and the mod discards it. Your conversation does not change.
- Your next real request starts exactly like the entry the ping refreshed, so it matches.
Is "ping / pong" anything special?
No. "Reply pong" is just a short message I chose; any tiny message works. Nothing in the API knows about pings. What makes it work is ordinary prompt caching:
- the request starts with exactly your last request: same model, same system prompt, same tools in the same order, same messages;
- it is sent before the entry expires;
- it adds as little as possible after the match and asks for a tiny answer, so it costs about one cache read.
So yes, you can do the same in your own projects with the plain Messages API. Part 8 has the code. The ping is a side request with a 5-minute TTL, but that doesn't shorten your entry: while your entry is warm, the ping writes nothing, and in our test the session was still fully cached 11 minutes after the ping.
"idleCompaction": false in your settings (Claude Code 2.1.290 or later). docs not tested7. Sessions, rewind, branch and fork, from the API's point of view
7.1 The API has no sessions
The Messages API takes a model, a system prompt, tools, a list of messages and a max-tokens limit, and returns one response. It keeps no conversation on its side. The only thing it keeps is the prompt cache, and the cache is keyed by content and model, not by session, user or branch (it is shared within one workspace on the Claude API, and within one organization on Amazon Bedrock and Google Cloud).
So every Claude Code feature below is the same move underneath: Claude Code decides which list of messages to send next. The cache simply rewards lists that start the same way as a recent one.
| In Claude Code | What Claude Code does with the message list | What the API sees | Cache effect |
|---|---|---|---|
| a session | Keeps one list; appends each message, reply, tool call and result | Requests, each extending the previous one | Each reads the previous one's entry and writes the new tail |
/rewind (restore conversation) | Cuts the list back to before the chosen message; can also restore files from its local snapshots | A request that starts like an older one, then differs | Reads the longest live entry in the kept part |
/branch | Copies the list into a new session, switches you into it; the original stays on disk | Requests that start exactly like the original's | The copy's first request reads the original's entries; after that they diverge |
/fork (agent view) | Copies the list into a new background session and adds an instruction message at the end; you stay in the original | Same as branch, plus one message at the end | Reads the original's entries |
/subtask (forked subagent) | Sends your list + a brief as a separate run; keeps that run's tool calls out of your list; adds only its final result | A side request that starts like yours | Reads your entries; its own tail gets a 5-minute entry |
| subagent | Starts a new list with its own system prompt and tools | An unrelated request | Its own entries, 5-minute TTL |
keep-warm ($.model.fork) | Re-sends the last request + a ping; discards the reply | A request that starts like your last one | Refreshes your entries; writes nothing while they are warm |
/compact | Sends the list + "summarize"; then replaces the list with the summary | One request like yours, then requests that start with the summary | Reads once (if warm); then a new, short chain |
/model | Same list, different model field | Same content, other model | Separate cache per model |
7.2 Branch vs fork vs subtask
You move into a copy. The original stays on disk, unchanged, and you return with /resume. Same process and folder. Use it to try another approach and keep the old path.
A copy runs as its own background session while you stay in the original. It gets an instruction message at the end, usually works in its own worktree, and the two are independent from then on.
Not a session: a helper run that sees your whole conversation, works on the side, and sends back only its result as a message in your conversation.
At the API level all three are the same: a request whose messages start like yours. The differences are what Claude Code does around it: which list keeps growing, which session you are in, where files get edited, and what comes back.
7.3 Edge cases with fork, branch and rewind
| Case | What happens | |
|---|---|---|
| Rewind inside a branch | Only the branch's list is cut. The original's conversation, its checkpoints, and its cache entries are untouched. Both share entries for the part they have in common. Scenario "/branch, then rewind the branch". | docs model |
| Rewind the original after branching | The mirror image: the branch is untouched. | docs model |
| "Restore code" in a branch | Rewind restores files on disk. /branch runs in the same folder as the original, so restoring code in one changes the files the other sees too. A /fork copy usually edits in its own worktree, so its files are separate. | inferred |
| Rewind a branch to before the branch point | Checkpoints are saved with the conversation, and /branch copies the conversation, so the earlier prompts should be in the branch's rewind menu too. | not tested |
| The same session resumed in two terminals, without branching | Messages from both interleave into one transcript. Use /branch or claude --continue --fork-session instead. | docs |
| Branch after a long break | Both are cold. Branching copies the list, not the cache; the first request of either pays the full write. | model |
| Keep-warm in the original, branch later | The ping keeps the original's chain warm, and the branch starts with that chain, so the branch's first request hits. | model |
| A reply shared by both lines | It is written once per line, because no entry ends right after it (rule 2). Small, but it is why a branch's or rewind's first request writes a little more than just your new message. | model |
8. Build it yourself on the Anthropic API
Everything in part 7 can be built in your own harness, because none of it is a server feature. Sessions, rewind, branch, fork, subtasks, subagents, compaction and keep-warm are all "decide which messages to send". The cache does the rest, if you follow these rules:
| Rule for your harness | Why |
|---|---|
Put a cache_control 🚩 on the last block of every request, and one at the end of the system prompt. | Entries exist only where you put 🚩 (rule 2). Up to 4 per request. A top-level cache_control field (the API calls it automatic caching) puts the last one for you. |
Never change anything before the end: same tools, same order, same system text, never edit old messages. Keep tool_choice, the thinking settings and effort the same. No timestamps or random IDs in the system prompt. | One changed byte early on and nothing after it matches (rule 1). |
Keep the same model for a conversation. | Each model has its own cache. |
Ask for "ttl": "1h" if users pause for more than 5 minutes. | The default entry lives 5 minutes; a 1-hour entry costs 2× to write instead of 1.25×. |
| In long tool loops, add a 🚩 partway if a single request appends 20 or more blocks. | The search for an earlier entry looks back only 20 blocks from each 🚩. A run of parallel tool calls, or of their results, counts as one. |
Check usage.cache_read_input_tokens on every response. | It is the only proof the cache worked. |
| Keep prompts above the model's minimum cacheable length. | Very short prefixes are not cached at all, and the API returns no error. The minimum is per model: 512 tokens on current models such as Opus 5.5, up to 4,096 on some older ones. |
A sketch in Python with the official SDK. Not run: it shows the shape, check names against the current API docs.
# pip install anthropic import copy, anthropic client = anthropic.Anthropic() MODEL = "claude-opus-5-5" TTL = {"type": "ephemeral", "ttl": "1h"} SYSTEM = [{"type": "text", "text": open("system.md").read(), "cache_control": TTL}] # 🚩 after SYS TOOLS = [...] # same list, same order, every request def with_flag(messages): """A copy of the list with a 🚩 on its very last block.""" msgs = copy.deepcopy(messages) last = msgs[-1] if isinstance(last["content"], str): last["content"] = [{"type": "text", "text": last["content"]}] last["content"][-1]["cache_control"] = TTL return msgs def send(session, text): """A normal turn: append, send everything, append the reply. No tool loop here: a tool_use reply needs tool_result blocks in the next message.""" session.append({"role": "user", "content": text}) r = client.messages.create(model=MODEL, max_tokens=4096, system=SYSTEM, tools=TOOLS, messages=with_flag(session)) session.append({"role": "assistant", "content": r.content}) print(r.usage) # cache_read_input_tokens, cache_creation_input_tokens, input_tokens return r def rewind(session, index): """/rewind: drop everything from this user message on. Pick an index of a real user prompt, not a tool result, so roles stay valid.""" del session[index:] def branch(session): """/branch or /fork: an independent copy. Its first request reads the original's cache.""" return copy.deepcopy(session) def subtask(session, brief): """/subtask: a side run on a copy; only its answer comes back. Put the answer into your next message in the main session.""" side = branch(session) return send(side, brief).content[-1].text def keep_warm(session): """Keep-warm: your last request again, with max_tokens=0, so no reply. Nothing is kept.""" msgs = with_flag(session[:-1]) # the last request exactly: everything before the last reply r = client.messages.create(model=MODEL, max_tokens=0, system=SYSTEM, tools=TOOLS, messages=msgs) return r.usage.cache_read_input_tokens # about the whole conversation if it worked def compact(session): """/compact: ask for a summary over the cached list, then start a new list from it.""" side = branch(session) summary = send(side, "Summarize this conversation so work can continue.").content[-1].text session[:] = [{"role": "user", "content": "Summary of our work so far:\n" + summary}, {"role": "assistant", "content": "Understood."}]
On the plain API you need no ping. max_tokens=0 is the API's documented pre-warming mode: it reads the prompt into the cache and returns no output, so no output tokens are billed. It is refused with streaming, extended thinking, structured outputs or a forced tool_choice. Claude Code's $.model.fork has no such option, which is why keep-warm sends a ping. Run keep_warm on a timer a few minutes before the TTL runs out, and stop after a few rounds if nobody comes back.
9. What resets the cache, and what doesn't
Anything that changes an early block changes the start of every later request. From Claude Code's prompt-caching docs:
| The next request is read from scratch after… | The cache is kept after… |
|---|---|
switching model (/model)changing effort, on most models (not on Opus 5.5 / Sonnet 5.5 / Haiku 5.5 / Fable 5.1 with a subscription or API key) turning on fast mode (once per conversation) compaction ( /compact, auto or idle compaction)adding or removing tools, when tools are loaded up front (not the default) many images, when old ones get dropped a new Claude Code version (in a new session) |
editing files editing CLAUDE.md mid-session (it applies only after /clear, /compact or a restart)MCP servers connecting or leaving, with tool search on (the default) changing permission mode or output style running skills and slash commands, /recapspawning a subagent /rewind (restore conversation or code; its two "Summarize" options replace part of the conversation, so they work like /compact), /fork/branch (not on that page: it copies the conversation, per the sessions docs)
|
10. What we measured
In the browser terminal on 2026-10-14, with Claude Haiku 5.5 sessions:
| Test | Result |
|---|---|
| One keep-warm ping in a 69k-token session | 68.8k cache read, about 400 uncached (the ping), 4 output tokens. |
| The next real message after that ping | 99% cache read. Asked whether the conversation contained a ping, Claude said no. |
| Two identical 128k sessions, idle 61 minutes, then the same prompt | Without a ping: 24% cache read, $0.0994. With a ping at minute 50: 100% cache read, $0.0065, about 15× cheaper. |
The session without a ping still got 24%: most likely its SYS stayed warm because the other test sessions in the same folder sent the same SYS (Claude Code's docs say sessions run in parallel in the same directory read each other's cache, and sequential ones do when their startup git status matches). Not tested.
11. Where each fact comes from
| Fact | Source |
|---|---|
Requests re-send everything; prefix matching; block order; 1 h main / 5 min side on a subscription, 5 min for both on an API key; reads restart the timer; what resets the cache; rewind keeps entries warm; restoring files doesn't touch the cache; /fork adds its instruction at the end so the cache stays intact; a forked subagent reads the parent's cache | docs Claude Code: prompt caching |
/branch copies the conversation and switches you into it; two terminals on one session interleave | docs Claude Code: sessions |
/fork copies into a background session; the two are independent | docs Claude Code: agent view |
/subtask inherits system prompt, tools, model and history; only its result comes back | docs Claude Code: subagents |
| Rewind checkpoints and actions | docs Claude Code: checkpointing |
cache_control, up to 4 breakpoints, the ~20-block search, the usage fields, the price multipliers, the 1-hour TTL, the minimum length, the timer starting with the request, workspace and organization scope, max_tokens=0 pre-warming | docs Anthropic API: prompt caching |
| Keep-warm's fork repeats the last request; its tail is never cached | docs this Claude Code build's API types ($.model.fork, 2.1.299) |
| The ping read, the 99% follow-up, the 15× result, the session still fully cached 11 minutes after a ping | measured our tests, 2026-10-14 |
Where exactly Claude Code puts its breakpoints; the 1-hour TTL of a /fork copy; all scenario numbers; the edge cases tagged model, inferred or not tested; the Python sketch; the 24% explanation | model follows from the rules above; not measured |