●FORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtask●LIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its own●MCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS●PLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callback●SONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15●IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October●FORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtask●LIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its own●MCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MS●PLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callback●SONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15●IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October
Splitting Claude API prompt cache into 5m and 1h tiers — separate TTLs cut cost and stabilize ops
Anthropic's cache_control supports two TTLs: 5 minutes and 1 hour. Splitting them into a two-tier layout — 1h for static system/tools, 5m for variable few-shot — meaningfully changed both my costs and my on-call life. Here's the design with the numbers I observed.
The Messages API in the Anthropic SDK exposes a cache_control field that lets you ask the server to cache the prefix of a long prompt. I run Claude Lab as part of Dolice Labs (four sites I operate in parallel) and a handful of iOS / Android apps I have been shipping as an indie developer since 2014 (cumulative downloads passed 50 million along the way). Across all of that, the single biggest lever I have found for API cost is not "do you cache or not" but "how many TTLs do you use."
cache_control supports two TTLs: the default 5-minute ephemeral cache, and a 1-hour cache via ttl: "1h". I used to keep everything on 5m and assume that was fine. Six months ago I read the invoices line by line and realized I was leaving a lot on the table. The winning layout turned out to be 1h for static layers (system + tools) and 5m for variable layers (few-shot, RAG slices). This article is the engineering record of that change, with the actual numbers I saw.
Cache hits started appearing in usage.cache_read_input_tokens, so it looked like things were working. Two real problems took weeks of logs to surface.
First, every time traffic went quiet for more than five minutes — which happened a lot at night and on weekends — the full 4,500-token system prompt was re-written and re-billed at the write rate. The write rate is 1.25x the normal input rate for 5m TTL, so dozens of cold-starts per day quietly ate most of my cache savings.
Second, when I tried to add a variable few-shot block at the end of the prompt, my fixed prefix kept getting invalidated. That was my own misunderstanding of where to put the breakpoint, but the two-tier layout described below removes the temptation entirely.
When 1h TTL is actually worth it
Passing ttl: "1h" extends the cache window to one hour, but the write cost roughly doubles versus the 5m option. If you apply 1h indiscriminately, your bill goes up, not down.
The rule I use is simple:
write-cost increase < hit savings
↓
mean inter-request gap < 1h, with no extended idle windows
↓
1h TTL is worth it
For variable content — per-user few-shot, just-fetched RAG passages — 1h is meaningless. That content does not survive an hour and does not need to. Keep those layers on 5m.
In my workloads, the layers that actually benefit from 1h are:
The long system prompt (persona, output format, safety rails)
The tools JSON Schema block, especially when I have five or more tools defined
A shared knowledge "core" common across all users
Everything user-specific or daily-rotating stays on 5m.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦A concrete two-tier layout: 1h TTL for system/tools, 5m TTL for variable few-shot — with code
✦How to read cache_read_input_tokens vs. cache_creation_input_tokens to decide whether your TTL choice is correct
✦Numbers from my production workload (multiple iOS/Android apps and the four Dolice Labs sites) where this layout reduced input-token cost by about 42%
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
import anthropicclient = anthropic.Anthropic()def build_two_tier_request( system_prompt: str, tools: list[dict], fewshot_block: str, user_text: str,): """Build a Messages API request with a 1h+5m two-tier cache. L1/L2 are 1h; L3 is 5m. If L3 lapses overnight, L1/L2 still survive the next morning. """ system = [ { "type": "text", "text": system_prompt, "cache_control": {"type": "ephemeral", "ttl": "1h"}, # L1 } ] # L2: 1h breakpoint at the end of the tools array tools_with_cache = [dict(t) for t in tools] tools_with_cache[-1] = { **tools_with_cache[-1], "cache_control": {"type": "ephemeral", "ttl": "1h"}, } messages = [ { "role": "user", "content": [ { "type": "text", "text": fewshot_block, "cache_control": {"type": "ephemeral"}, # L3 (5m) }, {"type": "text", "text": user_text}, # L4: never cached ], } ] return system, tools_with_cache, messagessystem, tools, messages = build_two_tier_request( system_prompt=SYSTEM_PROMPT, tools=TOOLS, fewshot_block=current_fewshot(), user_text=user_input,)resp = client.messages.create( model="claude-sonnet-4-6", max_tokens=2048, system=system, tools=tools, messages=messages,)
Three pieces matter here. L1 and L2 must live on separate breakpoints, so swapping tools does not invalidate the system prompt. L3 sits on the user content array, not on system; placing it on system chains invalidations as the variable content grows. L4 must never carry cache_control; user input is unique every time, so caching it only inflates write cost.
Metrics — let numbers decide if your TTLs are right
I do not trust intuition on whether caching "is working." I look at three numbers from the usage block of every response:
def cache_efficiency_metrics(usage: dict) -> dict: """Per-request cache efficiency, suitable for daily aggregation.""" cache_read = usage.get("cache_read_input_tokens", 0) cache_create = usage.get("cache_creation_input_tokens", 0) plain_input = usage.get("input_tokens", 0) total_input = cache_read + cache_create + plain_input if total_input == 0: return {} return { # (1) hit ratio: share of input that came from cache "hit_ratio": cache_read / total_input, # (2) write ratio: writes per read "write_ratio": cache_create / max(cache_read, 1), # (3) plain ratio: input that bypassed the cache entirely "plain_ratio": plain_input / total_input, }
I read those three numbers like this:
If hit_ratio < 0.6, either the TTL or the breakpoint position is wrong. My chat-style workload sits at 0.72–0.85 once tuned.
If write_ratio > 0.3, you are writing too often. A 1h tier that does not lower write_ratio is a layer whose access pattern does not survive an hour. Put it back on 5m.
If plain_ratio > 0.5, you are not exploiting the cache at all. The most common cause I see is putting variable few-shot inside system instead of in the user message.
Aggregating those daily, the change to a two-tier layout moved my mean hit_ratio from 0.58 to 0.78 and dropped my monthly input-token bill by about 42%. Doubling the write cost on the 1h layer is a real number, but the read savings dwarf it.
Three cases where 1h TTL hurts
I have seen 1h fail in three specific shapes. All three came down to me misreading the access pattern.
The first is a service whose traffic goes to zero at night. If you regularly leave the cache idle for more than an hour, you pay the higher write rate without recovering it on the next call. I have one art-related companion app that quiets down between 03:00 and 05:00 JST; that path stays on 5m.
The second is a system prompt under heavy A/B testing. When a prompt registry bumps from v17 to v18, the cache key changes and the write fires. Keep 5m while you are iterating; switch to 1h after the prompt has been stable for a week.
The third is trying to cache the entire messages history. Putting 1h breakpoints inside long conversation history invalidates every layer the moment a user sends a new turn. I do not place explicit breakpoints inside conversation history at all; I let it fall off naturally.
A safe rollout order
I would not flip every layer to 1h on day one. The order I used was:
Add measurement first. Ship the three metrics above to whatever you use for dashboards. Watch for a week.
Put L3 (few-shot) on 5m first. Smallest blast radius. Easy to roll back.
Move L1 (system) to 1h.cache_creation_input_tokens will spike for the first few hours. If hit_ratio rises after 24 hours, you are good.
Move L2 (tools) to 1h. Big payoff if you have five or more tools defined.
Add a "force 5m" kill switch. Useful for A/B phases and bad days.
A 24–48 hour observation window between each step makes regressions easy to attribute.
Watch out — only four breakpoints per request
cache_control allows at most four breakpoints per request. The mistake I caught myself making was putting a breakpoint on every tool definition. The fifth tool I added silently disabled all my caching.
# Anti-pattern: a breakpoint per toolfor tool in tools: tool["cache_control"] = {"type": "ephemeral", "ttl": "1h"}
The correct pattern is to put a single breakpoint on the last tool definition; the cache key covers everything up to that point. Breakpoints partition the prefix; they do not multiply caches.
Ship the rollback before the change
Before I push a TTL change to production, I add an environment-variable kill switch that flattens everything back to 5m:
import osdef cache_ttl_for(layer: str) -> dict: """Env-var rollback to 5m for emergencies.""" if os.getenv("PROMPT_CACHE_FORCE_5M") == "1": return {"type": "ephemeral"} # 5m if layer in ("system", "tools"): return {"type": "ephemeral", "ttl": "1h"} return {"type": "ephemeral"} # 5m
This means a bad evening for hit_ratio is a one-variable rollback, not a deploy. From years of running AdMob since 2014, the design choices that pay back the most are not new features but the ones that save you 30 seconds of judgment at 2 AM. This is one of those.
What I learned after two months on the two-tier layout
Two things shifted in how I think about prompt design after running this for a couple of months.
First, the cache layout is the seam in the system design. Drawing the line between L1 and L3 forced me to be explicit about which content is shared across all users and which is per-request. That clarity helped the code, not just the bill.
Second, TTL is a third operational lever, alongside "edit the prompt" and "swap the model." Being able to dial TTL based on access pattern changed how I respond to cost incidents — sometimes the right answer is not a prompt rewrite but a TTL change.
Twelve years of indie development has trained me to care about server cost in a slightly anxious way. Prompt caching reframes that anxiety into a design problem with crisp controls, which I appreciate.
If you are about to try this
Start with the measurement step. Resist the urge to flip 1h on first; ship the three metrics and watch them for a week. Once you know your current hit_ratio and the time bands where it dips, the order of breakpoints becomes obvious.
In my own case the first week of measurement is what surfaced the dead overnight window I would have missed. A small investment in observability up front saves a lot of "is it working?" debate later. Thank you for reading — I hope this layout is useful in your own workload.
Share
Thank You for Reading
Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.