cd /news/artificial-intelligence/claude-s-20-block-cache-lookback-sil… · home topics artificial-intelligence article
[ARTICLE · art-106210] src=promptcube3.com ↗ pub= topic=artificial-intelligence verified=true sentiment=· neutral

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.

read3 min views1 publishedAug 21, 2026
Claude's 20-block cache lookback silently kills agent loops —
Image: Promptcube3 (auto-discovered)

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 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))

    for _, block in flat:
        block.pop("cache_control", None)

    for i, (_, block) in enumerate(reversed(flat)):
        if i % stride == 0:
            block["cache_control"] = {"type": "ephemeral"}

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 →

── more in #artificial-intelligence 4 stories · sorted by recency
── more on @anthropic 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/claude-s-20-block-ca…] indexed:0 read:3min 2026-08-21 ·