inqizitblog

Theme

Linen — The house face. Warm paper, rust ink, and nothing shouting.

ai · llm ·

Prompt caching, from the causal mask outwardPart 2 of 4

Where the breakpoint goes

The placement that bills more than no caching, why the last block suits a growing loop, and how four breakpoints and the cascade divide a prompt.

IndexFull pageRecap

ContentsPart 2
  1. Start hereIntroduction
  2. Part 1Why the cache is a prefix
  3. Part 2Where the breakpoint goes
    1. 07The placement that costs more than no caching at all
    2. 08When “the last block” is exactly right
    3. 09Four breakpoints, four clocks
    4. 10The cascade, and the doors out of it
  4. Part 3What it costs, and for how long
  5. Part 4Running it
  6. After the partsRecap
  7. After the partsPractice
  8. AppendixGlossary
  9. AppendixReferences

IndexFull page

The placement that costs more than no caching at all

Now the failure. Static context in the first few blocks, a per-request block carrying a timestamp at the end, breakpoint on the last block. Every request produces a different hash there. The walk steps back through the earlier blocks and finds nothing — because no request ever wrote at those positions. So you pay a fresh cache write every single time and never once read.

Breakpoint placement: the one that costs money✗ breakpoint on a block that variestools + system18,000 tokens · identical every requestuser — "Today is 2026-09-12."user — the questionbreakpointrequest 1 write 18,040 read 0request 2 write 18,040 read 0request 50 write 18,040 read 01.25× on every request, forever.More expensive than no cache at all.never read✓ breakpoint on the last stable blocktools + system18,000 tokens · identical every requestuser — "Today is 2026-09-12."user — the questionbreakpointrequest 1 write 18,000 read 0request 2 write 0 read 18,000request 50 write 0 read 18,0001.25× once, then 0.1×.The stamp below the line costs nothing.never read
Left: a 1.25× surcharge on every request, forever — strictly worse than not caching. Right: the same request with the breakpoint moved up one block.

Surely the backward walk saves me here? No, and this is the crux of the whole mechanism. The walk looks for writes. Those earlier blocks are stable, but stability is not the criterion — no request ever placed a breakpoint there, so there is nothing to find. The fix is to move the breakpoint to the last block that is byte-identical across requests, not to hope the fallback covers you.

Automatic caching falls into exactly this trap when the prompt ends in unique per-request content, because it targets the last block. The signature in the usage fields is unmistakable once you know it: cache_creation_input_tokens on every request while cache_read_input_tokens never covers the shared prefix.

Move the breakpoint below and watch the arithmetic. The document here is 18,000 tokens; the timestamp that ruins it is forty.

Place the breakpointClick a block. Everything at or above it becomes the cached prefix.
cache_control
no caching$2.11
your breakpoint$0.341
best placement$0.341
Hit. The 21,000-token prefix is byte-identical across requests, so it is written once and read 19 times. This is the correct placement: the last block that does not vary.
usage on request 2 — cache_read_input_tokens: 21,000 · cache_creation_input_tokens: 0 · input_tokens: 100
total prompt = the sum of all three = 21,100

Two things in there are worth sitting with. The pathological placement is more expensive than no caching — not merely wasteful. And nothing in the response says so: no error, no warning, no field that turns red. Just a bill.

When “the last block” is exactly right

A growing conversation is the case automatic caching was built for. Earlier turns never change, so each request’s lookback finds the entry the previous request wrote a few positions back. The breakpoint rolls forward and the cached region ratchets.

The rolling breakpoint in an append-only conversationturn 1Su1▾ writeturn 2Su1a1u2▾ writereadturn 3Su1a1u2a2u3▾ writereadEach turn reads everything the last turn wrote and writes only what it added. Reads grow; writes stay small.
Reads grow turn over turn; writes stay the size of the last turn. This is what a healthy agent loop looks like in the usage fields.

That gives you the signature of a loop that is working, which is worth memorising because it is how you tell a real hit from a coincidence:

The catch is the twenty-position lookback. Each turn in the diagram adds two positions, so turn three easily reaches turn two’s write. An agent loop that appends a dozen positions per iteration — long sequential tool chains, many text or image blocks — will eventually push the breakpoint more than twenty positions past the last write, and then it falls off a cliff: full miss, full rewrite, byte-identical payloads, no warning. Place an intermediate breakpoint roughly every fifteen positions in long turns, so a write has accumulated closer in before you need it.

