The two sides of this change are more than 400 edits apart, too far apart to line up, so this is the differ's own diff of it and the words inside a line are not marked.
from line 1
11---
22title: Preserved thinking
33url: https://platform.claude.com/docs/en/build-with-claude/preserved-thinking
4description: Modifying a conversation now results in an error or a dropped block; how to check whether your integration does that and how to migrate.
4description: Preserved thinking lets a model use a thinking block from an earlier turn only if that model or an earlier one produced it and nothing before the block has changed.
55---
66
7On Claude Fable 5.1, changing prior turns in the conversation (the `system` prompt, the `tools`, or any earlier message) affects the API response. By default, it makes the API reject the request with an error, unless you opt to have the affected thinking blocks dropped from what the model sees instead (`prefix_mismatch_behavior: "drop_block"`). The check is enforced by default for new accounts created on or after August 31, 2026, 00:00 UTC. There are more details in *[How it works](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#how-it-works)* and *[Who is affected](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#who-is-affected).*
8
9When you send a block back, the API uses its `signature` to check that the prior conversation is unchanged and that the current model can read the block. The check exists so that reasoning produced under one set of instructions can't be replayed under another, potentially adversarial set of instructions.
10
11The API provides first-class alternatives to modify a conversation as it progresses, covering most use cases for transcript edits: [mid-conversation system messages](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages) for new instructions, [turn-scoped system messages](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#per-turn-reminders) for per-turn reminders, [mid-conversation tool changes](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages#mid-conversation-tool-changes) for adding and removing tools, and [per-message effort](https://platform.claude.com/docs/en/build-with-claude/effort#change-effort-mid-conversation-beta) to adjust depth of thinking per turn. The rest of this page covers how to tell whether your integration is affected and how to migrate common harness patterns to these features. As an added benefit, keeping everything before each thinking block byte-for-byte unchanged also keeps the prefix stable for [prompt caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching).
12
13Whether you need to do anything depends on what manages your conversation history:
14
15* **You use an official Claude product or SDK:** Claude Code, claude.ai, [Claude Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview), or the [Claude Agent SDK](https://code.claude.com/docs/en/agent-sdk/overview). These keep the prefix intact for you.
16
17* **You call the Messages API directly**, from your own agent loop or any other setting. You should check your code and ensure that the `messages` array is treated as append-only. These common patterns edit the prefix and invalidate the thinking after the edit:
18
19 * Trimming or dropping older turns
20 * Summarizing older turns on the client and keeping recent ones
21 * Injecting a reminder into an earlier turn and removing it on the next request
22 * Rebuilding the `system` prompt each request (current time, token budget, mode flags)
23 * Adding or removing entries in `tools` mid-session
24
25## How it works
26
27For new requests the API checks:
28
29* **The model is the same or newer.** A block is readable by the model that produced it and by later models, not by earlier ones. A conversation that moves to a newer model keeps its reasoning. A conversation that moves to an older model fails the model check for those blocks, and the API drops them for that request. See [Preserved thinking](https://platform.claude.com/docs/en/build-with-claude/thinking#preserved-for-model) for the exact per-model list.
30* **Nothing before the block has changed.** The top-level `system` prompt, the set of tools in `tools`, and every message before the block. With server-side compaction the checked prefix starts at the most recent [compaction block](https://platform.claude.com/docs/en/build-with-claude/compaction).
31* **The chain of earlier thinking blocks is unbroken.** Earlier `thinking` and `redacted_thinking` blocks aren't part of the prefix, but each thinking block records the one before it, across turns. You can remove thinking blocks from the front of the history. Removing one from the middle invalidates every thinking block after it.
32
33A block that fails the model check is always dropped. For a prefix mismatch you choose what happens with `thinking.block_binding.prefix_mismatch_behavior`, which requires the `thinking-binding-controls-2026-08-01` [beta header](https://platform.claude.com/docs/en/api/beta-headers):
34
35* `"drop_block"`: the API removes the block and every thinking block after it in the conversation, and the request succeeds. Dropped blocks aren't billed. The response lists them in a top-level `input_transformations` array (on the `message_start` event when streaming).
36* `"error"`: the API rejects the request with a 400 `invalid_request_error` that names the first failing block.
37
38The default is `"error"`. The header lets you set the field and adds `input_transformations` to responses.
39
40## Who is affected
41
42Claude Fable 5.1. See [Preserved thinking](https://platform.claude.com/docs/en/build-with-claude/thinking#preserved-thinking) for the model list.
43
44On Claude Fable 5.1, the API enforces the check for new accounts. A new account is one created on or after August 31, 2026, 00:00 UTC. The same definition applies on the Claude API and on cloud platforms. Later models will enforce the check for all users.
45
46A request that sets `prefix_mismatch_behavior` opts into enforcement regardless of account age, which is how you test from an older account. To check whether your account is enforced by default, send a request that edits history without the beta header: a 400 that names the header means enforced.
7Preserved thinking is a property of newer Claude models that guards against distillation. It decides whether the model can use a thinking block that you send back from an earlier turn. Starting with Claude Fable 5.1, when a `thinking` or `redacted_thinking` block comes back in a request, the API checks the block's `signature` for two things:
8
9* **The model is the one that produced the block, or a newer one.** A model reads its own thinking blocks and those of earlier models. Claude Fable 5.1 reads blocks from Claude Opus 5, but Claude Opus 5 can't read blocks from Claude Fable 5.1. If the current model can't read a block, the API drops it from that request without an error. See [Switching models mid-conversation](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#switching-models).
10* **Nothing before the thinking block has changed.** The top-level `system` prompt, `tools`, and `messages` before the block are its prefix. If the prefix differs from what you sent when the block was produced, that block and every later thinking block are invalid, and the API rejects the request with a 400 error or drops the invalid blocks, whichever you choose. See [Keeping the prefix unchanged](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#prefix-check).
11
12The model check applies to every account. The API enforces the prefix check by default for accounts created on or after August 31, 2026, 00:00 UTC. On older accounts, it enforces the prefix check only on requests that set `thinking.block_binding.prefix_mismatch_behavior`. **Later models will enforce the prefix check for all accounts**, so make your integration append-only now.
13
14## Switching models mid-conversation
15
16Claude Fable 5.1 and Claude Mythos 5.1 read thinking blocks produced by each other and by earlier Claude models. No earlier model reads thinking blocks from Claude Fable 5.1 or Claude Mythos 5.1.
17
18* **A conversation that moves up to Claude Fable 5.1 keeps its reasoning.** The earlier model's thinking blocks stay readable, so the model thinks as usual from the first turn after the switch.
19* **A conversation that moves down to an earlier model loses Claude Fable 5.1's reasoning for that request.** This happens when a router sends a turn to a cheaper model, after a [classifier refusal fallback](https://platform.claude.com/docs/en/build-with-claude/refusals-and-fallback), or during a [server-side fallback](https://platform.claude.com/docs/en/build-with-claude/refusals-and-fallback#server-side-fallback). The API removes the unreadable blocks before the prompt reaches the model. They aren't billed and don't count toward `input_tokens`.
20
21Keep sending the full history on every request, thinking blocks included, and let the API drop what the current model can't read. The API never edits your `messages` array, so the dropped blocks stay in your history. When the same history goes back to Claude Fable 5.1, its blocks are readable again, along with the earlier model's thinking. The reasoning is lost for good only if your client removes the blocks itself, for example a harness that strips thinking on a model switch or rebuilds the history from what each model used.
22
23
24
25With the `thinking-binding-controls-2026-08-01` [beta header](https://platform.claude.com/docs/en/api/beta-headers), the response lists each dropped block in a top-level `input_transformations` array with `reason: "model_binding_mismatch"`:
26
27```json
28{
29 "input_transformations": [
30 {
31 "type": "thinking_dropped",
32 "path": "messages.3.content.0",
33 "reason": "model_binding_mismatch"
34 }
35 ]
36}
37```
38
39Without the header, the drop is silent. This entry isn't a bug in your integration, and `prefix_mismatch_behavior` has no effect on it: a block the current model can't read is always dropped.
40
41## Keeping the prefix unchanged
42
43On Claude Fable 5.1, a thinking block stays valid only while everything you sent before it is unchanged on later requests. The checked prefix has three parts:
44
45* The top-level `system` prompt
46* The set of `tools`
47* Every `message` before the block
48
49Note: With server-side [compaction](https://platform.claude.com/docs/en/build-with-claude/compaction), the checked prefix starts at the most recent compaction block.
50
51Request parameters outside those three fields, such as `effort`, `max_tokens`, `output_config`, `tool_choice`, and `metadata`, aren't part of the prefix check, and neither are `cache_control` markers. [What counts as an edit](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#what-counts-as-an-edit) has the full list.
52
53Earlier thinking blocks aren't in the prefix, but each thinking block records which thinking block came before it, across turns. You can remove thinking blocks from the front of the history, oldest first. Removing one from the middle invalidates thinking blocks after it.
54
55Keep `system` and `tools` fixed for the session and treat `messages` as append-only. The same discipline keeps the prefix stable for [prompt caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching): the edits that invalidate thinking are the edits that restart the cache.
56
57### What the API does with an invalid block
58
59You choose with `thinking.block_binding.prefix_mismatch_behavior`:
60
61* **`"error"` (the default):** the API rejects the request with a 400 `invalid_request_error` that names the first failing block.
62* **`"drop_block"`:** the API drops each failing block and every thinking block after it, and the request succeeds. Dropped blocks aren't billed. The model answers that turn without using reasoning from dropped blocks, and the prompt cache restarts at the edit. The response lists each dropped block in `input_transformations` (on the `message_start` event when streaming) with `reason: "prefix_binding_mismatch"`.
63
64Both the field and the `input_transformations` array require the `thinking-binding-controls-2026-08-01` [beta header](https://platform.claude.com/docs/en/api/beta-headers). [Set the mismatch behavior and read `input_transformations`](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#preserved-thinking-controls) shows the request in each SDK.
65
66The 400 message begins:
67
68```text wrap
69messages.1.content.0: Invalid `signature` in `thinking` block. The block is bound to a different conversation. Remove the block, or set `thinking.block_binding.prefix_mismatch_behavior` to "drop_block".
70```
71
72If the request didn't send the beta header, the message continues:
73
74```text wrap
75That setting requires the `thinking-binding-controls-2026-08-01` value in the `anthropic-beta` header.
76```
77
78It usually ends with a sentence naming what changed, for example that the `system` prompt or the `tools` list differs from when the block was created. See [Troubleshooting thinking](https://platform.claude.com/docs/en/build-with-claude/thinking-troubleshooting#error-thinking-block-signature) for every variant of this error.
79
80If you hit this 400 in production, retrying the same body fails the same way. Retry with the beta header and `prefix_mismatch_behavior: "drop_block"` and keep sending it for the rest of the session, or strip every `thinking` and `redacted_thinking` block from the history yourself and retry once. Then fix the edit that caused the mismatch. In the Message Batches API, an item that leaves the field unset drops failing blocks instead of erroring, so set `"error"` explicitly there if you want batch items to fail.
81
82A tampered or undecryptable signature is a different failure. It always returns a 400 (``Invalid `signature` in `thinking` block`` with no sentence about the conversation), and `prefix_mismatch_behavior` doesn't apply to it.
83
84### Set the mismatch behavior and read `input_transformations`
85
86The `thinking-binding-controls-2026-08-01` [beta header](https://platform.claude.com/docs/en/api/beta-headers) adds:
87
88* A top-level `input_transformations` array on every response
89* A `block_binding` object on the `thinking` configuration, whose one field is `prefix_mismatch_behavior`
90
91`block_binding` is accepted alongside `thinking.type: "adaptive"` and `thinking.type: "enabled"`. Sending it without the beta header returns a 400 error. Models that don't run the prefix check accept the object and report only model-check drops, so one request body works across models.
92
93The following request opts into dropping rather than rejecting. On a first turn there's nothing to replay, so `input_transformations` comes back empty:
94
95<CodeGroup>
96 ```bash cURL
97 curl https://api.anthropic.com/v1/messages \
98 -H "x-api-key: $ANTHROPIC_API_KEY" \
99 -H "anthropic-version: 2023-06-01" \
100 -H "anthropic-beta: thinking-binding-controls-2026-08-01" \
101 -H "content-type: application/json" \
102 -d '{
103 "model": "claude-fable-5-1",
104 "max_tokens": 16000,
105 "thinking": {
106 "type": "adaptive",
107 "block_binding": {
108 "prefix_mismatch_behavior": "drop_block"
109 }
110 },
111 "messages": [
112 {
113 "role": "user",
114 "content": "What is the greatest common divisor of 1071 and 462?"
115 }
116 ]
117 }'
118 ```
119
120 ```bash CLI
121 ant beta:messages create --beta thinking-binding-controls-2026-08-01 \
122 --transform '{content.#(type=="text")#.text,input_transformations}' \
123 --format yaml <<'YAML'
124 model: claude-fable-5-1
125 max_tokens: 16000
126 thinking:
127 type: adaptive
128 block_binding:
129 prefix_mismatch_behavior: drop_block
130 messages:
131 - role: user
132 content: What is the greatest common divisor of 1071 and 462?
133 YAML
134 ```
135
136 ```python Python
137 client = anthropic.Anthropic()
138
139 response = client.beta.messages.create(
140 model="claude-fable-5-1",
141 max_tokens=16000,
142 thinking={
143 "type": "adaptive",
144 "block_binding": {"prefix_mismatch_behavior": "drop_block"},
145 },
146 messages=[
147 {
148 "role": "user",
149 "content": "What is the greatest common divisor of 1071 and 462?",
150 }
151 ],
152 betas=["thinking-binding-controls-2026-08-01"],
153 )
154
155 for block in response.content:
156 if block.type == "text":
157 print(block.text)
158
159 print(f"Input transformations: {len(response.input_transformations or [])}")
160 ```
161
162 ```typescript TypeScript
163 const client = new Anthropic();
164
165 const response = await client.beta.messages.create({
166 model: "claude-fable-5-1",
167 max_tokens: 16000,
168 thinking: {
169 type: "adaptive",
170 block_binding: { prefix_mismatch_behavior: "drop_block" }
171 },
172 messages: [
173 { role: "user", content: "What is the greatest common divisor of 1071 and 462?" }
174 ],
175 betas: ["thinking-binding-controls-2026-08-01"]
176 });
177
178 for (const block of response.content) {
179 if (block.type === "text") {
180 console.log(block.text);
181 }
182 }
183 console.log(`Input transformations: ${response.input_transformations?.length ?? 0}`);
184 ```
185
186 ```csharp C#
187 AnthropicClient client = new();
188
189 var response = await client.Beta.Messages.Create(
190 new()
191 {
192 Model = "claude-fable-5-1",
193 MaxTokens = 16000,
194 Thinking = new BetaThinkingConfigAdaptive
195 {
196 BlockBinding = new()
197 {
198 PrefixMismatchBehavior = BetaThinkingPrefixMismatchBehavior.DropBlock,
199 },
200 },
201 Messages =
202 [
203 new()
204 {
205 Role = Role.User,
206 Content = "What is the greatest common divisor of 1071 and 462?",
207 },
208 ],
209 Betas = [AnthropicBeta.ThinkingBindingControls2026_08_01],
210 }
211 );
212
213 foreach (var block in response.Content)
214 {
215 if (block.TryPickText(out var textBlock))
216 {
217 Console.WriteLine(textBlock.Text);
218 }
219 }
220
221 Console.WriteLine($"Input transformations: {response.InputTransformations?.Count ?? 0}");
222 ```
223
224 ```go Go
225 client := anthropic.NewClient()
226
227 response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
228 Model: "claude-fable-5-1",
229 MaxTokens: 16000,
230 Thinking: anthropic.BetaThinkingConfigParamUnion{
231 OfAdaptive: &anthropic.BetaThinkingConfigAdaptiveParam{
232 BlockBinding: anthropic.BetaThinkingBlockBindingParam{
233 PrefixMismatchBehavior: anthropic.BetaThinkingPrefixMismatchBehaviorDropBlock,
234 },
235 },
236 },
237 Messages: []anthropic.BetaMessageParam{
238 anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("What is the greatest common divisor of 1071 and 462?")),
239 },
240 Betas: []anthropic.AnthropicBeta{anthropic.AnthropicBetaThinkingBindingControls2026_08_01},
241 })
242 if err != nil {
243 log.Fatal(err)
244 }
245
246 for _, block := range response.Content {
247 if textBlock, ok := block.AsAny().(anthropic.BetaTextBlock); ok {
248 fmt.Println(textBlock.Text)
249 }
250 }
251 fmt.Printf("Input transformations: %d\n", len(response.InputTransformations))
252 ```
253
254 ```java Java
255 import com.anthropic.models.beta.AnthropicBeta;
256 import com.anthropic.models.beta.messages.BetaMessage;
257 import com.anthropic.models.beta.messages.BetaThinkingBlockBinding;
258 import com.anthropic.models.beta.messages.BetaThinkingConfigAdaptive;
259 import com.anthropic.models.beta.messages.BetaThinkingPrefixMismatchBehavior;
260 import com.anthropic.models.beta.messages.MessageCreateParams;
261
262 void main() {
263 AnthropicClient client = AnthropicOkHttpClient.fromEnv();
264
265 MessageCreateParams params = MessageCreateParams.builder()
266 .model("claude-fable-5-1")
267 .maxTokens(16000L)
268 .addBeta(AnthropicBeta.THINKING_BINDING_CONTROLS_2026_08_01)
269 .thinking(BetaThinkingConfigAdaptive.builder()
270 .blockBinding(BetaThinkingBlockBinding.builder()
271 .prefixMismatchBehavior(BetaThinkingPrefixMismatchBehavior.DROP_BLOCK)
272 .build())
273 .build())
274 .addUserMessage("What is the greatest common divisor of 1071 and 462?")
275 .build();
276
277 BetaMessage response = client.beta().messages().create(params);
278
279 response.content().stream()
280 .flatMap(block -> block.text().stream())
281 .forEach(textBlock -> IO.println(textBlock.text()));
282 IO.println("Input transformations: "
283 + response.inputTransformations().map(List::size).orElse(0));
284 }
285 ```
286
287 ```php PHP
288 use Anthropic\Beta\AnthropicBeta;
289 use Anthropic\Beta\Messages\BetaThinkingBlockBinding;
290 use Anthropic\Beta\Messages\BetaThinkingConfigAdaptive;
291 use Anthropic\Beta\Messages\BetaThinkingPrefixMismatchBehavior;
292 use Anthropic\Client;
293
294 $client = new Client();
295
296 $response = $client->beta->messages->create(
297 model: 'claude-fable-5-1',
298 maxTokens: 16000,
299 thinking: BetaThinkingConfigAdaptive::with(
300 blockBinding: BetaThinkingBlockBinding::with(
301 prefixMismatchBehavior: BetaThinkingPrefixMismatchBehavior::DROP_BLOCK,
302 ),
303 ),
304 messages: [
305 ['role' => 'user', 'content' => 'What is the greatest common divisor of 1071 and 462?'],
306 ],
307 betas: [AnthropicBeta::THINKING_BINDING_CONTROLS_2026_08_01],
308 );
309
310 foreach ($response->content as $block) {
311 if ($block->type === 'text') {
312 echo $block->text, PHP_EOL;
313 }
314 }
315
316 echo 'Input transformations: ', count($response->inputTransformations ?? []), PHP_EOL;
317 ```
318
319 ```ruby Ruby
320 client = Anthropic::Client.new
321
322 response = client.beta.messages.create(
323 model: "claude-fable-5-1",
324 max_tokens: 16_000,
325 thinking: {
326 type: "adaptive",
327 block_binding: {prefix_mismatch_behavior: "drop_block"}
328 },
329 messages: [
330 {role: "user", content: "What is the greatest common divisor of 1071 and 462?"}
331 ],
332 betas: [Anthropic::AnthropicBeta::THINKING_BINDING_CONTROLS_2026_08_01]
333 )
334
335 response.content.each do |block|
336 puts block.text if block.type == :text
337 end
338
339 puts "Input transformations: #{response.input_transformations&.length || 0}"
340 ```
341</CodeGroup>
342
343```text Output wrap
344The greatest common divisor of 1071 and 462 is 21.
345Input transformations: 0
346```
347
348Under the beta header, every response from a thinking-capable model carries `input_transformations`. It's empty when nothing was dropped. Each entry has `type: "thinking_dropped"`, the `path` of the dropped block (for example `messages.1.content.0`), and a `reason` of `prefix_binding_mismatch` or `model_binding_mismatch` (see [Switching models mid-conversation](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#switching-models)). Ignore entries whose `type` or `reason` you don't recognize, because later checks add values.
349
350When [streaming](https://platform.claude.com/docs/en/build-with-claude/streaming), the array arrives on the `message` object in the `message_start` event. After a mid-stream server-side fallback, the final `message_delta` event carries it again with the serving model's entries. In a [message batch](https://platform.claude.com/docs/en/build-with-claude/batch-processing), an item whose block fails the prefix check under an explicit `"error"` resolves as `errored`, and an item that leaves the field unset drops the failing blocks instead. The [token counting](https://platform.claude.com/docs/en/build-with-claude/token-counting) endpoint runs the same prefix check and returns the same 400.
351
352### When the API enforces the check
353
354The prefix check runs on Claude Fable 5.1 for new accounts.
355
356* **Accounts created on or after August 31, 2026, 00:00 UTC:** the API checks Claude Fable 5.1 requests and applies `"error"` unless you set `"drop_block"`. The same definition of a new account applies to the Claude API and to cloud platforms.
357* **Older accounts:** the API checks requests that set `prefix_mismatch_behavior`. This parameter opts a request in, so you can see what a new account sees without creating one.
358* **Later models:** every account, on every request.
359
360To find out which group your account is in, take a Claude Fable 5.1 conversation that contains a thinking block, change something before that block, and send it to Claude Fable 5.1 without the beta header or the `block_binding` field. A 400 response that names the header means your account is enforced by default.
47361
48362<Note>
49 If you maintain a tool or framework that people run with their own API key, your users on new accounts hit the check before you do: your own key is likely on an older account. Test with `prefix_mismatch_behavior` set so you see what they'll see.
363 If you maintain a tool or framework that people run with their own API key, your users on new accounts hit the check before you do, because your own key is likely on an older account. Test with `prefix_mismatch_behavior` set so you see what they see.
50364</Note>
51365
52## How to tell whether your integration is impacted
53
54Capture the exact request bodies your integration sends over a few normal turns, including a compaction or a tool change if your product does those. For each pair of consecutive requests, compare `system`, `tools`, and the shared part of `messages`. They should be byte-identical up to the newly appended turns.
55
56Then confirm against the API. With the `thinking-binding-controls-2026-08-01` [beta header](https://platform.claude.com/docs/en/api/beta-headers) and `claude-fable-5-1`, set `thinking.block_binding.prefix_mismatch_behavior` to `"drop_block"` and run a normal multi-turn session through your integration. This request is the second turn of such a session, sending back the first response's assistant turn exactly as received:
57
58```bash
59curl https://api.anthropic.com/v1/messages \
60 -H "content-type: application/json" \
61 -H "x-api-key: $ANTHROPIC_API_KEY" \
62 -H "anthropic-version: 2023-06-01" \
63 -H "anthropic-beta: thinking-binding-controls-2026-08-01" \
64 -d '{
65 "model": "claude-fable-5-1",
66 "max_tokens": 16000,
67 "thinking": {
68 "type": "adaptive",
69 "block_binding": { "prefix_mismatch_behavior": "drop_block" }
366### What counts as an edit
367
368Each row compares two consecutive requests:
369
370| Change between requests | Later thinking blocks |
371| ------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------- |
372| Append messages at the end | Valid |
373| Add a tool with `defer_loading: true` that nothing has referenced yet | Valid |
374| Remove `thinking` blocks from the start of the history | Valid |
375| Change any request parameter outside `system`, `tools`, and `messages` (`effort`, `max_tokens`, `output_config`, `tool_choice`, `metadata`, and so on) | Valid |
376| Add, move, or remove `cache_control` markers | Valid |
377| A rotating signed URL that returns the same bytes | Valid |
378| Server-side compaction or context editing removes or replaces content | Valid (the check compares what you sent, not the server's edited copy) |
379| A cleared [turn-scoped system message](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#per-turn-reminders) left in place | Valid |
380| Edit, reorder, or delete any earlier `user`, `assistant`, or `system` message | Invalid |
381| Add a text block to an earlier user turn, or remove one you added last time | Invalid |
382| Change the top-level `system` string or blocks | Invalid |
383| Add, remove, rename, or edit a tool in `tools` | Invalid |
384| Remove a `thinking` block from the middle of the history and keep later ones | Invalid for every later thinking block |
385| An image or document URL that returns different bytes on the next request | Invalid |
386| The same turn-scoped message deleted or reworded on a later request | Invalid |
387
388### Check whether your code edits the prefix
389
390First, diff what you send. Capture the request bodies your integration sends over a few normal turns, including a compaction or a tool change. For each pair of consecutive requests, compare `system`, `tools`, and the `messages` they share. They should be identical up to the newly appended turns.
391
392Then confirm against the API. Add the `thinking-binding-controls-2026-08-01` beta header, set `prefix_mismatch_behavior` to `"drop_block"`, and run a normal multi-turn session through your integration on claude-fable-5-1. The following example runs two turns the way your integration should: `messages` only grows, each assistant turn goes back exactly as the API returned it, `thinking` blocks included, and `block_binding` is set on every request. It prints the number of dropped blocks after each turn:
393
394<CodeGroup>
395 ```bash cURL
396 FIRST=$(curl -s https://api.anthropic.com/v1/messages \
397 -H "content-type: application/json" \
398 -H "x-api-key: $ANTHROPIC_API_KEY" \
399 -H "anthropic-version: 2023-06-01" \
400 -H "anthropic-beta: thinking-binding-controls-2026-08-01" \
401 -d '{
402 "model": "claude-fable-5-1",
403 "max_tokens": 16000,
404 "thinking": {
405 "type": "adaptive",
406 "block_binding": { "prefix_mismatch_behavior": "drop_block" }
407 },
408 "messages": [{ "role": "user", "content": "What is 27 * 453?" }]
409 }')
410 echo "$FIRST" | jq '.input_transformations | length'
411
412 # Turn 2: the assistant turn goes back exactly as returned, then the next user message
413 MESSAGES=$(jq -n --argjson first "$FIRST" '[
414 { role: "user", content: "What is 27 * 453?" },
415 { role: "assistant", content: $first.content },
416 { role: "user", content: "Now divide that result by 3." }
417 ]')
418
419 jq -n --argjson messages "$MESSAGES" '{
420 model: "claude-fable-5-1",
421 max_tokens: 16000,
422 thinking: {
423 type: "adaptive",
424 block_binding: { prefix_mismatch_behavior: "drop_block" }
70425 },
71 "system": "You are a coding agent.",
72 "messages": [
73 { "role": "user", "content": "Fix the failing test." },
426 messages: $messages
427 }' | curl -s https://api.anthropic.com/v1/messages \
428 -H "content-type: application/json" \
429 -H "x-api-key: $ANTHROPIC_API_KEY" \
430 -H "anthropic-version: 2023-06-01" \
431 -H "anthropic-beta: thinking-binding-controls-2026-08-01" \
432 -d @- | jq '.input_transformations | length'
433 ```
434
435 ```bash CLI
436 FIRST=$(ant beta:messages create --beta thinking-binding-controls-2026-08-01 \
437 --transform content --format json <<'YAML'
438 model: claude-fable-5-1
439 max_tokens: 16000
440 thinking:
441 type: adaptive
442 block_binding:
443 prefix_mismatch_behavior: drop_block
444 messages:
445 - role: user
446 content: What is 27 * 453?
447 YAML
448 )
449
450 # Turn 2: the assistant turn goes back exactly as returned, then the next user message
451 ant beta:messages create --beta thinking-binding-controls-2026-08-01 \
452 --transform input_transformations --format json <<YAML
453 model: claude-fable-5-1
454 max_tokens: 16000
455 thinking:
456 type: adaptive
457 block_binding:
458 prefix_mismatch_behavior: drop_block
459 messages:
460 - role: user
461 content: What is 27 * 453?
462 - role: assistant
463 content: $(echo "$FIRST" | jq -c .)
464 - role: user
465 content: Now divide that result by 3.
466 YAML
467 ```
468
469 ```python Python
470 client = anthropic.Anthropic()
471
472 # messages grows across turns: each assistant turn goes back exactly as returned
473 messages = []
474 for user_turn in ["What is 27 * 453?", "Now divide that result by 3."]:
475 messages.append({"role": "user", "content": user_turn})
476 response = client.beta.messages.create(
477 model="claude-fable-5-1",
478 max_tokens=16000,
479 thinking={
480 "type": "adaptive",
481 "block_binding": {"prefix_mismatch_behavior": "drop_block"},
482 },
483 messages=messages,
484 betas=["thinking-binding-controls-2026-08-01"],
485 )
486 messages.append({"role": "assistant", "content": response.content})
487 print(len(response.input_transformations or []))
488 ```
489
490 ```typescript TypeScript
491 const client = new Anthropic();
492
493 // messages grows across turns: each assistant turn goes back exactly as returned
494 const messages: Anthropic.Beta.BetaMessageParam[] = [];
495 for (const userTurn of ["What is 27 * 453?", "Now divide that result by 3."]) {
496 messages.push({ role: "user", content: userTurn });
497 const response = await client.beta.messages.create({
498 model: "claude-fable-5-1",
499 max_tokens: 16000,
500 thinking: {
501 type: "adaptive",
502 block_binding: { prefix_mismatch_behavior: "drop_block" }
503 },
504 messages,
505 betas: ["thinking-binding-controls-2026-08-01"]
506 });
507 messages.push({ role: "assistant", content: response.content });
508 console.log(response.input_transformations?.length ?? 0);
509 }
510 ```
511
512 ```csharp C#
513 AnthropicClient client = new();
514
515 // messages grows across turns: each assistant turn goes back exactly as returned
516 List<BetaMessageParam> messages = [];
517 foreach (var userTurn in new[] { "What is 27 * 453?", "Now divide that result by 3." })
518 {
519 messages.Add(new() { Role = Role.User, Content = userTurn });
520 var response = await client.Beta.Messages.Create(
521 new()
522 {
523 Model = "claude-fable-5-1",
524 MaxTokens = 16000,
525 Thinking = new BetaThinkingConfigAdaptive
526 {
527 BlockBinding = new()
528 {
529 PrefixMismatchBehavior = BetaThinkingPrefixMismatchBehavior.DropBlock,
530 },
531 },
532 Messages = messages,
533 Betas = [AnthropicBeta.ThinkingBindingControls2026_08_01],
534 }
535 );
536 messages.Add(new()
74537 {
75 "role": "assistant",
76 "content": [
77 { "type": "thinking", "thinking": "", "signature": "EqQBCkYIBxgCKkD..." },
78 { "type": "text", "text": "I need to see the test first. Which file is it in?" }
79 ]
538 Role = Role.Assistant,
539 Content = response.Content.Select(block => new BetaContentBlockParam(block.Json)).ToList(),
540 });
541 Console.WriteLine(response.InputTransformations?.Count ?? 0);
542 }
543 ```
544
545 ```go Go
546 client := anthropic.NewClient()
547
548 // messages grows across turns: each assistant turn goes back exactly as returned
549 messages := []anthropic.BetaMessageParam{}
550 for _, userTurn := range []string{"What is 27 * 453?", "Now divide that result by 3."} {
551 messages = append(messages, anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock(userTurn)))
552 response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
553 Model: "claude-fable-5-1",
554 MaxTokens: 16000,
555 Thinking: anthropic.BetaThinkingConfigParamUnion{
556 OfAdaptive: &anthropic.BetaThinkingConfigAdaptiveParam{
557 BlockBinding: anthropic.BetaThinkingBlockBindingParam{
558 PrefixMismatchBehavior: anthropic.BetaThinkingPrefixMismatchBehaviorDropBlock,
559 },
560 },
561 },
562 Messages: messages,
563 Betas: []anthropic.AnthropicBeta{anthropic.AnthropicBetaThinkingBindingControls2026_08_01},
564 })
565 if err != nil {
566 log.Fatal(err)
567 }
568 messages = append(messages, response.ToParam())
569 fmt.Println(len(response.InputTransformations))
570 }
571 ```
572
573 ```java Java
574 import com.anthropic.models.beta.AnthropicBeta;
575 import com.anthropic.models.beta.messages.BetaMessage;
576 import com.anthropic.models.beta.messages.BetaThinkingBlockBinding;
577 import com.anthropic.models.beta.messages.BetaThinkingConfigAdaptive;
578 import com.anthropic.models.beta.messages.BetaThinkingPrefixMismatchBehavior;
579 import com.anthropic.models.beta.messages.MessageCreateParams;
580
581 void main() {
582 AnthropicClient client = AnthropicOkHttpClient.fromEnv();
583
584 // The builder's message list grows across turns: each assistant turn goes back exactly as returned
585 MessageCreateParams.Builder conversation = MessageCreateParams.builder()
586 .model("claude-fable-5-1")
587 .maxTokens(16000L)
588 .thinking(BetaThinkingConfigAdaptive.builder()
589 .blockBinding(BetaThinkingBlockBinding.builder()
590 .prefixMismatchBehavior(BetaThinkingPrefixMismatchBehavior.DROP_BLOCK)
591 .build())
592 .build())
593 .addBeta(AnthropicBeta.THINKING_BINDING_CONTROLS_2026_08_01);
594
595 for (String userTurn : List.of("What is 27 * 453?", "Now divide that result by 3.")) {
596 conversation.addUserMessage(userTurn);
597 BetaMessage response = client.beta().messages().create(conversation.build());
598 conversation.addMessage(response);
599 IO.println(response.inputTransformations().map(List::size).orElse(0));
600 }
601 }
602 ```
603
604 ```php PHP
605 use Anthropic\Beta\AnthropicBeta;
606 use Anthropic\Beta\Messages\BetaThinkingBlockBinding;
607 use Anthropic\Beta\Messages\BetaThinkingConfigAdaptive;
608 use Anthropic\Beta\Messages\BetaThinkingPrefixMismatchBehavior;
609 use Anthropic\Client;
610
611 $client = new Client();
612
613 // $messages grows across turns: each assistant turn goes back exactly as returned
614 $messages = [];
615 foreach (['What is 27 * 453?', 'Now divide that result by 3.'] as $userTurn) {
616 $messages[] = ['role' => 'user', 'content' => $userTurn];
617 $response = $client->beta->messages->create(
618 model: 'claude-fable-5-1',
619 maxTokens: 16000,
620 thinking: BetaThinkingConfigAdaptive::with(
621 blockBinding: BetaThinkingBlockBinding::with(
622 prefixMismatchBehavior: BetaThinkingPrefixMismatchBehavior::DROP_BLOCK,
623 ),
624 ),
625 messages: $messages,
626 betas: [AnthropicBeta::THINKING_BINDING_CONTROLS_2026_08_01],
627 );
628 $messages[] = ['role' => 'assistant', 'content' => $response->content];
629 echo count($response->inputTransformations ?? []), PHP_EOL;
630 }
631 ```
632
633 ```ruby Ruby
634 client = Anthropic::Client.new
635
636 # messages grows across turns: each assistant turn goes back exactly as returned
637 messages = []
638 ["What is 27 * 453?", "Now divide that result by 3."].each do |user_turn|
639 messages << {role: "user", content: user_turn}
640 response = client.beta.messages.create(
641 model: "claude-fable-5-1",
642 max_tokens: 16_000,
643 thinking: {
644 type: "adaptive",
645 block_binding: {prefix_mismatch_behavior: "drop_block"}
80646 },
81 { "role": "user", "content": "tests/test_auth.py" }
82 ]
83 }'
84```
85
86Every response then carries a top-level `input_transformations` array. Log it on each turn:
647 messages: messages,
648 betas: [Anthropic::AnthropicBeta::THINKING_BINDING_CONTROLS_2026_08_01]
649 )
650 messages << {role: "assistant", content: response.content}
651 puts (response.input_transformations || []).length
652 end
653 ```
654</CodeGroup>
655
656```text Output wrap
6570
6580
659```
660
661Both turns print `0` because nothing earlier changed. Log `input_transformations` on every turn of your own integration. When the API drops a block, the entry looks like the following:
87662
88663```json
89664{
from line 672
97672}
98673```
99674
100* **Empty on every turn:** your integration keeps history intact.
101* **`reason: "prefix_binding_mismatch"`:** something before the block at `path` changed between this request and the previous one. Diff `system`, `tools`, and `messages` up to that turn to find it.
102* **`reason: "model_binding_mismatch"`:** the conversation moved to a model that can't read the earlier model's blocks (a router, a fallback). Not a bug in your integration. Keep sending the blocks and let the API drop what the current model can't read.
103
104This works from any account, because setting the field opts the request into enforcement. To fail loudly in CI instead, set `"error"`. The 400 begins:
675* **Empty on every turn:** your integration keeps the prefix intact.
676* **`reason: "prefix_binding_mismatch"`:** something before the block at `path` changed since the previous request. Diff `system`, `tools`, and `messages` up to that turn to find it, then find the matching replacement in [Make changes without editing the prefix](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#replace-prefix-edits).
677* **`reason: "model_binding_mismatch"`:** the conversation moved to a model that can't read the earlier model's blocks. This isn't a prefix edit. See [Switching models mid-conversation](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#switching-models).
678
679To fail loudly in CI instead, set `"error"` and treat the 400 described in [What the API does with an invalid block](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#mismatch-behavior) as a test failure.
680
681## Make changes without editing the prefix
682
683Each common prefix edit has a replacement that gives the model the same information and leaves earlier bytes unchanged, so later thinking stays valid. Find the edit your code makes today in the first column:
684
685| Instead of | Use | Beta header |
686| --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
687| Rebuilding the top-level `system` prompt | A [mid-conversation system message](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#new-instructions) | None |
688| Injecting a reminder and deleting it on the next request | A [turn-scoped system message](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#per-turn-reminders) (`clear_at: "next_user_message"`) | `mid-conversation-system-clear-at-2026-08-21` |
689| Adding or removing entries in `tools` | [`tool_addition` and `tool_removal` blocks](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#tool-changes) | `mid-conversation-tool-changes-2026-07-01` |
690| Changing top-level `output_config.effort` (restarts the cache, doesn't affect thinking) | A [per-message `output_config`](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#effort-changes) | `mid-conversation-output-config-2026-07-01` |
691| Dropping or summarizing old turns on the client | Server-side [compaction or context editing](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#server-side-trimming), or [client-side compaction](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#custom-compaction-on-the-client) that keeps no stale thinking | `compact-2026-01-12` or `context-management-2025-06-27` |
692| An image or document URL whose bytes change between requests | A [`file_id` from the Files API](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#files-by-id), or base64 | None |
693
694All of these assume you [send assistant turns back exactly as returned](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#append-assistant-turns-exactly-as-returned). To use several betas in one request, combine the values in one `anthropic-beta` header. The same names apply on Amazon Bedrock and Google Cloud (see [Beta headers](https://platform.claude.com/docs/en/api/beta-headers)):
105695
106696```text wrap
107messages.1.content.0: Invalid `signature` in `thinking` block. The block is bound to a different conversation. Remove the block, or set `thinking.block_binding.prefix_mismatch_behavior` to "drop_block".
108```
109
110Without the beta header on the request, the message continues: ``That setting requires the `thinking-binding-controls-2026-08-01` value in the `anthropic-beta` header.`` The message usually ends with a sentence naming what changed, for example that the `system` prompt or the `tools` list differs from when the block was created.
111
112See [Troubleshooting thinking](https://platform.claude.com/docs/en/build-with-claude/thinking-troubleshooting#error-thinking-block-signature) for every variant of this error.
113
114## What counts as an edit
115
116Between two consecutive requests:
117
118| Change between requests | Later thinking blocks |
119| ------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
120| Append messages at the end | Valid |
121| Add a tool with `defer_loading: true` that nothing has referenced yet | Valid |
122| Remove `thinking` blocks from the start of the history (every thinking block before some point) | Valid |
123| Change any request parameter outside `system`, `tools`, and `messages` (`max_tokens`, `output_config`, `tool_choice`, `metadata`, and so on) | Valid |
124| Add, move, or remove `cache_control` markers | Valid |
125| A rotating signed URL that returns the same bytes | Valid |
126| Server-side compaction or context editing removes or replaces content | Valid (the check compares what you sent, not the server's edited copy) |
127| A cleared [turn-scoped system message](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#per-turn-reminders) left in place | Valid |
128| Edit, reorder, or delete any earlier `user`, `assistant`, or `system` message | Invalid |
129| Add a text block to an earlier user turn, or remove one you added last time | Invalid |
130| Change the top-level `system` string or blocks | Invalid |
131| Add, remove, rename, or edit a tool in `tools` | Invalid |
132| Remove a `thinking` block from the middle of the history and keep later ones | Invalid for every later thinking block |
133| An image or document URL that returns different bytes on the next request | Invalid |
134| The same turn-scoped message deleted or reworded on a later request | Invalid |
135
136## Update your integration
137
138Each pattern replaces one kind of history edit with an API feature that has the same effect on the model without changing earlier bytes.
139
140### Append assistant turns exactly as returned
141
142Store the `content` array from each response and send it back unchanged as the assistant turn, every block type in the order received, including `thinking` blocks whose `thinking` field is empty. Don't reserialize through an intermediate type that drops unknown block types or empty fields.
143
144### Add instructions with a mid-conversation system message, not by editing `system`
145
146If your code rebuilds the top-level `system` prompt each request (current time, token budget, mode flag, newly discovered project context), every thinking block in the conversation fails the check. Freeze `system` at session start, and when something changes append a [`role: "system"` message](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages) at the point in `messages` where it becomes true:
697anthropic-beta: thinking-binding-controls-2026-08-01,mid-conversation-system-clear-at-2026-08-21,mid-conversation-tool-changes-2026-07-01
698```
699
700### Send assistant turns back exactly as returned
701
702Store the `content` array from each response and send it back unchanged as the assistant turn: every block type, in the order received, including `thinking` blocks whose `thinking` field is empty. A serializer that drops unknown block types, drops empty fields, or reorders blocks edits the prefix for every later turn.
703
704### Add instructions with a mid-conversation system message
705
706Some harnesses rebuild the top-level `system` prompt on each request to carry the current time, a token budget, a mode flag, or newly discovered project context. That invalidates every thinking block in the conversation. Instead, freeze `system` at session start. When something changes, append a [`role: "system"` message](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages) at the point in `messages` where the change becomes true:
147707
148708```json
149709{
from line 712
152712}
153713```
154714
155The model treats it with system-prompt authority, and everything before it is unchanged. No beta header is needed on Claude Fable 5.1. In a tool loop, place it after the `tool_result` user message, never between an assistant `tool_use` and its `tool_result` (see [Limitations](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages#limitations)).
715The model treats this message with system-prompt authority, and everything before it stays unchanged. In a tool loop, place the message after the `tool_result` user message, never between an assistant `tool_use` and its `tool_result` (see [Limitations](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages#limitations)). Once sent, the message is part of the prefix for later thinking: leave it in place on later requests.
156716
157717### Send per-turn reminders as turn-scoped system messages
158718
159The most common history edit is the per-turn nudge: a line appended after each batch of tool results ("request independent reads together", "you haven't updated the user in a while") and removed on the next request so reminders don't pile up. Removing it is the edit.
160
161Instead, send the nudge as a [mid-conversation system message](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages) with `clear_at: "next_user_message"` after the `tool_result` user message (beta header `mid-conversation-system-clear-at-2026-08-21`). This `messages` array is the request after two tool rounds. `messages[3]` is the previous request's nudge, left in place, and `messages[6]` is this request's copy:
719The most common prefix edit is the per-turn nudge: a line such as "request independent reads together" or "you haven't updated the user in a while" that your code appends after each batch of tool results. To keep reminders from piling up, send each nudge as a [mid-conversation system message](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages) with `clear_at: "next_user_message"`, placed after the `tool_result` user message. `clear_at` requires the beta header `mid-conversation-system-clear-at-2026-08-21`. The following `messages` array is the request after two tool calls and their results. `messages[3]` is the previous request's nudge, left in place, and `messages[6]` is this request's copy:
162720
163721```json
164722[
from line 766
208766]
209767```
210768
211A `tool_result`-only user message counts as the "next user message", so `messages[3]` is already cleared: it renders nothing and costs no input tokens, but it's still in the array, so the thinking in `messages[4]` stays valid. `messages[6]` is what the model sees this turn. On later requests keep both where they are and append the next copy after the next `tool_result` message. Turn-scoped messages carry `text` only and take no `cache_control`. Put the cache breakpoint on the preceding user turn. See [Turn-scoped system messages](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages#turn-scoped-system-messages).
212
213Without the beta, append the nudge as a `text` block after the `tool_result` blocks in the same user message, and leave earlier copies in place. The model acts on the newest one.
214
215### Change tools with `tool_addition` and `tool_removal`, not by editing `tools`
216
217If the set of tools changes mid-session (a tool unlocks after authentication, a dangerous tool is withdrawn after a mode switch), don't edit `tools`. Declare the full set at session start and use [mid-conversation tool changes](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages#mid-conversation-tool-changes) to offer or withdraw a tool from that point on (beta header `mid-conversation-tool-changes-2026-07-01`). A tool that isn't available yet gets `defer_loading: true` and a later `tool_addition` block, same shape as this `tool_removal`:
769A user message that contains only `tool_result` blocks counts as the "next user message", so `messages[3]` is already cleared. It adds nothing to what the model sees and costs no input tokens, but because it's still in the array, the thinking in `messages[4]` stays valid. `messages[6]` is the copy the model sees this turn. On later requests, keep both where they are and append a fresh copy after the next `tool_result` message.
770
771### Add or remove tools with `tool_addition` and `tool_removal`
772
773Editing the `tools` array mid-session invalidates preserved thinking blocks. Instead, declare every tool the session might need in `tools` on the first request and never change the array. To change which tools the model can use from some point on, append a `role: "system"` message that carries a `tool_removal` or `tool_addition` block. These are [mid-conversation tool changes](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages#mid-conversation-tool-changes) and need the beta header `mid-conversation-tool-changes-2026-07-01`. For example, to withdraw a dangerous tool after a mode switch:
218774
219775```json
220776{
from line 782
226782}
227783```
228784
229A tool whose schema you learn mid-session (an MCP server discovered at runtime) can be appended to `tools` with `defer_loading: true` and offered with `tool_addition`. An unreferenced deferred tool isn't part of the prefix, so appending it is safe. Appending a regular tool isn't.
230
231### Trim context on the server where you can
232
233Client-side truncation and summarization are the second most common edit: drop or summarize the oldest turns and keep the recent ones verbatim. The recent turns' thinking blocks were produced while the history you removed was still in place, so they fail the check. The server-side equivalents don't count as edits, because the check compares the conversation as you sent it:
234
235* [Compaction](https://platform.claude.com/docs/en/build-with-claude/compaction) summarizes older turns into a compaction block when the context approaches a threshold you set, and the checked prefix restarts from that block. Its [`instructions` parameter](https://platform.claude.com/docs/en/build-with-claude/compaction#custom-summarization-instructions) takes your own summarization prompt ("preserve every ticker, position size, and stated assumption").
236* [Context editing](https://platform.claude.com/docs/en/build-with-claude/context-editing) clears old tool results (`clear_tool_uses_20250919`) or old thinking blocks oldest-first (`clear_thinking_20251015`) by rule.
237
238### Custom compaction on the client
239
240This check doesn't prohibit client-side compaction. The rule is narrower: **don't keep a thinking block behind a prefix you've rewritten.**
241
242**Simple compaction** is the recommended shape and needs no changes. When the conversation grows too long, summarize it into one message and start the next request with that summary plus the new user turn, replaying no earlier turns or thinking blocks: `messages` becomes `[{"role": "user", "content": "<summary of the session so far>\n\n<the next instruction>"}]`. No earlier thinking remains, so nothing fails, and the model thinks afresh on the compacted conversation. Claude models are trained on long-horizon tasks with this scheme, and it performs comparably to more elaborate ones for most workloads. It resets the prompt cache at the compaction point, as any compaction does.
243
244Two other common shapes fail as written and need one change each:
245
246* **Keep-tail compaction** summarizes older turns and keeps the most recent turns verbatim. The kept turns' thinking blocks were produced against the full history, so they fail behind the summary. Fix: strip `thinking` and `redacted_thinking` from every assistant turn you carry across, keeping `text` and `tool_use`, or send `prefix_mismatch_behavior: "drop_block"` and let the API strip them.
247* **Background compaction** builds the summary off the critical path and swaps it in while the conversation continues, so every turn produced in the meantime has thinking that predates the swap. Fix: send `"drop_block"` on every request that still carries thinking blocks produced before the swap (or strip those blocks yourself; `input_transformations` on the first response after the swap lists exactly which ones), or compact synchronously.
248
249Snipping individual turns out of the middle of the transcript invalidates everything after them, and no client-side shape avoids that. Use a mid-conversation system message for the instruction change you were making, or server-side [context editing](https://platform.claude.com/docs/en/build-with-claude/context-editing) for selective removal.
250
251Don't compact in the middle of a tool round: an assistant turn whose `tool_use` is still waiting on a `tool_result` should go back with its thinking intact, so the model finishes the round with its reasoning (see [Preserving thinking blocks](https://platform.claude.com/docs/en/build-with-claude/thinking#preserving-thinking-blocks)).
252
253### Reference files by ID, not by URL that changes content
254
255For an `image` or `document` block with a `url` source, the fetched bytes are part of the checked prefix and the URL string isn't. A "latest screenshot" endpoint or an edited document invalidates later thinking. A rotating signed URL for the same file doesn't. For content you reference across turns, upload it once with the [Files API](https://platform.claude.com/docs/en/build-with-claude/files) and use the `file_id`, or send base64.
256
257### Decide what happens on a mismatch
258
259Once your integration is append-only, choose a `prefix_mismatch_behavior` for production. It governs only prefix mismatches. A block the current model can't read (after a router switch or [server-side fallback](https://platform.claude.com/docs/en/build-with-claude/refusals-and-fallback#server-side-fallback)) is always dropped, and reported in `input_transformations` when the beta header is sent.
260
261* **`"error"`** (the default) if a prefix mismatch can only mean a bug in your code. You find out from a 400 in testing rather than from silently dropped blocks. In the Message Batches API, the unset default drops failing blocks instead of failing the batch item; set `"error"` explicitly if you want items to error.
262* **`"drop_block"`** if you'd rather drop the affected blocks than fail. Log `input_transformations`.
263
264If you catch the 400 in production, retrying the same request won't clear it. Retry with `prefix_mismatch_behavior: "drop_block"` (and the beta header), which removes exactly the blocks that fail, including any in an assistant turn whose `tool_use` is still waiting on its `tool_result`. The drop applies to that request only, so keep sending `"drop_block"` (and the beta header) for the rest of the session. Without the beta, strip every `thinking` and `redacted_thinking` block from the history, leaving each turn's `text` and `tool_use` blocks in place, and retry once. Then fix the edit that caused it.
265
266## API features used on this page
267
268| Feature | What it replaces | Status | Header |
269| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ------ | --------------------------------------------- |
270| [Controls for blocks that aren't preserved](https://platform.claude.com/docs/en/build-with-claude/thinking#preserved-thinking-controls) (`thinking.block_binding.prefix_mismatch_behavior`, `input_transformations`) | Choose reject or drop on a prefix mismatch, and see what was dropped | Beta | `thinking-binding-controls-2026-08-01` |
271| [Mid-conversation system messages](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages) (`role: "system"` in `messages`) | Rebuilding the top-level `system` prompt | Stable | None |
272| [Turn-scoped system messages](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#per-turn-reminders) (`clear_at: "next_user_message"`) | Injecting a reminder and deleting it next request | Beta | `mid-conversation-system-clear-at-2026-08-21` |
273| [Mid-conversation tool changes](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages#mid-conversation-tool-changes) (`tool_addition`, `tool_removal`) | Editing the `tools` array | Beta | `mid-conversation-tool-changes-2026-07-01` |
274| [Compaction](https://platform.claude.com/docs/en/build-with-claude/compaction) (`instructions` for a custom summary prompt) | Client-side summarization of old turns | Beta | `compact-2026-01-12` |
275| [Context editing](https://platform.claude.com/docs/en/build-with-claude/context-editing) (`clear_tool_uses_20250919`, `clear_thinking_20251015`) | Client-side deletion of old tool results or thinking | Beta | `context-management-2025-06-27` |
276| [Files API](https://platform.claude.com/docs/en/build-with-claude/files) (`file_id` sources) | URLs whose content changes between requests | Stable | None |
277| [Per-message effort](https://platform.claude.com/docs/en/build-with-claude/effort#change-effort-mid-conversation-beta) (`output_config.effort` on a `role: "system"` message) | Changing top-level effort between requests (protects the prompt cache, not thinking: effort isn't part of the prefix) | Beta | `mid-conversation-output-config-2026-07-01` |
278
279To combine headers in one request:
280
281```text wrap
282anthropic-beta: thinking-binding-controls-2026-08-01,mid-conversation-system-clear-at-2026-08-21,mid-conversation-tool-changes-2026-07-01
283```
284
285The same beta names apply on Amazon Bedrock and Google Cloud. See [Beta headers](https://platform.claude.com/docs/en/api/beta-headers) for how to send them with each SDK.
286
287## Checklist
288
289* If an official Claude product or SDK (Claude Code, claude.ai, Claude Managed Agents, the Claude Agent SDK) manages your conversation history, stop here.
290* Consecutive request bodies are byte-identical in `system`, `tools`, and the shared `messages` prefix.
291* A full session under `prefix_mismatch_behavior: "drop_block"` logs no `prefix_binding_mismatch` entries.
292* Assistant turns go back byte-for-byte as returned, all block types included.
293* Top-level `system` and `tools` are fixed for the session. Changes go in `role: "system"` messages and `tool_addition` / `tool_removal` blocks.
294* Per-turn reminders are turn-scoped system messages (or trailing text blocks) that are appended fresh and never removed.
295* Context is trimmed by compaction or context editing, or by a client-side compaction that leaves no thinking blocks behind the rewritten prefix and never splits a tool round.
296* Cross-turn files are `file_id` or base64, not mutable URLs.
297* A production `prefix_mismatch_behavior` is set and its 400s or dropped entries are monitored.
785To offer a tool later instead, declare it in `tools` with `defer_loading: true` so the model doesn't see it at first. When it becomes available, append a `tool_addition` block:
786
787```json
788{
789 "role": "system",
790 "content": [
791 { "type": "tool_addition", "tool": { "type": "tool_reference", "name": "deploy" } },
792 { "type": "text", "text": "Authentication succeeded. Deployment is now available." }
793 ]
794}
795```
796
797Sometimes you can't declare a tool up front because you don't know its schema yet. An MCP server discovered at runtime is the common case. Append that tool to `tools` with `defer_loading: true`, then offer it with a `tool_addition` block. Adding a deferred tool is safe: the prefix check ignores a deferred tool until a `tool_addition` block references it, so earlier thinking stays valid. Adding a tool without `defer_loading: true` changes the prefix and invalidates earlier thinking.
798
799The `role: "system"` messages that carry these blocks join the prefix for later thinking. Leave them in place on later requests.
800
801### Change effort with a per-message `output_config`
802
803Changing top-level `output_config.effort` between requests doesn't invalidate thinking, because effort isn't part of the prefix. Changing top-level effort does restart the prompt cache. On Claude Fable 5.1, use [per-message effort](https://platform.claude.com/docs/en/build-with-claude/effort#change-effort-mid-conversation-beta) instead: append a `role: "system"` message with empty `content` and the new level. It needs the beta header `mid-conversation-output-config-2026-07-01`.
804
805```json
806{ "role": "system", "content": [], "output_config": { "effort": "low" } }
807```
808
809The new level takes effect from the next `user` turn. Once sent, the message is part of `messages` and therefore part of the prefix for later thinking: leave it in place on later requests, and append another one to change effort again.
810
811### Trim context on the server
812
813The second most common prefix edit is client-side trimming: dropping or summarizing the oldest turns and keeping the recent ones verbatim. The kept turns' thinking blocks were produced while the removed history was still in place, so they fail the check. The server-side equivalents don't count as edits, because the check compares the conversation as you sent it:
814
815* [Compaction](https://platform.claude.com/docs/en/build-with-claude/compaction) summarizes older turns into a compaction block when the context approaches a threshold you set, and the checked prefix restarts from that block. Its [`instructions` parameter](https://platform.claude.com/docs/en/build-with-claude/compaction#custom-summarization-instructions) takes your own summarization prompt, such as "preserve every ticker, position size, and stated assumption".
816* [Context editing](https://platform.claude.com/docs/en/build-with-claude/context-editing) clears old tool results or old thinking blocks by rule, oldest first. The strategies are `clear_tool_uses_20250919` and `clear_thinking_20251015`.
817
818### Compact on the client
819
820You can still compact on the client. Once you rewrite anything earlier in the conversation, don't send back a thinking block that was produced before the rewrite.
821
822#### Simple compaction (recommended)
823
824When the conversation grows too long, summarize the whole session into one user message and send only that message plus the next instruction. Nothing earlier is replayed, so there's no thinking left to fail the check, and the model reasons afresh from the summary.
825
826
827
828```json
829[
830 {
831 "role": "user",
832 "content": "<summary of the session so far>\n\n<the next instruction>"
833 }
834]
835```
836
837Claude models are trained on long-horizon tasks with this scheme and for most workloads it performs well.
838
839#### Keep-tail compaction
840
841Keep-tail compaction summarizes the older turns and keeps the most recent turns verbatim, so the model still sees the last few exchanges word for word. As usually written it breaks the rule: the kept assistant turns still carry thinking blocks that were produced when the original turns, not the summary, came before them. Those blocks fail.
842
843
844
845Fix: keep the turns exactly as they are and send `prefix_mismatch_behavior: "drop_block"`. The API drops the stale thinking blocks, the model reads the kept turns' `text` and `tool_use` blocks, and the request succeeds.
846
847Pass the compacted history as `messages` and set `block_binding` on the `thinking` configuration. In the following example, `compacted_messages` is the array your compaction step produced: the summary message followed by the kept turns exactly as the API returned them, `thinking` blocks included:
848
849<CodeGroup>
850 ```bash cURL
851 curl https://api.anthropic.com/v1/messages \
852 -H "content-type: application/json" \
853 -H "x-api-key: $ANTHROPIC_API_KEY" \
854 -H "anthropic-version: 2023-06-01" \
855 -H "anthropic-beta: thinking-binding-controls-2026-08-01" \
856 -d "{
857 \"model\": \"claude-fable-5-1\",
858 \"max_tokens\": 16000,
859 \"thinking\": {
860 \"type\": \"adaptive\",
861 \"block_binding\": { \"prefix_mismatch_behavior\": \"drop_block\" }
862 },
863 \"messages\": $COMPACTED_MESSAGES
864 }"
865 ```
866
867 ```bash CLI
868 ant beta:messages create --beta thinking-binding-controls-2026-08-01 <<YAML
869 model: claude-fable-5-1
870 max_tokens: 16000
871 thinking:
872 type: adaptive
873 block_binding:
874 prefix_mismatch_behavior: drop_block
875 messages: $COMPACTED_MESSAGES
876 YAML
877 ```
878
879 ```python Python
880 client = anthropic.Anthropic()
881
882 # compacted_messages: the summary message, then the kept turns as returned
883 response = client.beta.messages.create(
884 model="claude-fable-5-1",
885 max_tokens=16000,
886 thinking={
887 "type": "adaptive",
888 "block_binding": {"prefix_mismatch_behavior": "drop_block"},
889 },
890 messages=compacted_messages,
891 betas=["thinking-binding-controls-2026-08-01"],
892 )
893
894 print(response.input_transformations)
895 ```
896
897 ```typescript TypeScript
898 const client = new Anthropic();
899
900 // compactedMessages: the summary message, then the kept turns as returned
901 const response = await client.beta.messages.create({
902 model: "claude-fable-5-1",
903 max_tokens: 16000,
904 thinking: {
905 type: "adaptive",
906 block_binding: { prefix_mismatch_behavior: "drop_block" }
907 },
908 messages: compactedMessages,
909 betas: ["thinking-binding-controls-2026-08-01"]
910 });
911
912 console.log(response.input_transformations);
913 ```
914
915 ```csharp C#
916 AnthropicClient client = new();
917
918 // compactedMessages: the summary message, then the kept turns as returned
919 var response = await client.Beta.Messages.Create(
920 new()
921 {
922 Model = "claude-fable-5-1",
923 MaxTokens = 16000,
924 Thinking = new BetaThinkingConfigAdaptive
925 {
926 BlockBinding = new()
927 {
928 PrefixMismatchBehavior = BetaThinkingPrefixMismatchBehavior.DropBlock,
929 },
930 },
931 Messages = compactedMessages,
932 Betas = [AnthropicBeta.ThinkingBindingControls2026_08_01],
933 }
934 );
935
936 Console.WriteLine(response.InputTransformations?.Count ?? 0);
937 ```
938
939 ```go Go
940 client := anthropic.NewClient()
941
942 // compactedMessages: the summary message, then the kept turns as returned
943 response, err := client.Beta.Messages.New(context.TODO(), anthropic.BetaMessageNewParams{
944 Model: "claude-fable-5-1",
945 MaxTokens: 16000,
946 Thinking: anthropic.BetaThinkingConfigParamUnion{
947 OfAdaptive: &anthropic.BetaThinkingConfigAdaptiveParam{
948 BlockBinding: anthropic.BetaThinkingBlockBindingParam{
949 PrefixMismatchBehavior: anthropic.BetaThinkingPrefixMismatchBehaviorDropBlock,
950 },
951 },
952 },
953 Messages: compactedMessages,
954 Betas: []anthropic.AnthropicBeta{anthropic.AnthropicBetaThinkingBindingControls2026_08_01},
955 })
956 if err != nil {
957 log.Fatal(err)
958 }
959
960 fmt.Println(len(response.InputTransformations))
961 ```
962
963 ```java Java
964 import com.anthropic.models.beta.AnthropicBeta;
965 import com.anthropic.models.beta.messages.BetaMessage;
966 import com.anthropic.models.beta.messages.BetaThinkingBlockBinding;
967 import com.anthropic.models.beta.messages.BetaThinkingConfigAdaptive;
968 import com.anthropic.models.beta.messages.BetaThinkingPrefixMismatchBehavior;
969 import com.anthropic.models.beta.messages.MessageCreateParams;
970
971 void main() {
972 AnthropicClient client = AnthropicOkHttpClient.fromEnv();
973
974 // compactedMessages: the summary message, then the kept turns as returned
975 MessageCreateParams params = MessageCreateParams.builder()
976 .model("claude-fable-5-1")
977 .maxTokens(16000L)
978 .thinking(BetaThinkingConfigAdaptive.builder()
979 .blockBinding(BetaThinkingBlockBinding.builder()
980 .prefixMismatchBehavior(BetaThinkingPrefixMismatchBehavior.DROP_BLOCK)
981 .build())
982 .build())
983 .messages(compactedMessages)
984 .addBeta(AnthropicBeta.THINKING_BINDING_CONTROLS_2026_08_01)
985 .build();
986
987 BetaMessage response = client.beta().messages().create(params);
988
989 IO.println(response.inputTransformations());
990 }
991 ```
992
993 ```php PHP
994 use Anthropic\Beta\AnthropicBeta;
995 use Anthropic\Beta\Messages\BetaThinkingBlockBinding;
996 use Anthropic\Beta\Messages\BetaThinkingConfigAdaptive;
997 use Anthropic\Beta\Messages\BetaThinkingPrefixMismatchBehavior;
998 use Anthropic\Client;
999
1000 $client = new Client();
1001
1002 // $compactedMessages: the summary message, then the kept turns as returned
1003 $response = $client->beta->messages->create(
1004 model: 'claude-fable-5-1',
1005 maxTokens: 16000,
1006 thinking: BetaThinkingConfigAdaptive::with(
1007 blockBinding: BetaThinkingBlockBinding::with(
1008 prefixMismatchBehavior: BetaThinkingPrefixMismatchBehavior::DROP_BLOCK,
1009 ),
1010 ),
1011 messages: $compactedMessages,
1012 betas: [AnthropicBeta::THINKING_BINDING_CONTROLS_2026_08_01],
1013 );
1014
1015 var_dump($response->inputTransformations);
1016 ```
1017
1018 ```ruby Ruby
1019 client = Anthropic::Client.new
1020
1021 # compacted_messages: the summary message, then the kept turns as returned
1022 response = client.beta.messages.create(
1023 model: "claude-fable-5-1",
1024 max_tokens: 16_000,
1025 thinking: {
1026 type: "adaptive",
1027 block_binding: {prefix_mismatch_behavior: "drop_block"}
1028 },
1029 messages: compacted_messages,
1030 betas: [Anthropic::AnthropicBeta::THINKING_BINDING_CONTROLS_2026_08_01]
1031 )
1032
1033 puts response.input_transformations
1034 ```
1035</CodeGroup>
1036
1037The response carries the new assistant turn as usual, plus one `input_transformations` entry per dropped block. For the history in the diagram, that's the thinking on assistant turns 3 and 4:
1038
1039```json
1040{
1041 "input_transformations": [
1042 {
1043 "type": "thinking_dropped",
1044 "path": "messages.2.content.0",
1045 "reason": "prefix_binding_mismatch"
1046 },
1047 {
1048 "type": "thinking_dropped",
1049 "path": "messages.4.content.0",
1050 "reason": "prefix_binding_mismatch"
1051 }
1052 ]
1053}
1054```
1055
1056Keep sending `"drop_block"` on later requests for as long as those two turns stay in the history. Thinking the model produces from this request onward follows the summary and stays valid. If you'd rather not depend on the beta header, the alternative is to strip the `thinking` and `redacted_thinking` blocks from the kept assistant turns yourself when you build the compacted history.
1057
1058#### Patterns that don't work with preserved thinking
1059
1060* **Background compaction.** Building the summary off the critical path and swapping it in a few requests later breaks the rule the same way keep-tail does, with a delay: every assistant turn produced while the summary was being built carries thinking that predates the swap, and it all fails the moment the summary lands. If you need it, treat the swap like keep-tail and send `"drop_block"` from the swap onward. Otherwise compact synchronously.
1061* **Cutting turns out of the middle.** Removing individual turns invalidates every thinking block after them, and no compaction scheme avoids that. If you were cutting a turn to change an instruction, append a [mid-conversation system message](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#new-instructions) instead. To remove old tool results or old thinking selectively, use server-side [context editing](https://platform.claude.com/docs/en/build-with-claude/context-editing).
1062* **Compacting in the middle of a tool round.** Don't compact between an assistant turn's `tool_use` and the `tool_result` that answers it. Send that assistant turn back with its thinking intact so the model finishes the round with its reasoning. See [Preserving thinking blocks](https://platform.claude.com/docs/en/build-with-claude/thinking#preserving-thinking-blocks).
1063
1064### Reference files by ID, not by a URL whose content changes
1065
1066For an `image` or `document` block with a `url` source, the check covers the fetched bytes, not the URL string. A URL whose content changes invalidates later thinking: a "latest screenshot" endpoint, or a document someone edits between turns. A rotating signed URL for the same file doesn't. For content you reference across turns, upload it once with the [Files API](https://platform.claude.com/docs/en/build-with-claude/files) and use the `file_id`, or send base64.
2981067
2991068## FAQ
3001069
3011070<AccordionGroup>
302 <Accordion title="Do I need a new account to test the check?">
303 No. Send the `thinking-binding-controls-2026-08-01` beta header and set `thinking.block_binding.prefix_mismatch_behavior`. Setting the field opts that request into enforcement regardless of account age: `"error"` rejects an edited history with the same 400 a new account gets, and `"drop_block"` lets the request through and lists what was dropped in `input_transformations`. See [How to tell whether your integration is impacted](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#how-to-tell-whether-your-integration-is-impacted).
1071 <Accordion title="Do I need a new account to test preserved thinking?">
1072 No. Send the `thinking-binding-controls-2026-08-01` beta header and set `thinking.block_binding.prefix_mismatch_behavior`. Setting the field opts that request into enforcement regardless of account age. `"error"` rejects an edited history with the same 400 a new account gets, and `"drop_block"` lets the request through and lists what was dropped in `input_transformations`. See [Check whether your code edits the prefix](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#how-to-tell-whether-your-integration-is-impacted).
3041073 </Accordion>
3051074
3061075 <Accordion title="If anything before a thinking block changes, even one tool description, is the conversation unusable?">
307 No. What fails is the thinking already in the history after the point you changed, and you choose what happens to it. With `prefix_mismatch_behavior: "drop_block"` the API drops those blocks and the request succeeds: the model answers that turn without that reasoning, and the prompt cache restarts at the edit. With the default `"error"` the API rejects the request with a 400 until you undo the edit or resend with `"drop_block"`; see [Decide what happens on a mismatch](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#decide-what-happens-on-a-mismatch). [What counts as an edit](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#what-counts-as-an-edit) lists which changes matter.
1076 No. What fails is the thinking already in the history after the point you changed, and you choose what happens to it. With `prefix_mismatch_behavior: "drop_block"`, the API drops those blocks and the request succeeds: the model answers that turn without that reasoning, and the prompt cache restarts at the edit. With the default `"error"`, the API rejects the request with a 400 until you undo the edit or resend with `"drop_block"`. See [What the API does with an invalid block](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#mismatch-behavior). [What counts as an edit](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#what-counts-as-an-edit) lists which changes matter.
3081077 </Accordion>
3091078
3101079 <Accordion title="Does changing effort or other thinking settings between requests invalidate earlier thinking?">
311 No. `output_config.effort`, `max_tokens`, and the `thinking` configuration aren't part of the checked prefix, which covers only `system`, `tools`, and `messages`. A top-level effort change still invalidates most of the prompt cache; on Claude Fable 5.1, a [per-message effort](https://platform.claude.com/docs/en/build-with-claude/effort#change-effort-mid-conversation-beta) change keeps it. Once sent, that effort message is part of the history: leave it in place on later requests.
1080 No. `output_config.effort`, `max_tokens`, and the `thinking` configuration aren't part of the checked prefix, which covers only `system`, `tools`, and `messages`. A top-level effort change invalidates most of the prompt cache. On Claude Fable 5.1, a [per-message effort](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#effort-changes) change keeps the prompt cache and is used as the new effort level until changed again.
3121081 </Accordion>
3131082
3141083 <Accordion title="My tool list changes mid-session. How do I avoid invalidating the conversation?">
315 Don't edit `tools`: declare the full set at session start, mark tools that aren't available yet `defer_loading: true`, and offer or withdraw them with `tool_addition` and `tool_removal` blocks. A tool whose schema you learn only mid-session, such as one from an MCP server discovered at runtime, can still be appended to `tools` with `defer_loading: true` and offered the same way, because an unreferenced deferred tool isn't part of the prefix. The `role: "system"` messages that carry these blocks join the prefix for later thinking, so don't move, reword, or delete them afterward. See [Change tools with `tool_addition` and `tool_removal`](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#change-tools-with-tool-addition-and-tool-removal-not-by-editing-tools).
1084 Don't edit `tools`. Declare the full set at session start, mark tools that aren't available yet with `defer_loading: true`, and offer or withdraw them with `tool_addition` and `tool_removal` blocks. If you learn a tool's schema only mid-session, such as from an MCP server discovered at runtime, you can still append it to `tools` with `defer_loading: true` and offer it the same way. That's safe because an unreferenced deferred tool isn't part of the prefix. The `role: "system"` messages that carry these blocks join the prefix for later thinking, so don't move, reword, or delete them afterward. See [Add or remove tools with `tool_addition` and `tool_removal`](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#tool-changes).
3161085 </Accordion>
3171086
3181087 <Accordion title="I compact by summarizing older turns and keeping recent turns verbatim. Does that still work?">
319 Not if the kept turns still carry their thinking: those blocks were produced against the history you replaced, so they fail the check. Strip `thinking` and `redacted_thinking` from the turns you carry across (their `text` and `tool_use` blocks stay), or send `prefix_mismatch_behavior: "drop_block"` and let the API drop them. Simple compaction (one summary message plus the next user turn, no earlier turns replayed) leaves no thinking behind to fail and is the recommended shape. Server-side [compaction](https://platform.claude.com/docs/en/build-with-claude/compaction) and [context editing](https://platform.claude.com/docs/en/build-with-claude/context-editing) don't count as edits. See [Custom compaction on the client](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#custom-compaction-on-the-client).
1088 Not if the kept turns still carry their thinking: those blocks were produced against the history you replaced, so they fail the check. Strip `thinking` and `redacted_thinking` blocks from the turns you carry across and keep their `text` and `tool_use` blocks, or send `prefix_mismatch_behavior: "drop_block"` and let the API drop them. Simple compaction leaves no thinking behind to fail and is the recommended approach: one summary message plus the next user turn, with no earlier turns replayed. Server-side [compaction](https://platform.claude.com/docs/en/build-with-claude/compaction) and [context editing](https://platform.claude.com/docs/en/build-with-claude/context-editing) don't count as edits. See [Compact on the client](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#custom-compaction-on-the-client).
3201089 </Accordion>
3211090
3221091 <Accordion title="How do I handle instruction files such as AGENTS.md or CLAUDE.md that change mid-session?">
323 Load them once at session start and keep the top-level `system` prompt and `tools` fixed. When a file changes, append the new version at that point in `messages` instead of editing the original: a [mid-conversation system message](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages) for instructions that come from you as the operator, or content in the next `user` turn for file text you treat as untrusted, which shouldn't carry system-prompt authority. See [Add instructions with a mid-conversation system message](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#add-instructions-with-a-mid-conversation-system-message-not-by-editing-system) and [Limitations](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages#limitations).
1092 Load them once at session start and keep the top-level `system` prompt and `tools` fixed. When a file changes, append the new version at that point in `messages` instead of editing the original. Use a [mid-conversation system message](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages) for instructions that come from you as the operator. For file text you treat as untrusted, which shouldn't carry system-prompt authority, put the content in the next `user` turn instead. See [Add instructions with a mid-conversation system message](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#new-instructions) and [Limitations](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages#limitations).
3241093 </Accordion>
3251094
3261095 <Accordion title="Can I resume a saved session later, after a restart or the next day?">
327 Yes. A resumed session is an ordinary follow-up request: `system`, `tools`, and the earlier `messages` must match what you last sent byte-for-byte. Persist what you sent and received (the rendered system prompt, the tool definitions, each assistant turn as returned) and replay that, rather than re-rendering from inputs that might have changed since, such as the date, an updated instruction file, or a new tool version. Anything new goes in an appended message. See [Append assistant turns exactly as returned](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#append-assistant-turns-exactly-as-returned).
328 </Accordion>
329
330 <Accordion title="What happens to thinking when the conversation moves from Claude Fable 5.1 to an older model and back?">
331 Keep sending the full history and let the API decide on each request. An older model can't read Claude Fable 5.1's thinking blocks, so the API leaves them out of what that model sees for that one request (no error, not billed, and reported as `model_binding_mismatch` in `input_transformations` when you send the beta header); it never edits your `messages` array, so the blocks stay in your history. When the same history goes back to Claude Fable 5.1, those blocks are readable again, along with the older model's thinking. The reasoning is lost only if your client removes the blocks itself, for example a harness that strips thinking on a model switch or rebuilds the history from what each model used. See [Only for the model that produced it, or a newer one](https://platform.claude.com/docs/en/build-with-claude/thinking#preserved-for-model) for which models read which blocks.
332
333 
1096 Yes. A resumed session is an ordinary follow-up request: `system`, `tools`, and the earlier `messages` must match what you last sent byte-for-byte. Persist exactly what you sent and received, and replay that: the rendered system prompt, the tool definitions, and each assistant turn as returned. Don't re-render from inputs that might have changed since, such as the date, an updated instruction file, or a new tool version. Anything new goes in an appended message. See [Send assistant turns back exactly as returned](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#append-assistant-turns-exactly-as-returned).
3341097 </Accordion>
3351098
3361099 <Accordion title="My harness can route a turn to a non-Claude model. Do those turns invalidate Claude's earlier thinking?">
from line 1101
3381101 </Accordion>
3391102
3401103 <Accordion title="Can I carry a conversation's reasoning into a new conversation?">
341 Not into a different conversation. A thinking block is usable only behind the exact `system`, `tools`, and `messages` it was produced from, so a branch that replays that history unchanged up to the fork point keeps its thinking, and a conversation that starts from anything else can't use it. Start that one from a summary of the task state (the goal, decisions made, files and results so far, and the next step), as in [simple compaction](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#custom-compaction-on-the-client).
1104 Not into a different conversation. A thinking block is usable only when it follows the exact `system`, `tools`, and `messages` it was produced from. A branch that replays that history unchanged up to the fork point keeps its thinking. A conversation that starts from anything else can't use it, so start that conversation from a summary of the task state, as in [simple compaction](https://platform.claude.com/docs/en/build-with-claude/preserved-thinking#custom-compaction-on-the-client): the goal, decisions made, files and results so far, and the next step.
3421105 </Accordion>
3431106</AccordionGroup>
3441107
3451108