# Claude API Mid-Conversation Tool Changes: Fix the Cache Bug

> Source: <https://byteiota.com/claude-api-mid-conversation-tool-changes/>
> Published: 2026-08-19 09:14:13+00:00

If you build long-running Claude agents with more than a handful of tools, you are probably bleeding money on cache misses you have not diagnosed yet. The `tools`

array is hashed before your system prompt, before your messages — it sits at the very front of Claude’s prompt prefix. Modify it mid-session to change which tools are available, and you invalidate the cache for the entire conversation. Anthropic shipped a beta with [Claude Opus 5](https://www.anthropic.com/news/claude-opus-5) that fixes this: mid-conversation tool changes. The `tools`

array stays frozen. You add or remove availability using system message blocks instead.

## Why Your Tool List Was Breaking the Cache

Claude’s prompt caching hashes the request prefix in order: `tools`

first, then `system`

, then `messages`

. A cache hit requires that prefix to match the previous request byte-for-byte up to the breakpoint. Editing the `tools`

array — even appending one new entry — changes the very front of the hash, and everything downstream misses.

For production agents, this is not a theoretical concern. A typical MCP-connected agent carries 3,000 to 8,000 tokens of tool schemas on every request. A long-running session with 30 turns and 6,000 tool schema tokens, without caching, pays for those schemas 30 times. With caching intact, you pay once to write the cache and then 10% of input price on every subsequent read. The difference on Claude Opus 5 ($5.00 per million input tokens, $0.50 per million on cache hits) works out to roughly 87% savings on tool schema tokens alone — per session.

Teams who noticed high `cache_creation_input_tokens`

and low `cache_read_input_tokens`

in their usage logs were often hitting this exact problem: something was changing the tool list mid-session, collapsing the cache on every turn.

## What Mid-Conversation Tool Changes Actually Do

The beta — enabled with the header `mid-conversation-tool-changes-2026-07-01`

— lets you declare your full tool set in `tools`

once, at the start, and never touch it again. From there, you control tool availability using `tool_addition`

and `tool_removal`

content blocks inside `role: "system"`

messages placed at the relevant point in the conversation. The blocks reference tools by name rather than redefining them, so the frozen `tools`

array stays byte-identical across turns and the cache keeps hitting.

This is available on Claude Fable 5, Mythos 5, Opus 4.8, and Opus 5, across the Claude API, Amazon Bedrock, and Google Cloud. One notable exception: Claude Sonnet 5 does not support this beta. If your production agent runs on Sonnet 5, you are waiting.

## How to Implement It

Three changes to your existing code:

- Add the beta header to your request
- Declare every tool your agent might ever use in
`tools`

at the start — do not modify this array - Use
`tool_removal`

or`tool_addition`

blocks in system messages to change what Claude can see

Here is a minimal example in Python:

```
client = anthropic.Anthropic()

response = client.beta.messages.create(
    model="claude-opus-5",
    max_tokens=1024,
    betas=["mid-conversation-tool-changes-2026-07-01"],
    # Declare all tools upfront. This array never changes.
    # The cache prefix stays byte-identical across turns.
    tools=[
        {
            "name": "read_file",
            "description": "Read file contents.",
            "input_schema": {
                "type": "object",
                "properties": {"path": {"type": "string"}},
                "required": ["path"],
            },
        },
        {
            "name": "write_file",
            "description": "Write content to a file.",
            "input_schema": {
                "type": "object",
                "properties": {
                    "path": {"type": "string"},
                    "content": {"type": "string"},
                },
                "required": ["path", "content"],
            },
        },
    ],
    messages=[
        {"role": "user", "content": "Review main.py for issues."},
        # ... (assistant turn with tool use, user turn with tool results) ...
        # After the review phase, withdraw write access.
        # This references the tool by name — no cache invalidation.
        {
            "role": "system",
            "content": [
                {
                    "type": "tool_removal",
                    "tool": {"type": "tool_reference", "name": "write_file"},
                },
            ],
        },
    ],
)
```

Note the placement constraint: a system message must immediately follow a user turn (or an assistant turn ending in a server tool result). Placing it between a `tool_use`

block and its `tool_result`

returns a 400 error. In an agentic loop, insert it after the user message that delivers tool results — that is the natural seam.

## defer_loading: The Companion Feature Worth Using

Declare a tool with `defer_loading: true`

and it is hidden from Claude until a `tool_addition`

block explicitly surfaces it. This pairs well with mid-conversation tool changes for agents with large, specialized tool libraries.

A practical pattern: a coding agent starts with read-only tools (`read_file`

, `search_code`

, `run_tests`

). After a review pass completes, a `tool_addition`

block surfaces the write tools (`write_file`

, `apply_patch`

). The full set is declared upfront — the cache prefix is stable — but Claude only sees the subset relevant to the current phase. You get dynamic capability management without paying a cache penalty for it.

## When This Is Worth the Complexity

Mid-conversation tool changes are not a one-size fix. Short sessions, static tool sets, or small tool inventories will see minimal benefit and added code complexity. The calculus changes when:

- Sessions run 10 or more turns
- Your tool set carries more than 3,000 tokens of schemas
- Tool availability legitimately changes mid-session (mode switches, capability unlocking, security revocation)

If none of those apply, stick with a static `tools`

array. The feature exists for the class of production agents where these conditions are true — and at scale with Opus 5 pricing, the savings are not marginal.

## Placement Rules and Caching Gotchas

System messages have strict placement requirements that will bite you if you ignore them. They must follow a user turn or a tool-result-bearing user turn. They cannot be the first message. And once sent, do not edit or remove them — rewriting a system message already in the cached history invalidates everything after it. If an instruction needs to change, append a new system message rather than modifying the existing one. Later system messages take precedence over earlier ones.

Anthropic’s [cache diagnostics tool](https://platform.claude.com/docs/en/build-with-claude/cache-diagnostics) is useful here: it identifies exactly where two requests diverged when an expected cache hit does not happen. Pair it with the [prompt caching docs](https://platform.claude.com/docs/en/build-with-claude/prompt-caching) to verify your breakpoints are placed correctly before blaming the tool change logic.

## Bottom Line

Mid-conversation tool changes is a narrow feature with outsized impact for a specific class of production agents. If you are running multi-phase or long-running Claude Opus 5 sessions with dynamic tool requirements, the combination of a frozen `tools`

array, `tool_removal`

and `tool_addition`

blocks, and `defer_loading`

is the architecture you want. The cache math makes the investment clear.

The Sonnet 5 exclusion is the only real friction — if Anthropic extends this beta to Sonnet 5, it becomes the default pattern for production Claude agents. Until then, this belongs in any Opus 5 agent that changes its tool set mid-session.

Beta header: `mid-conversation-tool-changes-2026-07-01`

. Full docs at [platform.claude.com](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages).
