Claude API の mid-conversation system messages(Opus 4.8)— prompt cache を壊さず会話の途中で system 指示を足す

Opus 4.8 で、messages 配列の途中に role: "system" を差し込めるようになりました(Claude API / Claude Platform on AWS、beta ヘッダ不要)。トップレベル system を書き換えると prompt cache が全部外れますが、末尾に system メッセージを追記すればキャッシュ済みのプレフィックスはそのまま。配置ルール(400 になる位置)・operator 権限・エージェントループでの活用・キャッシュ併用を実装目線でまとめます。

長時間の会話の途中で「ここから先はこの制約を守らせたい」と思ったとき、これまではトップレベルの system フィールドを書き換えるしかありませんでした。ところが system はプロンプトの最先頭にあるため、1 文足すだけでprompt cache のハッシュが変わり、それ以降のキャッシュが全部外れます

Opus 4.8 で入った mid-conversation system messages は、この穴を塞ぐ機能です。messages 配列の末尾に role: "system" を追記することで、キャッシュ済みのプレフィックスはそのままに、新しい指示を「system(=アプリ運営者)」の重みで適用できます。移行先 Opus 4.8 の全体像は Claude Opus 4.8 発表 を参照してください。

対応範囲(まず前提)

  • Opus 4.8 のみbeta ヘッダ不要
  • Claude APIClaude Platform on AWS で利用可能。Amazon Bedrock / Vertex AI / Microsoft Foundry では利用不可

旧モデル(Opus 4.7 以前)は messages 内の role: "system"400 エラーで拒否します。

なぜキャッシュが効くのか

prompt cache はリクエストのプレフィックスを toolssystemmessages の順でハッシュします。キャッシュヒットには、ブレークポイントまでがバイト単位で一致している必要があります。

  • トップレベル system はハッシュのほぼ先頭。ここに 1 文足すと別ハッシュになり、system 以降の全キャッシュがミス
  • mid-conversation system message は指示を messages の末尾に置きます。それより前は一切変わらないので既存キャッシュエントリが一致し、新しいメッセージだけが新規入力として処理されます。

どう書くか

messages 配列に {"role": "system"} を追加するだけです。contentuser / assistant と同様、文字列でもコンテンツブロックでも構いません。

import anthropic

client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=1024,
    cache_control={"type": "ephemeral"},  # 自動キャッシュを有効化
    system="You are a code review assistant. Be concise.",
    messages=[
        {"role": "user", "content": "Review process() in utils.py for performance issues."},
        {"role": "assistant", "content": "For large inputs, consider a generator..."},
        {"role": "user", "content": "Now review the calling code that invokes process()."},
        # 途中で「型注釈を必須にする」ポリシーが決まった。
        # ここに追記すれば前のターンはバイト一致のまま → キャッシュが効く。
        {"role": "system", "content": "From now on, every suggestion must include explicit type annotations."},
    ],
)

指示が競合した場合の優先順位は、後の system が先の system に勝ちmid-conversation system はそれ以降のターンでトップレベル system に勝ちます。会話全体に効かせたい指示は引き続きトップレベル system に置き、「途中から必要になる指示」だけを mid-conversation system に回すのが基本設計です。

system と user の違い:operator 権限

「同じ指示なら user メッセージで足してもいいのでは?」という疑問が当然出ます。違いは優先度です。

  • user メッセージ = エンドユーザーからの入力として扱われる
  • system メッセージ = **アプリ運営者(あなた)**からの入力として扱われる

両者が競合したときは system が優先します。「エンドユーザーが何を言っても守らせたい運営者レベルの事実・制約」は system ロールで、しかもキャッシュミスのコストを払わずに足せる、というのが本機能の価値です。

何に使えるか

  • セッション途中のポリシー/ペルソナ変更 — 数十ターン進んだ後に「ここから SQL は必ずパラメータ化クエリで書く」を足す。トップレベル system を書き換えると全履歴が再処理される
  • その時点で権威を持たせたい文脈 — フレッシュネス情報・セッション期限・ツール可用性の変化を system レベルで注入。頻繁に変わるのでキャッシュ済みプレフィックスには置きたくない
  • アプリが観測した状態変化 — ディスク上のファイル変更、ユーザーが auto-approve を切り替えた、利用可能ツールが変わった、残トークン予算が閾値を下回った、といった「運営者レベルの事実」
  • エージェントループを中断させたくないユーザー入力 — Claude がツール実行中にユーザーが追記したメッセージを、次の tool result の後に system として中継すると、ターンを再起動せずに進行中の作業へ織り込める
  • standing permission を与えるモード切替 — セッションレベルのモードで、高コストな機能(例:マルチエージェント workflow の自動起動)への恒常的な同意を与え、数ターンごとに短く再掲し、モード解除時に終了を告げる

