Skip to content
Skip to main content
A row of chunky metal toggle switches on an aged brass control panel, about half flipped up and glowing amber while the rest sit dark, representing tools being offered and withdrawn mid-conversation without rewiring the panel
8 min readBy Carlos Aragon

Claude Mid-Conversation Tool Changes: Keep the Cache

You can now add or withdraw a Claude tool partway through a session without editing the tools array — which means the prompt cache for the entire conversation survives. Declare every tool up front, then send tool_addition or tool_removal blocks inside a role: "system" message in your history. It's in beta behind the mid-conversation-tool-changes-2026-07-01header. The mechanism is simple; the placement rules are where everyone's first attempt returns a 400.

Why Touching tools Was So Expensive

Prompt caching hashes the request prefix in a fixed order: tools, then system, then messages. A cache hit needs that prefix to match a recent request byte for byte, up to your breakpoint.

Read that order again, because it's the whole story. Your tool definitions sit earlier than your system prompt. Change one description, drop one tool from the array, reorder two entries because your registry iterates a dict — and every byte after that point hashes differently. The cache misses for the tools, for the system prompt, and for all of the conversation you were counting on reading back cheaply.

That's why agent frameworks froze the tool list at session start. Not because nobody wanted dynamic tools — everybody wants dynamic tools — but because the workaround cost more than the problem. So people shipped agents carrying a delete_production_databasetool through a read-only session, and put "do not use this unless the user has confirmed" in the description, and hoped.

Mid-conversation tool changes remove the trade. The tools array never changes. What changes is which of those tools Claude is offered, and that instruction lives at the end of the message history, after your cache breakpoint, where it costs nothing to append.

How tool_addition and tool_removal Work

Both are content blocks inside a role: "system" message in the messages array, and they can be mixed with plain text blocks in the same message. The change applies from that point onward.

response = client.beta.messages.create(
    model="claude-opus-5",
    betas=["mid-conversation-tool-changes-2026-07-01"],
    tools=[...],                      # declared once, never edited
    messages=[
        {"role": "user", "content": "Ship it."},
        {"role": "system", "content": [
            {"type": "text",
             "text": "Deploy approval granted for this session."},
            {"type": "tool_addition",
             "tool": {"type": "tool_reference", "name": "deploy"}},
            {"type": "tool_removal",
             "tool": {"type": "tool_reference", "name": "dry_run"}},
        ]},
    ],
)

The tool field references a tool — it never defines one. Three reference types exist: tool_reference with a name for anything declared in tools; mcp_tool_reference with server_name plus name for one tool from an MCP connector; and mcp_toolset_reference with just server_name, which flips an entire MCP server on or off in one block.

That last one is the sleeper. If you've ever watched an agent get confused after you bolted on a fourth MCP server, you can now hand it one server per phase — GitHub during the code step, Sentry during the triage step — without re-sending a different tools payload and without paying for a cold prefix at every handoff.

Reference a name that isn't declared in toolsand you get a 400. There's no lazy registration here: if a tool might be needed at minute forty, it has to be in the array at minute zero.

defer_loading Quietly Got a Second Job

Default behavior: every tool in the array is offered to the model from turn one. The exception is a tool declared with defer_loading: true, which is withheld until a tool_addition block surfaces it. tool_addition also re-offers anything a previous tool_removal pulled.

So the flag now means two related things depending on what else is enabled. Under tool search, defer_loading means "Claude finds this itself when it needs it." Under mid-conversation tool changes, it means "Idecide when Claude sees this." Same flag, same payload behavior — the definition still ships on every request, it just doesn't enter the context window — and two completely different control models sitting on top of it.

In practice I default everything conditional to deferred and treat tool_additionas the grant. An agent that starts with six read tools and earns write access after a human approves is a much easier thing to reason about than one that starts with sixteen tools and a paragraph of pleading in the system prompt. It's also easier to audit: the grant is a discrete event sitting in the message history with a timestamp, not an inference about whether the model behaved.

The Placement Rules That Return a 400

This is where the first implementation always breaks, and the error message won't spell it out for you. A system message carrying content has to sit in a legal slot:

  • It must immediately follow a user turn — including a user turn made entirely of tool_result blocks — or an assistant turn ending in a server tool result.
  • It must precede an assistant turn or end the array. In an agentic loop that means: tool results in, system message with your tool change, then Claude's next turn.
  • It can never be the first entry in messages. Use the top-level system field for anything that applies from the start.
  • It cannot sit between a tool_use block and its tool_result. Answer the call first, then change the toolset.
  • Tool blocks are rejected right after a paused turn. An assistant turn ending in a server tool result accepts text blocks but not tool_addition or tool_removal. Resume the paused turn, then send the change on the next one.
  • Turn-scoped messages are text-only. A clear_at: "next_user_message" message (its own beta header, mid-conversation-system-clear-at-2026-08-21) 400s if you put tool blocks in it. Use a separate system message without clear_at.

