Claude's 20-block cache lookback silently kills agent loops — Anthropic's Claude prompt caching silently fails in agent loops when the conversation exceeds 20 content blocks between cache breakpoints, causing cache reads to drop to zero and triggering full-prefix cache writes at 1.25× the read price — a 12× cost swing per turn on Opus 5 ($5/MTok input), according to a technical analysis. The issue arises because each agentic turn with 8 parallel tool calls emits 18 blocks, so two turns push the trailing breakpoint past the 20-block lookback limit, and the article proposes rolling breakpoints with a stride of 15 blocks to maintain cache hits. Claude's 20-block cache lookback silently kills agent loops — cache read input tokens at 40K and climbing. Twelve tool calls later, reads drop to zero and cache creation input tokens spikes to the full conversation length — every single turn. Prompt didn't change. No timestamp, no reordered tools, no model switch. The prefix is byte-identical.You just hit the 20-block lookback ceiling. It's the single most expensive gotcha in Claude /en/tags/claude/ prompt caching that nobody puts in their retro. Why the lookup fails mid-loop Cache lookup is bounded. A cache control breakpoint walks backward at most 20 content blocks hunting for an existing entry. One agentic turn with 8 parallel tool calls emits: - Assistant: 1 thinking + 1 text + 8 tool use = 10 blocks - User: 8 tool result = 8 blocks Total: 18 blocks in one round trip Two turns and your single trailing breakpoint sits 36 blocks past the last cached point. Silent miss — no error, no warning field, just cache read input tokens: 0 and a full-prefix cache-write charge at 1.25× the read price. On Opus 5 $5/MTok input that's ~$0.50/MTok read vs ~$6.25/MTok write. A 12× swing per turn triggered by a config detail you never set.Chat apps never see this — one user turn = one block, one assistant turn = one block. You'd need ten round trips to move 20 blocks. Agent loops are a different beast entirely. Tiered invalidation — what actually busts the cache | Trigger | Cache impact | |---------|--------------| | tool choice change, images added/removed, thinking toggle | Preserves tools+system cache | | Tool definitions change, model switch | Full rebuild | | System prompt replaced mid-run | Nukes everything downstream | Workaround for system-prompt edits Opus 5, Opus 4.8, Fable 5 only : append {"role": "system", ...} to messages instead of replacing the top-level system field. Sonnet 5 doesn't support this. Rolling breakpoints — the pattern that holds Stop putting one marker on the last block. Rotate a small set through the message list at a stride shorter than the lookback window. Every breakpoint is both a write point and a read point, so a trailing chain means each new request always finds a prior entry within 20 blocks. Budget: 4 breakpoints per request total tools + system + messages . Spend one on the last system block — it caches tools and system together since render order is tools → system → messages . Rotate the remaining three. CACHEABLE = {"text", "image", "tool use", "tool result", "document"} STRIDE = 15 blocks apart, safely under the 20-block lookback def add rolling breakpoints messages: list dict , stride: int = STRIDE - None: """Mutates plain-dict messages in place. Round-trip SDK objects with .model dump first — you cannot set cache control on a response object.""" flat = for msg in messages: for block in msg.get "content", : if block.get "type" in CACHEABLE: flat.append msg, block clear existing for , block in flat: block.pop "cache control", None place markers every STRIDE blocks from the end for i, , block in enumerate reversed flat : if i % stride == 0: block "cache control" = {"type": "ephemeral"} usage each turn: add rolling breakpoints messages response = client.messages.create model="claude-opus-4-20250514", messages=messages, ... The system+tools breakpoint stays fixed. The three message breakpoints leapfrog: newest turn gets one, the two prior turns keep theirs. Next turn, the oldest drops off, the new one lands — always three live markers spaced ≤15 blocks apart. Lookup never misses. Dashboard trap input tokens in the usage block is uncached remainder only . Total prompt size = input tokens + cache creation input tokens + cache read input tokens . Grafana panels graphing input tokens alone show a flat line while you burn cache writes at 12× the read cost. Add the other two series or you're flying blind. Bottom line: the 20-block limit isn't documented in the quickstart. If you run multi-tool agent loops on Claude, rolling breakpoints every ~15 blocks is the difference between a $0.50/MTok read path and a $6.25/MTok rewrite every turn. Ship the snippet, watch cache read stay non-zero, and stop lighting money on fire. Next Qwen 3.5 4B actually runs a full agent loop on iPhone 15 Pro → /en/threads/7180/