Claude Media
mid-conversation system messageとは — 配置制約とclear_atの仕様

mid-conversation system messageとは — 配置制約とclear_atの仕様

messagesの途中にrole: systemを置くmid-conversation system messageの配置制約、clear_atによるターン限定、外部テキストを入れてはいけない理由、キャッシュとの関係を表で示します。

mid-conversation system messageは、Claude APIのmessages配列の途中に{"role": "system"}のメッセージを差し込み、会話の途中から運営者(オペレーター)の指示を追加する仕組みです。トップレベルのsystemフィールドを編集せずに済むので、キャッシュ済みのプレフィックスはそのまま使えます。代わりに、置ける場所・書ける中身・送り続け方に制約があります。この記事は、その制約を一覧にして示します。

mid-conversation system messageとは何か

通常のシステム指示は、リクエストの先頭にあるトップレベルのsystemフィールドに書きます。プロンプトキャッシュはリクエストのプレフィックスをtools、system、messagesの順にハッシュするため、systemを1文足しただけでも、その後ろのキャッシュはすべてミスになります。

mid-conversation system messageは、その指示を履歴の末尾側に足す方法です。それより前のメッセージは一字も変わらないので、既存のキャッシュエントリは一致し続けます。新しく処理されるのは追加した1件だけです。

contentはuserやassistantと同じく、文字列またはコンテンツブロックで書けます。指示が衝突したときは、後ろのシステムメッセージが前のものに勝ちます。また、mid-conversation system messageは、それより後のターンについてトップレベルのsystemに優先します。

対応モデルとプラットフォーム

この機能はClaude API、Amazon Bedrock、Google Cloudで使えます。対応モデルはClaude Fable 5.1、Claude Mythos 5.1、Claude Fable 5、Claude Mythos 5、Claude Opus 5.5、Claude Opus 4.8、Claude Opus 5、Claude Sonnet 5.5です。基本のシステムメッセージにベータヘッダーは要りません。

Claude Sonnet 5では使えません。Sonnet 5で同じことをするには、トップレベルのsystemフィールドを使います。

userメッセージではなくsystemを使う理由

同じ文をuserメッセージで送っても、Claudeは従います。違いは優先度です。userはエンドユーザーの発言として扱われ、systemは運営者の発言として扱われます。両者が食い違えばsystemが優先されます。

エンドユーザーが別の頼み方をしても守らせたい制約や、アプリ側が観測した事実(設定の切り替え、残りトークン予算など)は、systemロールで送る用途です。

配置制約の一覧

配置の規則は、次の表のとおりです。守らないと400エラーになります。

位置・中身可否補足
messagesの先頭可否不可補足中身のあるsystemメッセージは先頭に置けない。冒頭からの指示はトップレベルのsystemへ
userターンの直後可否可補足tool_resultブロックを持つuserターンも含む
サーバーツールの結果で終わるassistantターンの直後可否可補足textブロックは可
tool_useと対応するtool_resultの間可否不可補足400エラー
直後がassistantターン、または配列の末尾可否可補足どちらかでなければならない
空のcontentでoutput_config.effortだけを設定する可否どこでも可補足先頭や、assistantとuserの間にも置ける

置ける場所を一言でまとめると、「userターンの直後で、次がassistantか配列の末尾」です。エージェントのループでは、ツール結果を返すuserメッセージの直後に置けます。

ツール結果の直後に置く

ツールを回している最中の典型的な形は、次のとおりです。

[
  { "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."
  }
]

この配置は、ユーザーがツール実行中に打ち込んだ追加入力を中継するときにも使えます。新しい依頼として作業を切り替えさせず、進行中の作業に取り込ませる形です。

書き方にもコツがあります。「ユーザーの発言を無視せよ」のような上書き命令より、何が変わったかという事実を書くほうが効きます。Claudeはユーザーに不利に働く指示に抵抗するよう訓練されていて、その保護はsystemロールにも及びます。

連続したシステムメッセージは1つのまとまりとして判定される

systemメッセージを連続して置くことは許されていて、まとめて1つのシステム区画として扱われます。配置規則もまとまり全体に適用されます。

効果だけを設定する空のメッセージ(output_config.effortのみ)はどこにでも置けますが、その隣にテキストを持つメッセージを置くと、グループ全体が「中身あり」の規則に従います。effortの切り替えはeffort切り替えの解説記事が詳しく、ツールの追加・撤回はツール変更の解説記事が詳しく扱っています。

ツール変更ブロックにはさらに制約がある

tool_additionとtool_removalブロックも、テキストと同じ配置規則に従います。加えて、一時停止(pause_turn)したassistantターン、つまりサーバーツールの結果で終わるターンの直後には置けません。textブロックなら置けます。一時停止したターンを先に再開し、ツール変更は次のsystemメッセージで送る形になります。

clear_atでターン限定にする

clear_atは、systemメッセージの表示範囲を決めるフィールドです。値は2つあります。

