Zum Hauptinhalt springen

Mid-Conversation Tool Changes

Experte

Solange Claude Tool Use hat, war das tools-Array für die Lebensdauer einer Konversation eingefroren — oder, genauer, für die Lebensdauer eines Cache-Eintrags. Ändere es, und der Prompt-Cache schmilzt.

Das liegt daran, dass Prompt-Caching den Request-Präfix in fester Reihenfolge hasht: toolssystemmessages. Die Tool-Liste sitzt früher als alles andere, was du sendest. Füge ein Tool hinzu, benenne eine Beschreibung um, und jede gecachte Runde ab diesem Punkt misst daneben. In einer langen agentischen Session mit hunderttausenden gecachten Input-Tokens kostet dich diese „kleine Änderung“ echtes Geld und einen frischen mehrsekündigen Kaltstart.

Mid-Conversation-Tool-Changes sind das Tool-Array-Gegenstück zu Mid-Conversation-System-Messages. Du deklarierst das gesamte Universum der Tools weiterhin einmal, vorne, in tools. Aber du entscheidest jetzt, welche Teilmenge dem Modell tatsächlich in einer gegebenen Runde angeboten wird, indem du tool_addition- und tool_removal-Blöcke innerhalb einer role: "system"-Nachricht anhängst. Das tools-Array selbst ändert sich nie, sodass der gecachte Präfix byte-identisch bleibt.

What you'll learn
  • Warum das Bearbeiten von tools[] früher den ganzen Cache in die Luft gejagt hat, nicht nur den Tools-Abschnitt
  • Wie defer_loading, tool_addition und tool_removal Deklaration von Verfügbarkeit trennen
  • Die genauen Platzierungsregeln für die System-Nachricht, die diese Blöcke trägt (sie erben die Regeln von Mid-Conversation-System-Messages)
  • Wie man MCP-Tools einzeln (mcp_tool_reference) oder als ganzen Server (mcp_toolset_reference) referenziert
  • Wann diese Beta die Alternativen schlägt — Sub-Agenten mit eigenem tool_choice, per-Runde-Resend oder ein äußerer Router

★ Insight ───────────────────────────────────── Zwei Dinge machen dieses Feature still wichtig. Erstens, auf Opus 5 ist die minimale cacheable Prompt-Länge von 1.024 auf 512 Tokens gefallen, sodass auch kleinere Sessions vom Cache profitieren — was bedeutet, dass kleine Sessions jetzt auch leiden, wenn du ihn invalidierst. Zweitens, dass tools vor system im Hash sitzt, bedeutet: heute, wenn du mid-conversation-system-messages nutzt, um eine neue Anweisung einzuschmuggeln, zahlst du am Tag, an dem du ein neues Tool einführen willst, immer noch den vollen Preis. Diese Beta schließt das letzte Loch. ─────────────────────────────────────────────────

Das Cache-Hash-Problem in einem Bild

Der Cache-Key einer Anfrage ist ein Rolling Hash des Präfixes, in dieser Reihenfolge:

[ tools ][ system ][ messages…, bis zum Breakpoint ]

Ein Cache-Treffer verlangt, dass jedes Byte vor dem Breakpoint zu einer aktuellen Anfrage passt. Also:

Was du änderstWas noch Cache trifftWas du erneut zahlst
Neue user-Runde am Ende anhängenDer gesamte Präfix bis zu dieser RundeNur die neue Runde
Neue Mid-Conversation-system-Nachricht anhängenAlles davorDie neue System-Nachricht
Das Top-Level-system-Feld bearbeitenNur toolssystem + jede Nachricht
Ein neues Tool zu tools hinzufügenNichtssystem + jede Nachricht

Genau diese letzte Zeile ist die, die Mid-Conversation-Tool-Changes umschreiben.

Die drei beweglichen Teile

1. defer_loading: true — auf einer Tool-Deklaration in tools hält das das Tool deklariert, aber vom Modell zurückgehalten. Es wird weiter in den Cache-Präfix gehasht (genau das ist der Punkt), aber Claude sieht es nie als aufrufbar, bis du es freigibst.

