Claude Media
mid-conversation effortの切り替えでオーケストレーションモードを作る

mid-conversation effortの切り替えでオーケストレーションモードを作る

セッション単位でeffortとマルチエージェントのファンアウトを切り替える「オーケストレーションモード」を、公式ドキュメントの実装パターンから組み立てます。

Claude APIのエージェントに「今回は時間がかかっても徹底的にやってほしい」という要求だけを、セッション単位でオン・オフしたいことがあります。これは単一のAPIパラメータでは実現できず、effortの固定・mid-conversationシステムメッセージ・ツール説明への一文という3つの部品を組み合わせて作ります。切り替えの実体は3つの部品で、いずれもベータヘッダーを必要としません。以下、リクエストの組み立てから順に作ります。

前提 — オーケストレーションモードは何でできているか

オーケストレーションモードとは、オンの間は徹底性を優先してWorkflowツールでのマルチエージェントファンアウト(fan-out。1つの要求を複数のサブエージェントに分けて同時に走らせること)を既定の挙動にし、オフの間は元どおり個別リクエストで都度の明示的な指示が要る状態(opt-in)に戻すセッション単位の切り替えです。これはAPIパラメータではなく、次の3つのドキュメント化済みの部品だけで組み立てます。

  1. effortレベル: xhighのような、公式に文書化されたeffort値でリクエストを実行します。この例ではリクエストのトップレベルで毎回effortを指定するだけなので、ベータヘッダーは不要です。
  2. モードのリマインダー: mid-conversationシステムメッセージで、モードがオンであることをモデルに伝えます。数ターンごとに1行の再確認を送り、オフになったら終了通知を送ります。トップレベルのsystemフィールド自体は一度も変えないので、プロンプトキャッシュされたプレフィックスは保たれます。この機能はClaude Fable 5.1 / Mythos 5.1 / Fable 5 / Mythos 5 / Opus 4.8 / Opus 5で利用でき、Sonnet 5では使えないためトップレベルのsystemフィールドで代替します(この記事のcurl例がclaude-opus-5を指定しているのはこのためです)。
  3. ツール説明への常時の許可(standing consent): オーケストレーション用ツールの説明文に、「モードがオンの間は毎回このツールで作業を計画・実行してよい」という一文を含めておきます。

前提として、ファンアウト自体はトークン消費を大きく増やします。1回のリクエストが多数のサブエージェント会話を同時に生むため、このモードはコストに見合う重い作業にだけ使います。effortレベルそのものの基礎(低・中・高でどう変わるか)はClaude effortとは、サブエージェントを起動する側の実装はAgent SDKのサブエージェント定義で扱っています。

事前に決めておく定数

実装を始める前に、ファンアウトの形とリマインダーの頻度を決める定数を用意しておきます。

定数役割
MAX_CONCURRENT役割同時に走らせるサブエージェントの数の上限
MAX_TOTAL_SUBTASKS役割1回のWorkflow呼び出しでモデルが積めるサブタスク数の上限
MAX_SUBAGENT_TURNS役割1つのサブエージェントが使える最大ターン数
MAX_MAIN_TURNS役割オーケストレーター本体が使える最大ターン数
TURNS_BETWEEN_REFRESHERS役割MODE_REFRESHを再送するまでの間隔(ユーザーターン数)

MAX_CONCURRENTMAX_TOTAL_SUBTASKSを分ける理由は次の「よくあるつまずき」で扱います。

ステップ1: effortをリクエストのトップレベルで固定する

curl --fail-with-body -sS https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-opus-5",
    "max_tokens": 64000,
    "output_config": {"effort": "xhigh"},
    "system": "You are a helpful general-purpose agent. Answer the users request directly.",
    "tools": [{"name": "Workflow", "description": "...", "input_schema": {"type": "object", "properties": {"subtasks": {"type": "array", "items": {"type": "string"}}}, "required": ["subtasks"]}}, {"type": "bash_20250124", "name": "bash"}],
    "messages": [
      {"role": "user", "content": "現在のディレクトリを調査し、機能・コード品質の課題・具体的な改善案を徹底的にレビューして"},
      {"role": "system", "content": "Orchestration mode is on: optimize for the most exhaustive, correct answer rather than the fastest one. Use the Workflow tool on every substantive task."}
    ]
  }'

output_config.effortはリクエストごとに指定する通常のパラメータで、ベータヘッダーを必要としません。モードがオンの間は毎リクエストでxhighを指定し続けるだけで、モードがオフに戻ったら通常のeffort値へ戻します。会話の途中からeffortだけを1ターンに限って動的に変えたい場合は、ベータのmid-conversation-output-config-2026-07-01ヘッダー付きでrole: "system"メッセージにoutput_config.effortを乗せる方法もありますが、この例ではトップレベル指定で足りるため使いません。

