Claude Media
Compactionで残したthinking blockが無効になる条件と検証手順

Compactionで残したthinking blockが無効になる条件と検証手順

on-demand compactionで残したターンのthinking blockを有効に保つ3条件と、破ったときに次のリクエストで400になる仕組み、input_transformationsで確かめるテスト手順をまとめます。

on-demand compactionで直近のターンを残し、そのターンのthinkingブロックも送り返している実装は、3つの条件を満たしているあいだだけ動きます。条件が崩れても、compactionのレスポンスはエラーになりません。壊れたと分かるのは、そのブロックを次に送り返したリクエストです。

この記事は、その3条件と、崩れたときの挙動、テストで確かめる手順を扱います。対象は、preserved thinkingを持つモデルにthinkingブロックを送り返し、compactionブロックの後ろにターンを残す実装だけです。それ以外の人は読み飛ばして構いません。

対象になる実装と前提

「残したターン」とは、compactionブロックの後ろに続くターンです。要約リクエストに含めず手元に残した直近のターンと、要約の生成中に届いたターンがこれに当たります。前者の実装はCompactionブロックを会話へ戻す実装パターンの応用です。

preserved thinkingを持つモデルは、送り返された過去のthinkingブロックを、それを生んだ会話と照らして検証します。要約は会話の一部を置き換えますが、要約をAPI自身が書いた場合は、この置き換えを検証が受け入れます。だから残したターンのthinkingは有効なままでいられます。検証の仕組み自体はPreserved thinkingとはにまとめてあります。

compactionはcompact-2026-09-04ベータヘッダーの機能です。対応モデルには、Claude Fable 5.1、Claude Opus 5.5、Claude Sonnet 5.5などが並びます。Amazon Bedrockでは提供されていません。

残したthinkingが有効な3条件

次の3つがすべて成り立つあいだ、残したターンのthinkingブロックは有効です。

条件中身破れやすい場面
要約リクエストのモデル中身要約リクエストがpreserved thinkingを持つモデルで走る破れやすい場面要約だけ安いモデルへ振り分ける
残したターンの連続性と無改変中身要約した最後のメッセージの直後から、そのまま送る破れやすい場面メッセージの間引き・挿入、role: "system"の割り込み
systemとtoolsの不変中身要約リクエストと、その後のリクエストで同一破れやすい場面日付入りのsystem組み立て、ツールの動的追加

「破れやすい場面」は公式の条件文から導いた例で、公式が挙げた失敗例そのものではありません。

条件1: 要約リクエストはpreserved thinkingを持つモデルで走らせる

この条件は、直近の要約だけでなく、そのthinkingブロックが生まれて以降のすべての要約リクエストに掛かります。二度compactionして同じターンを残し続けたなら、二度とも該当モデルでなければなりません。会話で使っているモデルへ要約も投げる、という運用が最も単純な満たし方です。

条件2: 残したターンは連続していて、無改変で送る

残したメッセージは、履歴にあるとおりに送ります。要約した最後のメッセージと、残した最初のメッセージのあいだに、飛ばしたり足したりしてはいけません。

さらに、残した最初のメッセージは、要約した最後のメッセージと別のroleである必要があります。会話の途中に挟むrole: "system"メッセージも、最初の残しメッセージにはなれません。同じroleだと、APIはそれを要約側の最後のメッセージへ結合してしまいます。

最初の残しメッセージを確実に正しくする方法が、公式に1つ示されています。すでに送ったリクエストのmessagesをそのまま要約対象にするのです。そうすると残るターンは、そのリクエストに対するClaudeの返答から始まります。

条件3: systemと、defer_loading: trueでないtoolsは変えない

対象は、defer_loading: trueが付いていないtoolsとsystemです。要約リクエストでも、残したthinkingを作ったリクエストでも、その後に続くリクエストでも、同じ内容でなければなりません。

条件が崩れたとき、いつ何が起きるか

条件のどれが崩れても、compactionを実行した時点では何も失敗しません。後続のリクエストでAPIが要約ブロックを受け入れることも変わりません。失敗するのは、検証が働く場で、残したthinkingを初めて送り返したリクエストです。挙動はthinking.block_binding.prefix_mismatch_behaviorで決まります。