One more that isn't a 400 but will cost you money: never edit or delete a system message you've already sent. Rewriting an earlier turn invalidates the cache from that point forward — and on Fable 5.1 it also invalidates the thinking blocks in every later assistant turn. If the instruction needs to evolve, append a new one. Consecutive system messages are accepted and treated as a single section.

What the Old Way Actually Costs

Plain arithmetic at Fable 5.1 list prices — $10 per million input tokens, $0.25 per million cache reads, and cache writes at 1.25× base, so $12.50 per million. Take a modest agent with a 40k-token cached prefix:

Per turn, 40k prefixStable toolsEdited tools
What happensCache readFull re-read + cache write
Cost$0.01$0.50
Across a 30-turn session~$0.30~$15.00

Fifty times the input bill, to hide one tool. That's the tax people were quietly paying, or more often refusing to pay by never changing tools at all. And it gets worse as the cache read discount gets better: the cheaper reads become, the larger the multiple between a hit and a miss. I walked through that ratio in detail in the Fable 5.1 cache-read breakdown — the short version is that a 75% cut to cache reads only shows up on your invoice if your cache is actually hitting.

Multiply by concurrency. Sixty sessions a day with one toolset swap each is the difference between rounding error and a line item you have to explain.

When to Reach for This, and When Tool Search Is the Right Answer

These two features look similar and solve genuinely different problems. The question to ask is who decides.

  • Your application decides → mid-conversation tool changes. Permission escalation after a human approves. Phase transitions in a pipeline. Killing a tool that has failed three times. Withdrawing write access when a budget threshold trips. These are facts your code observes, not things the model should discover.
  • The model decides → tool search. A 120-tool catalog where any given request needs four of them and you have no way to predict which four. Let Claude search.
  • Both, honestly. They read the same defer_loading flag and compose fine. Gate the dangerous tools with tool_addition, let search handle the long tail.
  • Neither, if your session is short. Under ten tools and a handful of turns, the cache math barely moves and you've added a beta header and a placement constraint for nothing.

The pattern I keep coming back to, and the one the tool-use fundamentals were always pointing at: stop describing constraints to the model and start enforcing them structurally. "Don't call this yet" in a description is a suggestion. A tool that isn't offered is not callable.

A Correction That's Already Circulating

Several write-ups of this release say the feature still costs you a cache miss on the request where the tool change happens, because "the tools array sits early in the hashed prefix." That sentence is true about the old approach and backwards about this one.

The point of the feature is that the tools array doesn't change. The addition and removal blocks live in messages, after your breakpoint. The prefix hash is identical, the cache hits, and only the new message is processed as fresh input. If you read a summary that tells you to "batch tool changes to limit cache invalidation," that advice is solving a problem this feature already deleted.

Worth checking against the primary doc and the prompt caching reference, both of which are explicit about the prefix order. And enable caching on purpose while you're in there — a mid-conversation system message doesn't create a cache entry by itself, so without cache_control there are no savings to preserve. Plenty of people are about to discover that their cache was never on. If that's you, start with where to put your breakpoints before you touch any of this.

Your Agent Probably Has a Cache That Never Hits

Every stack I audit has at least one of these: a tools array built from an unordered map, a system prompt with a timestamp in it, or history that gets rewritten on compaction. Any one of them means you pay full input price on every request and never notice. I'll find where your prefix diverges, fix it, and show you the cache-hit rate before and after.

Related Posts

AI Agents

Tool Search vs Programmatic Tool Calling in Claude

One cuts tool-definition tokens, the other cuts tool-result tokens. The decision rule, the 400s that surprise people, and the MCP restriction that makes the choice for you.

AI Agents

MCP vs CLI for AI Agents: The 4–32× Token Tax Nobody Warns You About

For most agent tasks a CLI is 4–32× cheaper than an MCP server — 1,365–8,750 tokens per task instead of 32,000–82,000 — because every connected MCP server injects all of its tool definitions into every turn, used or not. One Microsoft Intune test came out ~35× cheaper on the CLI (~4,150 vs ~145,000 tokens), and at 10k ops/month that's roughly $3.20 vs $55.20. The CLI was also more reliable in one 75-run benchmark (100% vs 72%, MCP's failures were mostly TCP timeouts on its persistent connection). Use a CLI when a mature one exists and you own the box; keep MCP for OAuth SaaS, multi-tenant per-user auth, governance/audit needs, and tools with no CLI. For high-volume fan-out, let the model write code that orchestrates the calls (programmatic tool calling / Code Mode) to cut tokens 98–99%. The best agents mix all three; measure tokens per completed task, not per call.

AI Agents

Claude Agent SDK vs LangGraph: The Real Cost

The "Agent SDK burns 25x more tokens" stat is true and nearly meaningless — about 33k of those 35k tokens are cache reads at 10% of the rate, so the real multiple is 4-5x, or cents per run. The decision that actually matters is durable execution vs an inherited harness. Where each one earns its place, the hybrid pattern most teams land on, and the one question that settles it.