CLAUDE LABJP
PRICE — Sonnet 5's launch promotion of $2/$10 per Mtok ends August 31, with standard pricing of $3/$15 per Mtok taking over on September 1TOKENS — Sonnet 5's revised tokenizer maps identical content to 1.0-1.35x more tokens, so the list-price change alone understates what your bill will actually doBOOST — The temporary 50% weekly usage increase for Claude Code subscribers now runs through August 19. It is an extension, not a permanent changeOSS — Claude for Open Source gives maintainers and contributors six months of Claude Max 20x at no cost, worth roughly $1,200OPUS5 — Claude Code v2.1.219 makes Opus 5 the default Opus model, bringing a 1M context window and a fast mode priced at $10/$50 per MtokNETLOCK — Two additions worth knowing: sandbox.network.strictAllowlist denies any host outside your allowlist, and a DirectoryAdded hook fires after /add-dirPRICE — Sonnet 5's launch promotion of $2/$10 per Mtok ends August 31, with standard pricing of $3/$15 per Mtok taking over on September 1TOKENS — Sonnet 5's revised tokenizer maps identical content to 1.0-1.35x more tokens, so the list-price change alone understates what your bill will actually doBOOST — The temporary 50% weekly usage increase for Claude Code subscribers now runs through August 19. It is an extension, not a permanent changeOSS — Claude for Open Source gives maintainers and contributors six months of Claude Max 20x at no cost, worth roughly $1,200OPUS5 — Claude Code v2.1.219 makes Opus 5 the default Opus model, bringing a 1M context window and a fast mode priced at $10/$50 per MtokNETLOCK — Two additions worth knowing: sandbox.network.strictAllowlist denies any host outside your allowlist, and a DirectoryAdded hook fires after /add-dir
Articles/API & SDK
API & SDK/2026-08-01Advanced

Swapping Tools Mid-Conversation Without Losing Your Prompt Cache

Tools can now be added and removed between turns, but the tool block sits at the very front of the cache prefix. I measured 12 mutation patterns with fingerprints and built a stable-core plus volatile-tail registry with a guard that refuses unsafe changes.

Claude API116Prompt Caching6Tool Use9Architecture3Cost Optimization9

Premium Article

I was trimming unused tools from a long-running session.

Twelve tools were still attached, several of which clearly would not be called again in the back half of the conversation. The beta for adding and removing tools between turns had landed, so I dropped four of them. Input tokens should have gone down.

They did not. They went up.

cache_read_input_tokens fell to zero, and the entire accumulated conversation history was being resent. What I thought was a savings had quietly invalidated the cache and made me pay for everything again.

The tool block sits at the very front

Prompt caching works on prefix matching. Everything from the start of the request up to the first divergence is eligible for a cache hit. Change one byte anywhere, and everything downstream misses.

The trouble is where tool definitions live in that ordering.

PositionBlockExpected churnBlast radius
1toolsShould be lowAll of system and messages
2systemLowAll of messages
3messages (history)Append-onlyLater turns
4messages (current)Every turnNone

The tool array has the widest blast radius in the whole request. It does not matter whether a hundred turns have piled up behind it — swap one tool and all hundred turns go with it.

The beta unlocking mid-conversation tool changes and those changes being cache-safe are two separate things. The API accepting the mutation does not mean the prefix survives it.

I had conflated the two. Reading the beta note, I took "prompt cache is maintained" to mean maintained across any change at all.

Measuring 12 mutations by fingerprint

Rather than reason about it, I measured it. Build a rolling hash chain over the tool array — one hash per prefix length — then compare the chains before and after a change.

# toolprefix.py — measure prefix fingerprints over a tool array
import hashlib
import json
 
def canon(obj):
    """Deterministic serialization: sorted keys, fixed separators."""
    return json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
 