設定値挙動
"error"(既定)挙動400のinvalid_request_error。最初に失敗したブロックのパスから始まるメッセージが返る
"drop_block"挙動失敗したブロックと、以降のthinkingブロックを捨てて、リクエストは成功する。捨てたブロックは課金されない

Message Batches APIでは扱いが違います。この項目を未設定のまま送ったバッチ項目は、失敗しません。検証が既定で働く場では、失敗したブロックが捨てられます。バッチ項目を失敗させたいなら"error"を明示します。

400のメッセージは次の形で始まります。

messages.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".

末尾には、systemやtoolsが生成時と違うといった、変更点を名指しする一文が付くことがあります。署名が改竄された、あるいは復号できない場合は別の失敗です。こちらは常に400で、prefix_mismatch_behaviorの対象外になります。エラー文言から原因を辿る手順はthinking blocks cannot be modifiedエラーの直し方に別にあります。

検証が働くのはどのアカウントか

検証が効く範囲は、アカウントの作成時期で分かれます。

  • 2026年8月31日00:00 UTC以降に作ったアカウント: Claude Fable 5.1、Claude Opus 5.5、Claude Sonnet 5.5への要求を検証し、"drop_block"を指定しない限り"error"が適用される
  • それより古いアカウント: prefix_mismatch_behaviorを設定したリクエストだけが検証を受ける。未設定のリクエストでは検証は走るものの、失敗したブロックをモデルへ通す

古いアカウントで何も起きていないことは、実装が安全な証拠になりません。他人の環境で動くツールなら、新しいアカウントの利用者が先に400を受け取ります。だからテストでは、次に示すとおりprefix_mismatch_behaviorを自分で設定して、新しいアカウントと同じ条件を再現します。

残したthinkingが有効かをテストで確かめる

compactionのレスポンスは、残したthinkingが有効かどうかを教えてくれません。答えは、compaction後の最初のリクエストにあります。テストは4段階です。

  1. thinkingを有効にして短い会話をする。検証が走るモデルを選び、全段階で同じモデルを使う。読み取れないモデルは、エラーなしでthinkingを捨てるため
  2. 古いターンを要約し、thinkingブロックを持つターンを少なくとも1つ残す
  3. 次のリクエストを、compactionブロック、残したターン、新しいuserメッセージの順で作り、prefix_mismatch_behaviorを"error"にして送る
  4. 結果を読む。200でinput_transformationsが空なら、失敗したブロックも捨てられたブロックもない。400で「別の会話に紐づく」旨が出たら、どこかが崩れている

prefix_mismatch_behaviorにはthinking-binding-controls-2026-08-01ベータヘッダーが要ります。compactionのcompact-2026-09-04と併せて2本を送ります。このフィールドを設定すると、検証が既定で働かないアカウントでも、そのリクエストは検証の対象になります。

公式のPythonの例から、要点の部分だけを抜き出すと次の形です。

MODEL = "claude-fable-5-1"
BETAS = ["compact-2026-09-04", "thinking-binding-controls-2026-08-01"]
THINKING = {
    "type": "adaptive",
    "block_binding": {"prefix_mismatch_behavior": "error"},
}
 
# 2. 最初のターンだけを要約する(2ターン目は要約リクエストに入れない)
summary = client.beta.messages.create(
    model=MODEL, max_tokens=4096, system=SYSTEM, betas=BETAS,
    thinking=THINKING, messages=history[:2],
    compaction={"type": "summarize"},
)
 
# 3. ブロック、残したターン、新しい質問の順に並べて送る
history = [
    {"role": "assistant", "content": summary.content},
    *history[2:],
    {"role": "user", "content": "Which day should the release go out?"},
]
third = client.beta.messages.create(
    model=MODEL, max_tokens=8192, system=SYSTEM, betas=BETAS,
    thinking=THINKING, messages=history,
)
 
# 4. 空なら、残したthinkingは有効なまま
print(len(third.input_transformations))

