Placing cache_control Breakpoints on System Prompts and Tools
A cache_control breakpoint only caches what comes before it, in the exact order Claude assembles the request: tools first, then the system prompt, then the messages array.
Search across all documentation pages
A cache_control breakpoint only caches what comes before it, in the exact order Claude assembles the request: tools first, then the system prompt, then the messages array.
Get that ordering wrong and breakpoints either cache too little to matter, or they get invalidated by content that should never have been in the cached prefix.
This page walks through where to put breakpoints, why the order matters, and how to layer more than one for finer-grained caching.
Every Claude API request has three logical sections that participate in the cached prefix, always in the same order: tools, then system, then messages.
A breakpoint marks the end of the content you want cached, so a breakpoint on the last tool caches only tools, while a breakpoint on the system prompt caches tools plus system together.
Content placed after the final breakpoint - almost always the tail of messages - is never cached and is reprocessed on every call.
Getting this right means putting your most stable content first and your most volatile content last, then placing breakpoints at the natural seams between them.
This page shows the request shape, the ordering rules, and worked examples with one breakpoint and with two layered breakpoints.
Quick-reference recipe card - copy-paste ready.
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=500,
tools=[
{
"name": "search_docs",
"description": "Search the internal knowledge base for a topic.",
"input_schema": {
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"],
},
"cache_control": {"type": "ephemeral"},
}
],
system=[
{
"type": "text",
"text": "You are Acme Corp's support assistant. Follow the tone guide below.\n" + ("...tone guide text..." * 100),
"cache_control": {"type": "ephemeral"},
}
],
messages=[{"role": "user", "content": "What's your refund window?"}],
)When to reach for this:
cache_read_input_tokens and suspect breakpoint placement, not content drift.import anthropic
client = anthropic.Anthropic()
REFERENCE_DOCS = "Product catalog and return policy details...\n" * 400
TOOL_DESCRIPTION = "Look up a customer's order by order ID." * 15
def build_request(user_question: str) -> dict:
"""Build a request with two independent cache breakpoints:
one after the tool catalog, one after the system prompt.
"""
return {
"model": "claude-sonnet-5",
"max_tokens": 500,
"tools": [
{
"name": "lookup_order",
"description": TOOL_DESCRIPTION,
"input_schema": {
"type": "object",
"properties": {"order_id": {"type": "string"}},
"required": ["order_id"],
},
# Breakpoint 1: caches the tool catalog on its own.
"cache_control": {"type": "ephemeral"},
}
],
"system": [
{
"type": "text",
"text": (
"You are Acme Corp's support assistant.\n"
"Reference material:\n" + REFERENCE_DOCS
),
# Breakpoint 2: caches tools + system together.
"cache_control": {"type": "ephemeral"},
}
],
# Everything here is per-request and never cached.
"messages": [{"role": "user", "content": user_question}],
}
client = anthropic.Anthropic()
r1 = client.messages.create(**build_request("What's your refund window?"))
print("call 1:", r1.usage.cache_creation_input_tokens, r1.usage.cache_read_input_tokens)
r2 = client.messages.create(**build_request("Can I track order #4471?"))
print("call 2:", r2.usage.cache_creation_input_tokens, r2.usage.cache_read_input_tokens)What this demonstrates:
tools, system, and messages are built by a single function so the cached content is guaranteed byte-identical across calls.r1 and r2, and it correctly sits after both breakpoints, in messages.cache_read_input_tokens nonzero because the tools+system prefix matched exactly.tools, then system, then messages, regardless of the order the keyword arguments appear in your Python call.cache_control block placed on the last element of tools caches everything in tools up to and including that element.cache_control block placed on a system content block caches tools (if cached) plus system up to that point, as one combined segment.messages - is reprocessed every time and never contributes to cache_read_input_tokens.| Breakpoint location | Caches | Use when |
|---|---|---|
Last tool in tools | Tool schemas only | Tool catalog is large/stable but system prompt changes per user or session |
End of system text block | Tools (if marked) + system prompt | System prompt is large and stable; typical default choice |
| Not used | Nothing | Prompt is short, or every part changes every call |
# system can be a plain string OR a list of content blocks.
# cache_control is only settable when system is the LIST form.
# Will NOT cache - system is a bare string, no cache_control possible:
system = "You are a helpful assistant."
# WILL cache - system is a list of one content block carrying cache_control:
system = [
{"type": "text", "text": "You are a helpful assistant.", "cache_control": {"type": "ephemeral"}}
]| Parameter | Type | Description |
|---|---|---|
cache_control | dict | Marks the end of a cacheable segment; {"type": "ephemeral"} or {"type": "ephemeral", "ttl": "1h"}. |
tools[i].cache_control | dict | Placed on a tool definition; caches that tool and everything before it in the list. |
system[i].cache_control | dict | Placed on a system content block; caches tools (if marked) plus system up to that block. |
cache_control on a bare string system. A plain string system="..." has no place to attach cache_control. Fix: always use the list-of-content-blocks form of system when you want caching.messages= before tools= in your function call has no effect on the request's logical order. Fix: remember Claude always evaluates tools -> system -> messages, regardless of keyword order in your code.messages, after the breakpoint.cache_control only needs to go on the final tool in the list - it caches everything from the start of tools through that entry. Fix: mark one breakpoint per segment you want, not one per item.| Alternative | Use When | Don't Use When |
|---|---|---|
| Single breakpoint at end of system prompt | Simple prompts, one stable block | Tools and system prompt change independently and often |
| Two breakpoints (tools + system) | Tools and system have different change frequencies | The extra bookkeeping isn't worth it for a short-lived script |
| No cache_control at all | Prompt is short or already cheap | The prompt or tool catalog is large and reused across many calls |
Tools first, then the system prompt, then the messages array - always in that order, independent of how you order keyword arguments in Python.
No. system must be the list-of-content-blocks form for cache_control to have anywhere to attach. A bare string system prompt cannot be marked as cacheable.
No, one breakpoint on the last tool you want included is enough - it covers everything in the list up to and including that entry.
It's processed fresh on every request and never contributes to a cache hit. This is almost always the current turn's content in messages, which is exactly where per-request content belongs.
To get independent caching for content that changes at different rates - e.g. a stable tool catalog and a system prompt that's tweaked more often. Two breakpoints let a change to one still hit cache on the other.
Yes, because tools come before the system prompt in the prefix. If your tool schema changes, the segment covering "tools + system" is invalidated even if the system text itself is unchanged.
No - anything that varies per user or per request should go in messages, after the breakpoint. Putting it in the cached system block invalidates the cache on every call.
No. Whether you write tools=..., system=..., messages=... or a different argument order in your function call, Claude still evaluates the request in the fixed tools -> system -> messages order.
More than two is technically possible but rarely useful in practice; two (tools, then system) covers the vast majority of real caching needs without added complexity.
Rebuilding cached content (system text, tool schemas) as inline literals scattered through the codebase instead of from a single shared constant, which lets small, accidental differences creep in between calls.
Stack versions: Written against the Claude model lineup current as of ~June 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (the default), and Claude Haiku 4.5 - and the official
anthropicPython SDK (latest 0.x release). Model names, SDK versions, and pricing move quickly - verify current specifics at platform.claude.com/docs before relying on them.
Reviewed by Chris St. John·Last updated Jul 19, 2026