2. tool_addition — ein Content-Block innerhalb einer role: "system"-Nachricht. Gibt ein defer_loading-Tool ab dieser Runde frei. Bietet auch ein Tool erneut an, das eine vorherige tool_removal zurückgezogen hatte.

3. tool_removal — der Spiegel. Zieht ein aktuell angebotenes Tool ab dieser Runde zurück. Jede folgende Runde trifft den Cache, aber das Tool ist nicht mehr in Claudes Auswahlmenge.

Sowohl tool_addition als auch tool_removal referenzieren ein Tool über ein tool-Feld. Drei Referenzformen sind zulässig:

  • {"type": "tool_reference", "name": "get_forecast"} — ein normales Tool, das in tools deklariert ist.
  • {"type": "mcp_tool_reference", "server_name": "linear", "name": "create_issue"} — ein einzelnes MCP-Connector-Tool.
  • {"type": "mcp_toolset_reference", "server_name": "linear"} — jedes Tool, das ein MCP-Server bereitstellt, in einem Block.

Einen Namen zu referenzieren, der nicht in tools deklariert ist, liefert einen 400.

Minimales Arbeitsbeispiel

Die Beta verlangt den Header mid-conversation-tool-changes-2026-07-01 und eines von Fable 5, Mythos 5, Opus 4.8 oder Opus 5. Unten: sowohl ein „Read“- als auch ein „Write“-Tool vorne deklarieren, delete_file zurückhalten und es erst freigeben, nachdem der User eine destruktive Absicht bestätigt hat.

import anthropic

client = anthropic.Anthropic()

TOOLS = [
{
"name": "read_file",
"description": "Eine Datei von der Platte lesen.",
"input_schema": {
"type": "object",
"properties": {"path": {"type": "string"}},
"required": ["path"],
},
},
{
# Deklariert, aber zurückgehalten. In den Cache-Präfix gehasht,
# damit wir es später freigeben können, ohne etwas zu invalidieren.
"name": "delete_file",
"description": "Eine Datei dauerhaft von der Platte löschen.",
"defer_loading": True,
"input_schema": {
"type": "object",
"properties": {"path": {"type": "string"}},
"required": ["path"],
},
},
]

messages = [
{"role": "user", "content": "Lies notes.md und fasse es zusammen."},
# ...mehrere tool_use / tool_result-Runden...
{"role": "user", "content": "OK, ich bestätige: lösche notes.md."},
# delete_file ab hier freigeben. Der gecachte Präfix
# (tools + alle früheren Runden) passt weiter byte-für-byte.
{
"role": "system",
"content": [
{
"type": "tool_addition",
"tool": {"type": "tool_reference", "name": "delete_file"},
}
],
},
]

response = client.beta.messages.create(
model="claude-opus-5",
max_tokens=1024,
betas=["mid-conversation-tool-changes-2026-07-01"],
cache_control={"type": "ephemeral"},
tools=TOOLS,
messages=messages,
)

Die nächste Anfrage wird:

  1. tools hashen (unverändert) → Cache-Treffer.
  2. Jede frühere Runde hashen (unverändert) → Cache-Treffer.
  3. Nur die neue User-Runde + der system-role tool_addition-Block sind neuer Input.

Vergleiche das mit dem „alten Weg“ — delete_file erst in diesem Moment in tools fallen zu lassen. Diese eine Mutation hätte den gesamten Präfix invalidiert.

Übernahme in einer agentischen Schleife

Guided walkthrough1 of 6
  1. Nimm Tools mit auf, die du später freigeben willst, setze aber defer_loading: true. Der Punkt ist, den Tools-Abschnitt des Cache-Präfixes jetzt einzufrieren.

Referenzmuster

Destruktive Tools zurückhalten, bis der User bestätigt

system:
<tool_addition tool={type: "tool_reference", name: "delete_project"}>

Nur nach einer User-Runde anhängen, in der der User Zerstörung explizit bestätigt hat.
Nie vor einer „was genau soll gelöscht werden?“-Runde platzieren.

Toolsets für eine Plan → Execute → Review-Schleife phasen