history[:2]とhistory[2:]の切れ目が、条件2の「要約した最後のメッセージの直後から残す」に当たります。公式の完全な例は、thinkingブロックの数を数える手順も含めて8言語で載っており、期待される出力は次の2行です。

Thinking blocks in the kept turn: 1
Dropped thinking blocks: 0

本番では"drop_block"にして、input_transformationsを監視する

本番では、"drop_block"にすれば、条件が崩れてもリクエストは成功します。捨てたブロックは、input_transformationsにreason: "prefix_binding_mismatch"つきで1件ずつ載ります。そのpathが残したターンの中を指していれば、そのターンのthinkingは有効に保たれていません。

ただし"drop_block"は、エラーを隠すだけで原因は直しません。捨てられたブロックは課金されませんが、Claudeが考え直すぶん、セッション全体のトークン使用量が増えることがあります。増え方は、捨てるブロックが多いほど、また長いセッションの多くのターンで捨てるほど大きくなります。prefix_binding_mismatchのエントリを持つレスポンスを、セッションごとに数えて通知に流すのが公式の勧める運用です。

compactionを重ねる場合と、system・toolsを変えたい場合

もう一度compactionしても、古いthinkingは壊れない

再度compactionして、ターンを残すこともできます。新しいブロックは、古い要約と、要約リクエストに含めたそれ以降のメッセージをすべて覆います。要約リクエストに入れなかったターンが、新しいブロックの残したターンになります。

条件1は、thinkingブロックが生まれて以降のすべてのcompactionに掛かります。2回のcompactionをまたいで残したターンには、2回とも該当モデルでの要約が要ります。thinkingが生まれる前のcompactionは数えません。ブロックが置かれたあとに生まれたthinkingは、そのブロックに紐づき、条件を満たすその後のcompactionをくぐっても有効です。

systemやtoolsを変えたいときは、全部を要約してから

後続のリクエストでsystem、tools、モデルを変えても、APIはブロックを受け入れます。変えたことで無効になるのは、残したターンのthinkingだけで、ほかの影響はありません。

一つも無効にしたくないなら、会話全体を先にcompactionして、残すターンをゼロにします。変更は、その次のリクエストで行います。systemやtoolsに手を入れずに指示やツールを増減したいときは、変更内容をmessagesへ追記する方法があります。これはPreserved thinkingとはでも触れた「prefixを編集しない」方針の実装です。

要約された範囲にあった会話途中のrole: "system"メッセージは、要約されるため、その指示は要約後に効力を失います。効かせ続けたいなら、残したターンの後ろに来る最初の新しいuserターンの直後に、role: "system"メッセージでもう一度書きます。要約範囲内のツール変更は、要約リクエストにinline-tools-2026-09-15を付けていれば自動で引き継がれます。返るブロックのtool_changesフィールドに正味の変更が記録されるので、ブロックは無改変で送り返します。フィールドが無いときは、同じ方法で言い直します。ブロックと残したターンのあいだにsystemメッセージを置くと、残したターンのthinkingは壊れます。

どこで切ればいいか

切れ目は、ツール呼び出しが未完のまま残らない位置にします。要約リクエストのmessagesが、結果のまだ無いツール呼び出しを持つassistantターンで終わると、APIは要約リクエストを拒否します。

条件2を満たす切り方として公式が示すのは2つです。返答と次のuserメッセージのあいだで切る。あるいは、すでに送ったリクエストの末尾で切る。後者では、そのリクエストのmessagesをそのまま要約し、それ以降に履歴へ増えた分をすべて残します。残すターンを増やすほど、compactionで空く枠は小さくなります。

まとめ

残したターンのthinkingが効くかどうかは、要約のモデル、残したターンの連続性、systemとtoolsの3点で決まります。崩れても、compactionの時点では何も起きず、次に検証が働いたリクエストが失敗します。だからテストでは、compaction後の最初のリクエストに"error"を付けて、200と空のinput_transformationsを確かめます。本番では"drop_block"で止まらずに動かし、prefix_binding_mismatchの件数を見張ります。

この記事を共有:XはてブLinkedIn