ステップ2: モードのリマインダーを3種類用意する

リマインダーは短く保ちます。重い指示はツールの説明側に置き、リマインダーはモードのオン・オフを伝えて説明への参照を促すだけにします。

MODE_ENTER = (
    "Orchestration mode is on: optimize for the most exhaustive, correct answer "
    "rather than the fastest one. Use the Workflow tool on every substantive task, "
    "sized to the problem's natural decomposition. Work solo only on conversational "
    "or trivial turns."
)
MODE_REFRESH = "Orchestration mode is still on. Use the Workflow tool; see its standing consent section."
MODE_EXIT = "Orchestration mode is off. The Workflow tool's standard opt-in rule applies again."

MODE_ENTERはモードがオンになった最初のターンで1回だけ送ります。MODE_REFRESHは一定ターン数(たとえば10ターン)ごとに1行だけ送り直します。MODE_EXITはモードがオフになったときに1回だけ送ります。3つともrole: "system"メッセージとして、直前のuserターンの後ろに置きます。この位置なら、そこより前のキャッシュ済みバイト列には一切触れず、システムメッセージの配置ルール(直前はuserターンかサーバーツール結果で終わるassistantターン)も自然に満たせます。この位置を守らずにtool_usetool_resultの間へ差し込むと400エラーになるため、エージェントループの実装では「ユーザーの新しいターンが来た直後」という1か所に差し込み処理をまとめておくと事故を減らせます。

状態管理は3つのフラグだけで足ります。「モードがオンかどうか」「アナウンス済みかどうか」「終了通知待ちかどうか」、それに「前回のリマインダーから何ターン経ったか」のカウンタです。モードをオフにする関数は、すでにアナウンス済みなら終了通知を予約するだけで、その場では何も送信しません。実際にMODE_EXITが送られるのは、次にユーザーのターンが来てシステムメッセージを組み立てるタイミングです。このずらし方のおかげで、モードの切り替え自体はいつ呼び出してもよく、実際の送信タイミングはメッセージ配置ルールの制約に自動で合わせられます。

ステップ3: ツールの説明に常時の許可(standing consent)を明記する

Workflowツールの説明文自体に、モード中の既定挙動を明記します。

Orchestrate a multiagent workflow: split a large task into independent subtasks
and run them as parallel agents, then collect their results. Opt-in: only use
this tool when the user explicitly asks for a workflow, or when a system message
confirms that orchestration mode is on. Granularity: scope each subtask to a
distinct concern rather than per line or file section. Standing consent: while a
system message confirms orchestration mode is on, author and run a workflow for
every substantive task by default; work solo only on conversational turns or
trivial mechanical edits.

リマインダー(ステップ2)は「モードがオンだ」という事実だけを伝え、実際にどう振る舞うべきかの詳細ルールはこちらのツール説明に集約します。この分離のおかげで、リマインダーを短く保ったまま、モードの中身を後から変更してもツール定義側だけを直せば済みます。説明文の中の「Granularity」の指針(サブタスクは行やファイル断片単位ではなく、独立した関心事の単位で切る)も、モードのオン・オフに関わらず常に効く固定ルールとしてここに置いておきます。

ステップ4: ファンアウトを再開可能にするジャーナル

数十のサブエージェントを起動するファンアウトは、途中で落ちたときに最初からやり直すコストが高くつきます。プロンプトのSHA-256をキーにしたローカルJSONファイルを噛ませるだけで、再実行時に完了済みの結果を再利用できます。

def journaled(prompt: str, compute) -> str:
    """このプロンプトのキャッシュ済み結果を返すか、計算して永続化する。
    実行を中断して再実行しても、完了していないサブタスクだけが再計算される。"""
    key = hashlib.sha256(prompt.encode()).hexdigest()
    cached = _load_journal().get(key)
    if cached is not None:
        return cached
    result = compute()
    with _journal_lock:  # fan-outは複数スレッドから書き込む
        journal = _load_journal()
        journal[key] = result
        temp = f"{JOURNAL_PATH}.tmp"
        with open(temp, "w") as file:
            json.dump(journal, file)
        os.replace(temp, JOURNAL_PATH)  # POSIXでもWindowsでもアトミック
    return result

ジャーナルは実行を横断して重複を防ぐものであり、同一ファンアウト内の重複除去には使いません。やり直すときはジャーナルファイルを消すだけです。書き込みは一時ファイルへ書いてからリネームする形にしておくと、書き込み途中でプロセスが落ちてもジャーナルが壊れません。

ステップ5: 結果を検証する第2波を回す

1回目のファンアウトが終わったら、同じサブエージェントの仕組みを再利用して、各結果を「反証してみる」第2波を走らせます。検証エージェントに渡すプロンプトは次のように組み立てます。