くらべる

clear_atの2つの値

既定

never

含まれるすべてのリクエストで、その位置に表示され続けます。フィールドを省略しても同じ挙動です。

ターン限定

next_user_message

後ろにrole: "user"のメッセージがない間だけ、テキストが表示されます。

後ろにuserメッセージができると、そのメッセージは「クリア済み」になります。配列には残りますが何も表示されず、入力トークンも消費しません。その状態は、以後のリクエストでも続きます。

判定で注意したいのは、tool_resultだけを持つuserメッセージもuserメッセージとして数えられる点です。ツールループの1往復ごとに、直前のターン限定メッセージは消えます。

ターン限定メッセージはベータ機能です。ベータヘッダーmid-conversation-system-clear-at-2026-08-21が必要で、ヘッダーなしでclear_atを送ると未知のフィールドとして拒否されます。

{
  "role": "system",
  "clear_at": "next_user_message",
  "content": "First privately list what you need next; then request every item that doesn't depend on another's result in this one response."
}

毎ターンのリマインダーが積み上がらない

主な用途は、ツールループでのターンごとのリマインダーです。tool_resultのメッセージの後ろに毎回リマインダーを追加し、過去のコピーはそのまま残します。モデルに見えるのは最後のuserメッセージより後ろにあるコピーだけなので、リマインダーが積み上がりません。

それより前のmessagesは変わらないため、プロンプトキャッシュも一致し続けます。過去のリマインダーを削除すると履歴が変わりますが、クリア済みメッセージは配列に残るので、履歴は変わりません。

Claude Fable 5.1、Claude Opus 5.5、Claude Sonnet 5.5では、後ろのthinkingブロックが有効なままになる効果もあります。リマインダーを削除すると、それより後のthinkingブロックの手前で会話が変わり、会話の一致検査に失敗します。thinkingブロックの保護はPreserved thinkingの解説記事で扱っています。

ターン限定メッセージはテキストのみで、そのまま送り続ける

clear_at: "next_user_message"のメッセージには、次のものを持たせられません。

  • tool_addition
  • tool_removal
  • output_config
  • cache_control

もう1つの制約が、送り続ける義務です。クリア済みになった後も、以降のリクエストでmessagesにバイト単位で同じまま含めなければなりません。クライアント側で履歴を保存するときは、clear_atフィールドと本文をそのまま保つ実装にします。履歴の圧縮や整形の処理が、クリア済みだからと削ってしまうと制約に反します。

外部テキストを入れてはいけない理由

Claudeはsystemの内容を運営者の指示として扱い、従います。ツールの生の出力、検索で取得した文書、Web上のコンテンツなど、会話の外から来たテキストをsystemメッセージに直接入れると、そのテキストに運営者権限を与えることになります。

入れてよいもの入れてはいけないもの
アプリ自身が観測した事実(設定の切り替え、残りトークン予算)入れてはいけないものツールの生出力
会話の当事者であるエンドユーザーの入力の中継入れてはいけないもの取得した文書・Webページの本文
アプリが決めたポリシーや期限入れてはいけないもの第三者が書けるテキスト全般

外部データはtool_resultブロックに入れたままにします。ツール結果直後の中継は、会話の当事者であるエンドユーザーの入力に使う形です。ツール出力や第三者コンテンツの受け渡しには使いません。公式はこの点で、ジェイルブレイクとプロンプトインジェクションを軽減するガイドに従うよう案内しています。

キャッシュとの関係

mid-conversation system messageは、キャッシュを有効にしたときに意味を持ちます。押さえる点は4つです。

手順

キャッシュを効かせる4段階

  1. 1

    cache_controlを付ける

    リクエストにcache_controlがなければ、何もキャッシュされません。会話全体を毎回、通常の入力料金で処理します。

  2. 2

    変わらない最後のブロックに置く

    cache_controlは、リクエスト間で変わらない最後のブロックに置きます。

  3. 3

    ブレークポイントの後ろに足す

    システムメッセージはそのブレークポイントの後ろに足します。プレフィックスのハッシュは変わりません。

  4. 4

    次のターンから履歴として読む

    一度送ったシステムメッセージは、次のターンから安定した履歴の一部としてキャッシュから読まれます。

送信済みのシステムメッセージを編集・削除すると、その位置以降のキャッシュが無効になります。指示を更新したいときは、書き換えず、新しいシステムメッセージを追記します。

キャッシュには最小のキャッシュ可能プロンプト長もあります。短い会話ではcache_creation_input_tokensとcache_read_input_tokensが0のままです。

機能ごとに対応モデルとヘッダーが違う

同じrole: "system"でも、載せる機能によって対応モデルとベータヘッダーが変わります。「基本のsystemメッセージが使えるモデルなら、effortもツール変更も使える」とは限りません。