Phase 1 (Plan): sichtbare tools[] = { read_repo, search_web } — alles andere defer_loading: true.
Phase 2 (Execute): system-role tool_addition anhängen für { edit_file, run_tests }.
Phase 3 (Review): system-role tool_removal anhängen für { edit_file }, tool_addition für { post_review_comment }.

Das tools[]-Array ändert sich nie; nur die angebotene Menge. Cache über alle drei Phasen bewahrt.

MCP-Server nach einem Rate-Limit außer Dienst stellen

Auf 429 vom Linear-MCP-Connector anhängen:

system:
<tool_removal tool={type: "mcp_toolset_reference", server_name: "linear"}>

Ein Block zieht jedes Tool zurück, das dieser Server bereitgestellt hat. Erneut mit einem passenden tool_addition anbieten, sobald dein Backoff-Fenster abläuft.

Sandbox: einem Subagenten eine strikte Teilmenge geben

Wenn du einen Subagenten dispatched, erstelle KEINE neue Konversation mit einem kleineren tools[]. Verwende stattdessen dasselbe tools[] wieder (Cache-Treffer!) und öffne die Subagent-Runde mit einer system-role tool_removal für jede Capability, die dieser Subagent nicht anfassen soll. Die Eltern-Konversation kann sie beim Rücksprung mit einem passenden tool_addition wiederherstellen.

Platzierungsregeln (sie zählen — sehr)

Die role: "system"-Nachricht, die tool_addition / tool_removal-Blöcke trägt, ist eine reguläre Mid-Conversation-System-Nachricht und erbt ihre Platzierungsregeln:

  • Nie als erste. Eine system-Nachricht darf nicht der erste Eintrag in messages sein; deklariere das initiale Toolset im Top-Level-system-Feld und tools.
  • Muss auf eine User-Runde oder eine Server-Tool-Assistant-Runde folgen. Eine user-Nachricht, die tool_result-Blöcke trägt, zählt — genau das ist der Slot, um auf das gerade Zurückgegebene zu reagieren.
  • Muss vor einer Assistant-Runde stehen oder der letzte Eintrag sein.
  • Nie zwischen einem tool_use und seinem passenden tool_result. Das ist ein 400.

Aufeinanderfolgende system-Nachrichten sind zulässig und werden als ein Abschnitt behandelt. Du kannst tool_addition, tool_removal und einfache text-Blöcke im selben content-Array mischen.

Wie das mit Prompt-Caching zusammenspielt

  • Aktiviere Caching explizit. Ein cache_control-Feld irgendwo ist erforderlich; automatisches Caching auf oberster Ebene ist am einfachsten.
  • Cache den stabilen Präfix wie üblich — bis zum letzten Block, der sich zwischen Anfragen nicht ändert.
  • Weil die angehängte System-Nachricht nach dem gecachten Präfix kommt, ändert sie den Präfix-Hash nicht.
  • Sobald die System-Nachricht in der Konversation ist, wird sie stabile History und ist in der nächsten Runde cacheable.
  • Jedes Tool in tools, inklusive defer_loading: true-Tools, zählt in Richtung der minimalen cacheable Prompt-Länge — 512 Tokens auf Opus 5, 1.024 auf den meisten anderen Modellen.

★ Insight ───────────────────────────────────── Dieses Design drängt Agent-Autoren zu einer bestimmten Disziplin: die Ambition der Session vorne deklarieren und Runtime-Signale nutzen, um den Zugriff zu modulieren. Es ist näher daran, wie OS-Prozess-Capabilities modelliert werden (Capabilities, die du hast, vs. Capabilities, die du aktuell ausüben kannst), als daran, wie klassische Function-Calling-APIs geformt sind. Wenn du einen Agenten darauf aufbaust, wird „welche Tools hat dieser Agent?“ zu einer Frage mit zwei Antworten — dem deklarierten Universum und der angebotenen Teilmenge — und der Cache bleibt warm. ─────────────────────────────────────────────────

