| name | prompt-caching-strategy |
| description | Minimize token cost and latency with Anthropic prompt caching — cache breakpoints, static vs dynamic block separation, skills snapshot pre-computation, context invalidation strategy, and 90% cost reduction patterns for repeated system content. Use when asked about "prompt caching", "cache breakpoint", "reduce token cost", "Anthropic caching", "cache_control", "reuse system prompt", "skills snapshot", "context invalidation", "token budget", "90% cache hit", "cost optimization for Claude", or "cached prompt tokens". Do NOT use for: general output budget — see the OUTPUT_BUDGET_POLICY.md. Do NOT use for: session memory compaction — see pre-compact-backup.
|
| origin | adapted:MIT © Anthropic/anthropic-cookbook |
| license | MIT © 2026 Vũ Văn Tâm |
| version | 1.0.0 |
| compatibility | Claude 3.5+ / Claude 4 via API. claude-haiku-4-5, claude-sonnet-4-6, claude-opus-4-7. |
When to Use
- Use when: sending the same large system prompt (skills, rules, docs) on every request
- Use when: RAG context contains a stable large document + small dynamic query
- Use when: building a multi-turn conversation where history is repeated each turn
- Do NOT use for: one-off single requests — caching overhead not worth it
- Do NOT use for: Claude Code sessions (caching is automatic in the IDE)
How Prompt Caching Works
Without cache:
Every request = full prompt tokens charged at input rate
1000 requests × 10K tokens = 10M tokens billed
With cache (cache hit ~90%):
First request = full input tokens (write to cache)
Subsequent = 10% input tokens (cache read) + 90% cached at ~0.1× cost
TTL: 5 minutes (resets each time cache is read)
Break-even: caching pays off after ~2 requests with the same prefix.
Cache Breakpoints (API)
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system=[
{
"type": "text",
"text": LARGE_STATIC_CONTENT,
"cache_control": {"type": "ephemeral"}
}
],
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": LARGE_STATIC_CONTEXT,
"cache_control": {"type": "ephemeral"}
},
{
"type": "text",
"text": user_query
}
]
}
]
)
usage = response.usage
print(f"Input tokens: {usage.input_tokens}")
print(f"Cache write tokens: {usage.cache_creation_input_tokens}")
print(f"Cache read tokens: {usage.cache_read_input_tokens}")
Skills Snapshot — Pre-compute for Caching
bash core/scripts/build-skills-snapshot.sh > .claude/skills-snapshot.md
with open('.claude/skills-snapshot.md') as f:
SKILLS_SNAPSHOT = f.read()
system = [
{
"type": "text",
"text": f"You have access to the following skills:\n\n{SKILLS_SNAPSHOT}",
"cache_control": {"type": "ephemeral"}
}
]
Static vs Dynamic Block Design
Cached (stable, changes rarely):
├─ System instructions / rules
├─ Skills index snapshot
├─ Codebase architecture overview
├─ Reference documentation (API docs, style guide)
└─ Conversation history up to N turns ago
NOT cached (changes every request):
├─ Current user message
├─ Latest N turns of conversation
├─ Real-time data (current time, live metrics)
└─ Per-request session context
def build_messages_with_cache(history: list[dict], new_message: str):
messages = []
for msg in history[:-2]:
messages.append({
**msg,
"content": [{
"type": "text",
"text": msg["content"],
"cache_control": {"type": "ephemeral"}
}]
})
for msg in history[-2:]:
messages.append(msg)
messages.append({"role": "user", "content": new_message})
return messages
TTL Management
Cache TTL: 5 minutes from last read
Implication: if request rate < 1 per 5min, cache provides no benefit
For background batch jobs (low frequency):
→ Cache not useful — use streaming or batch API instead
For interactive apps (>1 req/min):
→ Cache hits every request after first — 90% cost reduction
For scheduled jobs (hourly):
→ Warm cache manually before batch starts:
send a preflight "ping" request 30s before batch to re-warm
Cost Calculation
SONNET_INPUT_PRICE = 3.00
SONNET_CACHE_WRITE = 3.75
SONNET_CACHE_READ = 0.30
static_tokens = 14_000
dynamic_tokens = 500
requests = 100
no_cache_cost = requests * (static_tokens + dynamic_tokens) / 1e6 * SONNET_INPUT_PRICE
with_cache = (1 * static_tokens / 1e6 * SONNET_CACHE_WRITE +
(requests - 1) * static_tokens / 1e6 * SONNET_CACHE_READ +
requests * dynamic_tokens / 1e6 * SONNET_INPUT_PRICE)
print(f"Without cache: ${no_cache_cost:.2f}")
print(f"With cache: ${with_cache:.2f}")
Anti-Fake-Pass Rules
Before claiming prompt caching is optimized, you MUST show:
Reference: gates/anti-fake-pass-gate.md