エージェントループでの配置

ツール実行ループでは、tool result を届ける user メッセージの直後に system メッセージを置きます。ここは、Claude が作業中にユーザーが打ったメッセージを中継する場所としても使えます。

[
  { "role": "user", "content": "Run the test suite and fix any failures." },
  { "role": "assistant",
    "content": [{ "type": "tool_use", "id": "toolu_01", "name": "run_tests", "input": {} }] },
  { "role": "user",
    "content": [{ "type": "tool_result", "tool_use_id": "toolu_01", "content": "12 passed, 0 failed" }] },
  { "role": "system",
    "content": "The user sent the following message while you were working: also update the changelog before you finish." }
]

書き方のコツは、「ユーザーを上書きする命令」ではなく「文脈の提示」として書くことです。「new input arrived from the user: X」「the remaining token budget is now Y」のように事実を述べて Claude に判断させる。Claude はユーザーの意図に反する指示に抵抗するよう訓練されており、その保護は system ロールにも及ぶため、「ignore what the user said」のような言い回しはむしろ効きにくくなります。

このパターンは会話のエンドユーザー本人の入力を中継するためのものです。ツール出力・取得した文書・第三者コンテンツを system に入れてはいけません(後述の制約)。

配置ルール(ここを外すと 400)

mid-conversation system message は置ける位置が決まっています。

  • 先頭には置けないmessages の最初のエントリにはできない。最初から効かせたい指示はトップレベル system
  • 直前は user ターン(tool_result ブロックを運ぶ user を含む)、またはサーバーツール使用で終わる assistant ターンであること
  • 直後は assistant ターンであるか、配列の末尾であること
  • tool_use ブロックとその tool_result の間には置けない
  • 連続した system メッセージは不可 — 指示は 1 つにまとめるか、次の user ターンを待って追記する

これら以外の位置に置くと 400 エラーになります。

prompt caching との併用

本機能とキャッシュは併用前提で設計されています。

  • キャッシュは明示的に有効化するcache_control(トップレベルの自動キャッシュ、またはブロックごとの明示ブレークポイント)が無ければ何もキャッシュされない。system メッセージ自体はキャッシュエントリを作らない
  • 安定プレフィックスを通常どおりキャッシュ — リクエスト間で変わらない最後のブロック(system 末尾・ツール定義末尾・履歴の安定点)に cache_control を置く
  • ブレークポイントの後ろに system を追記 — プレフィックスのハッシュを変えないのでヒットが続く
  • 追記した system は次ターンでキャッシュ対象になる — 一度会話に入れば安定履歴の一部。次のターンでブレークポイントをその先へ動かせばキャッシュから読まれる

注意点として、送信済みの mid-conversation system メッセージを編集・削除しないでください。過去メッセージへの変更と同じく、そこから先のキャッシュが無効になります。指示を更新したいときは古いものを書き換えず、新しい system メッセージを追記します。キャッシュが外れた原因の特定には cache diagnostics が使えます。

セキュリティ上の制約

system コンテンツは運営者の指示として扱われ、Claude はそれに従います。だからこそ、会話の外から来たテキスト(生のツール出力・取得文書・Web コンテンツ)を system に直接入れてはいけません。それをやると、その文章に運営者レベルの権限を与えることになり、プロンプトインジェクションの経路になります。そうしたデータは tool_result ブロックに留め、ジェイルブレイク/インジェクション対策の原則を守ってください。

まとめ

mid-conversation system messages は、**「会話の途中で運営者レベルの指示を、prompt cache を壊さずに足す」**ための Opus 4.8 専用機能です(Claude API / Claude Platform on AWS、beta ヘッダ不要)。トップレベル system の書き換えがキャッシュ全外しを招くのに対し、末尾追記なら既存キャッシュを温存できます。配置ルール(先頭不可・tool_use/tool_result の間不可・連続不可、違反は 400)と、untrusted コンテンツを system に入れないという 2 点さえ守れば、長時間エージェントのガードレールを動的に・安価に足していけます。

release notes タグで Anthropic / Claude API のリリース情報を継続フォローしています。

参考