Was es nicht tut

  • Es erlaubt dir nicht, ein Tool einzuführen, das gar nicht in tools war. Jedes Tool, das das Modell je angeboten bekommen kann, muss ab der ersten Anfrage in tools existieren. Das ist ein Feature, keine Einschränkung — genau das hält den Hash stabil.
  • Es erlaubt dir nicht, ein input_schema oder eine description eines Tools mitten in der Konversation zu ändern. Beides ist eine Mutation von tools und triggert einen Cache-Miss. Wenn ein Tool-Schema sich entwickeln muss, deklariere zwei Tools mit unterschiedlichen Namen.
  • Es gilt heute nicht für Claude Sonnet 5. Sonnet 5 unterstützt Mid-Conversation-System-Messages überhaupt nicht, also kann diese Beta nicht darauf aufsetzen. Route Sonnet-Tier-Runden über einen äußeren Router, wenn du dort dynamische Toolsets brauchst.

Wie andere Provider dasselbe Problem angehen

ProviderDynamisches Toolset ohne volle Präfix-Neuverarbeitung?
Anthropic Claude Opus/Fable/MythosJa, über diese Beta.
Anthropic Claude Sonnet 5Nein — tools erneut senden (Cache-Miss) oder über einen äußeren Supervisor routen.
OpenAI GPT-5/6Praktisch nein. Ein Ändern des tools-Arrays in der Responses/Chat-Completions-API ist eine Präfix-Änderung; du verlässt dich auf das automatische Caching-Präfix-Match, das an der Tool-Liste bricht. Häufiger Workaround: Eltern/Kind-Agenten, wobei das Kind ein scoped tools-Array hat.
Google Gemini 3Ähnlich wie OpenAI. Die tools-Konfiguration ist Teil der Anfrage; das pragmatische Muster sind Function-Declaration-Sets pro Phase, mit Akzeptanz der Kosten der Neu-Deklaration.
MCP-Server allgemeinEinige Hosts (Claude Code, Cursor) implementieren „On-Demand-Tool-Loading“ innerhalb des Hosts, aber das ist Transport-Level: das darunterliegende Modell empfängt weiterhin eine erneut gesendete tools-Liste, bis diese Beta providerseitig landet.

Wenn du einen Cross-Model-Harness baust, strukturiere deinen Code so, dass das „dynamische Tool“-Verhalten eine pro Modell feature-detected Capability ist statt etwas, das du überall annimmst.

Häufige Fehlermodi

  • Du hast den Beta-Header vergessen. Die Anfrage wird akzeptiert, tool_addition / tool_removal-Blöcke werden als unbekannter Content in einer System-Nachricht behandelt, und das Verhalten ist undefiniert — oft wird der Block still ignoriert und Claude sieht das neue Tool nie.
  • Du hast die System-Nachricht zwischen tool_use und tool_result platziert. 400 invalid_request_error. Verschiebe sie hinter die folgende User-Runde, die das tool_result trägt.
  • Du hast ein Tool referenziert, das nicht in tools deklariert ist. 400. Deklariere es mit defer_loading: true und versuche es erneut.
  • Du hast die Tool-Beschreibung „nur zur Klarstellung“ bearbeitet. Cache-Miss für die gesamte Konversation. Um ein Tool mitten in der Session weiterzuentwickeln, füge ein v2-Tool unter einem neuen Namen hinzu und nutze tool_removal auf v1 und tool_addition auf v2.
  • Du bist auf Sonnet 5 und fragst dich, warum es nicht funktioniert. Es funktioniert nicht, auf Sonnet 5. Nutze eine andere Stufe oder einen äußeren Router.
Noch keine Karten — füge welche hinzu, um zu lernen. 🃏

Check yourself

0/5
  1. Warum invalidiert das Hinzufügen eines einzigen neuen Tools zu tools[] mitten in der Konversation jede gecachte Runde?
  2. Was macht defer_loading: true tatsächlich?
  3. Wo muss die role: system-Nachricht platziert sein, die tool_addition-Blöcke trägt?
  4. Was ist der richtige Weg, das input_schema eines Tools mitten in der Konversation ohne vollen Cache-Miss weiterzuentwickeln?
  5. Welches Claude-Modell unterstützt dieses Feature heute NICHT?

Quellen & weiterführende Lektüre