Claude Certified Architect advanced 8 min read 6 flashcards

Prompt Caching Economics for Claude Agents

Caching is a prefix match, so one interpolated timestamp can make an entire agent uncacheable; the architecture decisions that matter are ordering and stability, not where you place the breakpoint markers.

A team adds cache_control to their agent's system prompt, deploys, and sees cache_read_input_tokens sitting at zero across thousands of requests. The markers are correct. The problem is three lines up, where the system prompt begins f"Current date: {datetime.now()}", and that one interpolation invalidates everything after it on every single request.

Everything about prompt caching follows from a single invariant: it is a prefix match, and any byte change anywhere in the prefix invalidates the cache from that point onward. The rendered order is toolssystemmessages, so tools sit at position zero and a tool-list change is the most destructive edit available.

Order by stability, not by readability

The design task is to sort content by how often it changes and lay it out in that order. Never changes first, per-session next, per-turn last, per-request eliminated. Concretely, that means the system prompt is frozen — no dates, no user names, no mode flags, no conditional sections — and everything dynamic moves into messages, where a change at turn five invalidates nothing before turn five. It means tools are serialised deterministically (sort by name) and held constant for the conversation's life. And it means a fork operation — a summariser, a sub-agent, a side call — must copy the parent's system, tools, and model verbatim, because reconstructing them with any difference misses the parent's cache entirely.

The invalidation hierarchy is worth knowing because it is narrower than most people fear:

Change Invalidates
Tool definitions, model switch Everything (tools, system, messages)
System prompt content System and messages
tool_choice, images, thinking toggle Messages only
Message content Messages only

So toggling tool_choice per request is fine. Adding one tool mid-conversation is not.

Two escape hatches worth knowing

The two most painful rows have workarounds, both of which move the change out of the top-level request and into the message array after the cached prefix. An operator instruction that arrives mid-conversation goes in as a {"role": "system", ...} message rather than as an edit to the top-level system field, which preserves the cached history and, unlike a <system-reminder> block inside a user turn, cannot be spoofed by untrusted input. On the tools side, tool search with defer_loading appends schemas rather than swapping the tool list, which is why it preserves the cache where a manual tool swap destroys it. A model switch has no escape hatch — caches are model-scoped — so keep the main loop on one model and delegate cheaper sub-tasks to a subagent.

The numbers, and the gotchas that hide behind them

Cache reads cost roughly 0.1x base input price; writes cost 1.25x for the five-minute TTL and 2x for the one-hour TTL. Break-even is therefore two requests on the short TTL (1.25 + 0.1 against 2.0) and three on the long one (2.0 + 0.2 against 3.0). The one-hour TTL is not strictly better; it survives traffic gaps and needs more reads to pay for itself.

Three behaviours surprise people. Minimum cacheable prefix varies by model and is not monotonic across generations — 512 tokens on the newest models, 1024 on several current ones, and as high as 4096 on others — and a prefix below the minimum silently fails to cache with no error. Each breakpoint looks back at most 20 content blocks to find a prior entry, so an agentic turn that appends thirty tool_use and tool_result blocks overshoots the window and misses; place an intermediate breakpoint every fifteen blocks or so in long turns. And a cache entry is readable only once the first response begins streaming, so firing N identical requests in parallel means all N pay full price; send one, wait for first token, then fan out.

When it breaks

  • input_tokens is the uncached remainder, not the prompt size. Total prompt = input_tokens + cache_creation_input_tokens + cache_read_input_tokens. An agent showing 4K input tokens after hours of work is a cache working, not a small prompt.
  • Non-deterministic serialisation is the silent killer. json.dumps without sort_keys, iteration over a set, a per-request UUID — each produces bytes that differ run to run and each defeats caching completely with no visible symptom.
  • Per-user content in the prefix eliminates sharing. Interpolating a tenant name into the system prompt gives every tenant a private prefix and multiplies cache writes by your tenant count.
  • Context editing and compaction fight caching by construction. Both rewrite the prefix; both are still usually worth it near the window limit, but the interaction should be measured rather than assumed.
  • Pre-warming is not free and is often unnecessary. A max_tokens: 0 request writes the cache ahead of traffic, which helps only when first-request latency is user-visible and there is idle time before traffic arrives. Under continuous traffic the first real request warms it anyway.
Check yourself

6 flashcards for this concept

Click a card to reveal the answer.

Drill the whole track