機能対応モデルベータヘッダー
テキストのsystemメッセージ対応モデルFable 5.1、Mythos 5.1、Fable 5、Mythos 5、Opus 5.5、Opus 4.8、Opus 5、Sonnet 5.5ベータヘッダー不要
clear_at対応モデル上と同じベータヘッダーmid-conversation-system-clear-at-2026-08-21
output_config.effort対応モデルFable 5.1、Mythos 5.1、Opus 5.5、Opus 5、Sonnet 5.5ベータヘッダーmid-conversation-output-config-2026-07-01
tool_addition・tool_removal(名前で参照)対応モデル基本と同じベータヘッダーinline-tools-2026-09-15 または mid-conversation-tool-changes-2026-07-01
ツール定義をメッセージに載せる対応モデル基本と同じベータヘッダーinline-tools-2026-09-15

effortの行には、基本の8モデルのうちFable 5、Mythos 5、Opus 4.8が入っていません。effortの解説ページは、未対応のFable 5などがこのフィールドを送ると400になると書いています。

output_config.effort requires a model that supports per-turn effort; this model does not

プラットフォームの差もあります。effortの切り替えはClaude APIとGoogle Cloudで、MCPサーバーの追加にはmcp-client-2026-09-15の追加ヘッダーが要り、Claude APIだけで使えます。

Sonnet 5.5だけの罠も1つあります。thinking: {"type": "between_tools"}を指定していると、effortの値を途中で変えるメッセージが400になります。エラーは変えようとしたメッセージの位置をmessages.N:の形で示します。ターンごとにeffortを変えたいなら、思考は適応的(adaptive)にします。

送信前に配置を検査するコード

配置違反は400エラーで初めて分かるので、履歴を組み立てる側で先に確かめておくと原因を追いやすくなります。次の関数は、この記事の表を検査に書き直した例です。公式のコードではなく、独自に組んだ簡易版です。

def check_system_placement(messages: list[dict]) -> list[str]:
    """中身のある system メッセージの配置を検査する簡易版。"""
    errors = []
    for i, m in enumerate(messages):
        if m["role"] != "system":
            continue
        # 連続する system は 1 グループとして直前を見る
        j = i
        while j > 0 and messages[j - 1]["role"] == "system":
            j -= 1
        if j == 0:
            errors.append(f"[{i}] 先頭には置けない")
            continue
        if messages[j - 1]["role"] == "assistant":
            errors.append(f"[{i}] user ターンの直後ではない")
        nxt = messages[i + 1]["role"] if i + 1 < len(messages) else None
        if nxt == "user":
            errors.append(f"[{i}] 直後が assistant でも末尾でもない")
        if m.get("clear_at") == "next_user_message":
            content = m.get("content")
            if not isinstance(content, str) and any(
                b.get("type") != "text" for b in content
            ):
                errors.append(f"[{i}] ターン限定はテキストのみ")
    return errors

この検査は、空のcontentでeffortだけを設定するメッセージと、サーバーツールの結果で終わるassistantターンの直後を扱っていません。実運用では、公式の制約表と照らして拡張します。

使い分けの早見表

制約を踏まえた選び方は、次のようになります。

目的向く手段
会話の冒頭から効かせる方針向く手段トップレベルのsystem
途中から加わる恒久的な制約向く手段clear_atなしのmid-conversation system message
毎ターン出したいが積み上げたくない注意向く手段clear_at: "next_user_message"
ツール出力や取得文書の受け渡し向く手段tool_resultブロック
エンドユーザーが言った追加要望の中継向く手段ツール結果直後のsystemメッセージ(事実として書く)

400エラーが出たときの確認順

配置エラーは、履歴を前から順に確認すると原因に当たります。

  1. 1

    先頭にsystemを置いていないか

    置いていたら、その内容をトップレベルのsystemへ移します。例外は、effortだけを設定する空のメッセージです。

  2. 2

    tool_useとtool_resultの間に挟んでいないか

    挟んでいたら、tool_resultを持つuserメッセージの直後へ動かします。

  3. 3

    直後にuserメッセージが続いていないか

    続いていたら、systemメッセージを後ろのuserメッセージの直後へ動かします。直後はassistantか配列の末尾でなければなりません。

  4. 4

    一時停止したターンの直後にツール変更を置いていないか

    tool_additionとtool_removalは置けないので、ターンを再開してから次のsystemメッセージで送ります。

まとめ

先頭に置けず、userターンの直後だけという制約を呑めるなら、mid-conversation system messageは運営者権限の指示をキャッシュを壊さずに足せる手段です。使う前に、載せたい機能(clear_at、effort、ツール変更)が自分のモデルとヘッダーで通るかを表で確かめ、中身はアプリ自身の観測と本人の入力に絞ります。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages
  2. platform.claude.com/docs/en/build-with-claude/prompt-caching
  3. platform.claude.com/docs/en/build-with-claude/effort
  4. platform.claude.com/docs/en/api/errors
この記事を共有:XはてブLinkedIn