def verify_prompt_for(subtask: str, result: str) -> str:
    return (
        "Adversarially verify the subagent result below: try to REFUTE it. "
        "Re-derive the claims yourself with bash rather than trusting the "
        "result, and look for evidence that contradicts them. Default to "
        "refuted if uncertain."
    )

ポイントは「結果を鵜呑みにせず、自分でbashを使って主張を再導出する」ことと「確信が持てなければrefuted(反証)寄りに倒す」ことの2つです。検証エージェントも同じサブエージェントの実行経路を再利用するため、ファンアウト用に用意したジャーナルや失敗分離の仕組みがそのまま検証の第2波にも効きます。元の結果と検証結果の両方をオーケストレーターへ返し、両方を突き合わせて最終判断させます。1つのサブエージェントが失敗しても、そのタスクだけをエラー文字列に落として実行全体は止めない、という失敗の分離も同時に行います。

サブエージェントに渡すsubtasksの受け取り方にも一手間必要です。モデルは配列そのもの、配列をJSON文字列化したもの、改行区切りのプレーンテキストのいずれでも返してくることがあるため、パース前に形式を正規化します。

def normalize_subtasks(raw) -> list[str]:
    """モデルが返す形式(配列/JSON文字列化された配列/改行区切り)を吸収する"""
    if isinstance(raw, str):
        try:
            raw = json.loads(raw)
        except json.JSONDecodeError:
            raw = raw.splitlines() if "\n" in raw else [raw]
    if not isinstance(raw, list):
        return []
    return [task.strip() for task in raw if isinstance(task, str) and task.strip()]

使い分け早見表

状況向いている実装
単発の質問・雑談的なやり取り向いている実装通常のリクエスト(effort既定値、Workflowはopt-in)
特定の1回だけ徹底的に調べてほしい向いている実装その1リクエストだけoutput_config.effortxhighに上げる
セッションを通じて「常に徹底的に・自動でファンアウトしてよい」状態にしたい向いている実装本記事のオーケストレーションモード(モードのリマインダー + ツール説明への常時の許可)
すでに手元でサブエージェントを個別に呼び分けている向いている実装Agent SDKのサブエージェント定義を直接使う構成に寄せる方が単純

よくあるつまずき

  • ファンアウトの上限を分けずに設計する: 「1回のWorkflow呼び出しで積める件数」(MAX_TOTAL_SUBTASKS)と「同時に走らせる件数」(MAX_CONCURRENT)は別のノブです。前者だけだとモデルが大量のバックログを一度に投げつけ、後者だけだと大きな計画を立てられません。両方を別々にキャップします。
  • サブタスクの形式を1つしか受け付けない: モデルは配列・JSON文字列化された配列・改行区切りのリストのいずれでもsubtasksを返してくることがあります。正規化せずに配列決め打ちでパースすると、Workflow呼び出しがそのまま失敗します。
  • リマインダーに全ルールを詰め込む: モードのリマインダーを毎回長文にすると、そのたびにトークンを消費しキャッシュも当たりにくくなります。詳細ルールはツールの説明側(ステップ3)に置き、リマインダーは状態の通知に徹します。
  • 1つのサブエージェントの失敗で全体を止める: 分離せずに例外を伝播させると、数十件中の1件の失敗でファンアウト全体が失敗扱いになります。個々の失敗はエラー文字列として結果に含め、実行は継続させます。

本番運用へ向けて足す3点

このパターンをそのまま小さな検証に使うのはよいですが、本番のワークロードに載せるには次の3点を足す前提で設計します。

足すもの理由
サンドボックス化されたオーケストレーションスクリプト理由サブタスクの平坦なリストだけでなく、分岐・ループ・reduceを含む計画をモデルに書かせ、隔離された環境で実行させる
永続的なジャーナル理由ローカルJSONファイルではプロセス再起動や複数マシンからの同時書き込みに耐えないため、永続ストアへ置き換える
予算の強制理由1回のWorkflow呼び出し単位ではなく、セッション全体で起動したサブエージェント総数を追跡し、上限を超えたら拒否する

モードのリマインダー・ツール説明への常時の許可・ジャーナル・検証の第2波というパターン自体は、これらを足しても変わりません。変わるのは、その周りの実行基盤の堅牢さだけです。小さく動かして仕組みを確かめてから、この3点を1つずつ足していくのが現実的な進め方です。

まとめ

オーケストレーションモードは、effortの固定・mid-conversationシステムメッセージによる状態通知・ツール説明への常時の許可という3つの部品の組み合わせで作れます。モード自体は単一のAPIパラメータではないため、リマインダーを短く保ちつつ詳細ルールをツール説明に集約する設計と、ファンアウトを再開可能にするジャーナル、そして結果を鵜呑みにしない検証の第2波を組み合わせることで、長時間のマルチエージェント実行でも壊れにくい実装になります。

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