def prefix_chain(tools):
    """Return the rolling hash after each successive tool."""
    chain, h = [], hashlib.sha256()
    for t in tools:
        h = h.copy()               # without copy(), later updates bleed backwards
        h.update(canon(t).encode())
        chain.append(h.hexdigest())
    return chain
 
def shared_prefix_len(a, b):
    """How many leading elements two hash chains agree on."""
    n = 0
    for x, y in zip(a, b):
        if x != y:
            break
        n += 1
    return n

That h.copy() matters. Without it you keep mutating a single hasher and every entry ends up as the final digest. I spent an evening staring at a meaningless 100% match before spotting it.

The baseline is five tools shaped like a real setup.

def mk(name, props):
    return {
        "name": name,
        "description": f"{name} tool",
        "input_schema": {
            "type": "object",
            "properties": props,
            "required": sorted(props.keys()),
        },
    }
 
BASE = [
    mk("search_docs", {"query": {"type": "string"}, "top_k": {"type": "integer"}}),
    mk("read_file",   {"path": {"type": "string"}}),
    mk("write_file",  {"path": {"type": "string"}, "body": {"type": "string"}}),
    mk("run_tests",   {"suite": {"type": "string"}}),
    mk("post_report", {"channel": {"type": "string"}, "text": {"type": "string"}}),
]
 
base_chain = prefix_chain(BASE)
for label, mutate in CASES:
    n = shared_prefix_len(base_chain, prefix_chain(mutate(BASE)))
    print(f"{label:<26} {n}/{len(BASE)}  {n / len(BASE):.0%}")

Here is what twelve patterns produced on my machine.

MutationShared prefixRetentionVerdict
Append one tool at the tail5/5100%Safe
Rebuild with identical content5/5100%Safe
Remove the last tool4/580%Partial
Edit the last tool's description4/580%Partial
Add an optional property to the last tool4/580%Partial
Insert a tool in the middle2/540%Partial
Remove a middle tool2/540%Partial
Rename a middle tool2/540%Partial
Swap the second and third tools1/520%Partial
Prepend one tool at the head0/50%Total loss
Remove the first tool0/50%Total loss
Edit the first tool's description0/50%Total loss

Two of twelve preserved the prefix completely. Average retention was 48%.

The "Partial" rows are the trap. A 4/5 prefix match is worth nothing unless a cache_control breakpoint sits exactly at that boundary. Cache granularity is the breakpoint-delimited segment, not the individual tool.

If your only breakpoint is at the end of the tool array — the common setup — then only the two "Safe" rows actually survive. The other ten collapse to zero whether they scored 80% or 20%.

That was precisely my failure. When I trimmed unused tools, post_report had been sitting in the middle of the array.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
12 tool mutations measured by prefix fingerprint: only 2 of 12 preserved the prefix completely, average retention 48%
36 logically identical tool definitions produced 36 distinct fingerprints under plain JSON serialization — a 100% false-invalidation rate
Splitting into stable core and volatile tail moved core retention from 1/40 turns to 40/40, with the guard implementation included
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

API & SDK2026-07-01
When Claude API Document Extraction Is Confidently Wrong — Field Notes on Catching Silent Errors with Invariants
In structured extraction from invoices and contracts, the real danger isn't a crash — it's a value that's silently wrong while the schema validates and confidence reads high. Field notes on invariants, two-pass extraction, and tracking field-level error rates.
API & SDK2026-06-22
Your Claude Files API Storage Is Quietly Filling Up — Dedup With a Content-Hash Ledger and Reap the Orphans
Use the Files API in an automated pipeline and the same file gets uploaded again and again while orphaned files pile up unnoticed. Here is a content-hash dedup ledger plus an orphan GC design, with working code.
API & SDK2026-06-22
When Your Claude API Cost Math Doesn't Match the Bill: Accounting for the Four Token Buckets
Turn on prompt caching and your homegrown cost tally drifts from the console bill. Here is how to weight the four token buckets the usage object returns and build a ledger you can reconcile.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →