The short version: you can now append a {"role": "system"} message inside the messages array partway through a Claude conversation, instead of editing the top-level system field. The instruction still carries operator-level authority — it outranks the user's turns — but the cached prefix that came before it stays byte-identical, so your next request still hits the prompt cache. Anthropic shipped this to general availability with no beta header on the Claude API, Amazon Bedrock, and Google Cloud (Vertex), for Claude Fable 5, Claude Mythos 5, and Claude Opus 4.8. It is not on Claude Sonnet 5.
If you run long agentic sessions, that one change is a direct token-cost win. Here's why, and how to use it without tripping the 400.
The problem it fixes#
System instructions normally live in the top-level system field, ahead of every message. That's the right home for anything that should hold from turn one — and it's great for prompt caching, because the system prompt is part of the stable prefix every later turn reads from cache.
It's a terrible place for an instruction you only discover you need on turn 40. Prompt caching hashes the request prefix in order — tools, then system, then messages — and a cache hit needs that prefix to match a recent request byte for byte. The top-level system field sits near the very start of that hash. Append a single sentence to it and you get a different hash: the request misses the cache for the system prompt and every message after it. On a long session, you just re-paid full input price for the entire history to add one rule.
How it works#
Add the instruction as a message with "role": "system" at the end of the message history. Everything before it is unchanged, so the existing cache entry still matches and only the new message is processed as fresh input. content takes a plain string or content blocks, exactly like a user or assistant turn.
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
cache_control={"type": "ephemeral"}, # automatic caching
system="You are a code review assistant. Be concise.",
messages=[
{"role": "user", "content": "Review process() in utils.py for perf."},
{"role": "assistant", "content": "The list comprehension is fine for small inputs; use a generator for large ones."},
{"role": "user", "content": "Now review the calling code."},
# Discovered mid-session. Appending here keeps earlier turns
# byte-identical, so the prefix cached last request still hits.
{"role": "system", "content": "From now on, every suggestion must include explicit type annotations."},
],
)
When instructions conflict, later system messages beat earlier ones, and a mid-conversation system message beats the top-level system field for the turns that follow it. You still set the top-level field for anything that should apply from the start.
The one placement rule that returns a 400#
This is the part that bites people. A system message:
- cannot be the first entry in
messages(use the top-levelsystemfield for turn-one instructions); - must immediately follow a
userturn — auserturn carryingtool_resultblocks counts — or an assistant turn ending in a server tool result; - must either end the array or be immediately followed by an
assistantturn.
It cannot sit between an assistant tool_use block and the tool_result that answers it. Any other position is a 400.
The pattern that actually matters: agentic loops#
In a tool-use loop, the natural slot is right after the user message that delivers the tool results, before Claude's next turn. That's also the clean way to fold in a message the user typed while Claude was still working — instead of restarting the turn:
[
{ "role": "user", "content": "Run the test suite and fix any failures." },
{ "role": "assistant", "content": [{ "type": "tool_use", "id": "toolu_01", "name": "run_tests", "input": {} }] },
{ "role": "user", "content": [{ "type": "tool_result", "tool_use_id": "toolu_01", "content": "12 passed, 0 failed" }] },
{ "role": "system", "content": "New input arrived from the user: also update the changelog before you finish." }
]
Phrase it as context, not a command that overrides the user — state the fact ("the remaining token budget is now 8k", "the user toggled auto-approve off") and let Claude act on it. Claude is trained to resist instructions that work against the user, and that protection applies to the system role too, so "ignore what the user said" lands worse than simply saying what changed.
The security line you don't cross#
Claude treats system content as operator instructions and follows it. That's the whole point — and the whole risk. Never drop raw tool output, retrieved documents, or web content into a system message; doing so hands that text operator-level authority, which is exactly what a prompt injection wants. Keep third-party data in tool_result blocks. Reserve the system role for facts you are asserting.
What to do this week#
If you run agents on Fable 5, Mythos 5, or Opus 4.8: audit every place you currently rebuild the top-level system field mid-session — mode switches, freshness notes, budget warnings, per-turn policy. Each one is a cache miss you can convert into a cache hit by appending a system message instead. Turn on caching explicitly first (a mid-conversation system message doesn't cache anything on its own — see how to cut your Claude API bill with prompt caching), then move the breakpoint past the new message on the following turn so it, too, reads from cache.
On Sonnet 5, none of this applies yet — stick with the top-level field.