Four breakpoints, four clocks

The reason for multiple breakpoints is not granularity for its own sake. You place them at change-frequency boundaries, and four is enough because prompts rarely have more than four distinct rates of change:

tools            ← breakpoint 1    changes on deploy
system: rules    ← breakpoint 2    changes on deploy
system: docs     ← breakpoint 3    changes daily
last user turn   ← breakpoint 4    changes every turn

Swap the documents and breakpoints 1 and 2 still hit; only 3 and 4 are rewritten. That is the entire point of the segmentation, and it is why a RAG agent that reindexes nightly should never put its retrieved rows ahead of its house rules.

The robust default for an agent loop is a combination: one explicit breakpoint on the last block of the static system prefix — so the expensive shared part has a guaranteed read point that survives whatever happens later in messages — plus top-level automatic caching for the growing conversation tail.

The cascade, and the doors out of it

Because the prefix is ordered tools → system → messages, a change at one tier invalidates that tier and everything after it. Nothing before it. That single sentence is the whole table, but the table is worth having in front of you, because the consequences are asymmetric in ways that are easy to get backwards.

What does this change cost me?tools → system → messages. A change takes its own tier and everything after.
tools cachesurvives
system cacheinvalidated
messages cacheinvalidated

System renders after tools and before messages, so the whole conversation behind it is invalidated too. This is why a "current date:" line in the system prompt is fatal.

Escape hatch — Send the instruction as a {role:"system"} message appended to messages[] instead. Available today with no beta header on Claude Opus 5, Opus 4.8, Fable 5 and 5.1, and Mythos 5 and 5.1 — not Claude Sonnet 5. It is also the non-spoofable operator channel, which text inside a user turn is not.

Three of those rows now have escape hatches, and they are the most useful recent development in this whole area, because each one converts a prefix rewrite into an append.

The one to reach for first: stop editing the top-level system prompt mid-conversation. An operator instruction that arrives on turn nine — a mode switch, injected state, a policy change — used to mean rewriting the block that sits in front of the entire conversation. Send it as a message instead:

await client.messages.create({
  model: 'claude-opus-5',
  max_tokens: 16_000,
  system: [
{ type: 'text', text: STABLE_CORE,
  cache_control: { type: 'ephemeral' } },   // byte-identical, still hits
  ],
  messages: [
...history,
{ role: 'user', content: userMessage },
{ role: 'system',                            // ← after the cached prefix
  content: 'Terse mode enabled — keep responses under 40 words.' },
  ],
})

Available today with no beta header on Claude Opus 5, Opus 4.8, Fable 5 and 5.1, and Mythos 5 and 5.1 — not on Claude Sonnet 5, where it returns a 400 you should catch and fall back from. It has a second property worth as much as the caching one: a role: "system" message is a channel nothing else can write to, whereas operator instructions embedded as text inside a user turn can be forged by anything that reaches user input.

It must follow a user message (or an assistant message ending in server-tool use), must be either last in messages or followed by an assistant turn, and cannot be messages[0] — the initial prompt still belongs in top-level system.

What about the inject-a-reminder-then-delete-it pattern? It is a history edit, and it costs you the cache from that position on every single turn — plus, on Fable 5.1 and Mythos 5.1, it invalidates every later thinking block. Give the message clear_at: "next_user_message" instead (beta mid-conversation-system-clear-at-2026-08-21): it renders for one turn, then stays in the transcript cleared — costing no input tokens, still part of the prefix. Append a fresh copy each turn and never remove the old ones.

The row with no door is the model switch. Caches are model-scoped, so a cost cascade that routes easy turns to a cheaper model forfeits cache reuse across its lanes — each model is its own namespace. Measure the simpler alternative first: the better model at lower effort, on one cache.

One more that catches people building agents: a fork must reuse the parent’s exact prefix. Summarizers, compactors and sub-agents usually spin up a separate call, and if that call rebuilds system, tools or model with any difference at all, it misses the parent’s cache entirely. Copy them verbatim and append the fork-specific content at the end.