DEV Community

Cover image for マルチエージェントハンドオフ:サブエージェント間のコンテキスト引き継ぎ
Akira
Akira

Posted on Originally published at apidog.com

マルチエージェントハンドオフ:サブエージェント間のコンテキスト引き継ぎ

マルチエージェントの引き継ぎで状態を失わない設計

調査エージェントは顧客アカウントを特定し、プランを確認して、過去4件の請求書を取得しました。しかし請求エージェントに渡されたのは、「顧客は返金を求めている」という一行だけ。請求エージェントはアカウントや請求書を知らないため、まずアカウントIDを尋ねるところから始めます。

Apidogを今すぐ試す

最初のエージェントが集めた事実は、境界を越えると失われました。これが引き継ぎの問題です。重複するAPI呼び出しが発生するだけでなく、2番目のエージェントが不完全な情報で処理するため、エラーも増えます。

このガイドでは、引き継ぐべき状態、状態を渡す3つの方法、要約で情報が失われる理由、そして引き継ぎが必要な情報を伝えたことをテストする方法を解説します。エージェントが本番環境で故障する理由でも、状態の喪失を主要な故障モードとして扱っています。

Apidogを使う理由の一つは、最も安価な解決策が、データそのものではなく識別子を渡すことだからです。ただし、すべてのエージェントが同じAPIから同じレコードを取得できる場合に限ります。

境界を越えて渡すべきもの

会話全体をコピーすればよいわけではありません。完全な履歴を渡すと、受信側のエージェントは重要な情報を自分で探さなければならず、コンテキストウィンドウも圧迫されます。

引き継ぐ状態は、次の4カテゴリに分けると整理しやすくなります。

識別子

アカウントID、注文ID、ジョブID、チケット番号などです。小さく安定しており、受信側が必要なデータを取得できます。最も価値が高い一方、要約で最も失われやすい情報です。

確定済みの判断

例:「顧客はポリシー3に基づいて返金対象である。」

この判断を受信側が再検討すると、同じタスク内でエージェント同士の判断が食い違う可能性があります。

制約

予算上限、承認済みの操作、実行済みアクションなどです。失うと二重請求や、同じ承認を何度も求める問題につながります。AIエージェントのべき等性とも直接関係します。

未解決の質問

最初のエージェントが解決できなかった点を明示します。これにより、受信側が黙って推測するのを防げます。

逆に、次の情報は原則として渡す必要がありません。

  • 生のAPIレスポンス
  • 推論のトランスクリプト
  • 受信側が1回のAPI呼び出しで取得できるデータ

状態を渡す3つの方法

1. 会話全体を渡す

最も単純な方法です。短いタスクで、エージェントが2つだけなら機能します。

ただし、トランスクリプトが長くなると急速に破綻します。受信側が履歴を読むためにコンテキスト予算の大半を使い、重要な事実が途中に埋もれるためです。ツール応答をコンテキストウィンドウから外すでは、モデルが情報を失いやすい位置について説明しています。

2. 要約を渡す

最初のエージェントが引き継ぎメッセージを書き、2番目のエージェントがその内容から処理を開始します。多くのフレームワークで標準的な方法です。

問題は、モデルが要約を物語として生成しやすいことです。識別子や金額よりも、状況の説明を優先します。

例えば、次の情報を渡したいとします。

アカウント8812、プランPro、請求書4件、請求書inv_44の返金承認済み

しかし実際の要約は、次のようになりがちです。

顧客は2年間購読しており、不満を抱いている。

3. 構造化された引き継ぎオブジェクトを渡す

最初のエージェントがスキーマを埋め、2番目のエージェントは散文ではなくフィールドを読み取ります。初期設定は必要ですが、最も持続可能な方法です。

{
  "task_id": "task_2026_08_26_0031",
  "from_agent": "research",
  "to_agent": "billing",
  "entities": {
    "customer_id": "cus_8812",
    "invoice_ids": ["inv_41", "inv_42", "inv_43", "inv_44"],
    "subscription_id": "sub_119"
  },
  "decisions": [
    { "decision": "refund_eligible", "value": true, "basis": "policy 3.2, charged twice in one cycle" }
  ],
  "constraints": {
    "max_refund_cents": 4900,
    "human_approval_granted": false,
    "actions_taken": ["read_invoices"]
  },
  "open_questions": ["Customer has not confirmed which invoice to refund"],
  "summary": "Customer cus_8812 was double-charged in August. Refund of one invoice is approved under policy 3.2, up to 4900 cents. Awaiting the customer's choice of invoice."
}
Enter fullscreen mode Exit fullscreen mode

summaryフィールドは、スキーマだけでは表現しきれないニュアンスを補足します。重要なのは、構造化フィールドの代わりではなく、構造化フィールドと併用することです。

引き継ぎ前にオブジェクトを検証しましょう。customer_idが欠落しているなら、2番目のエージェントが3回呼び出した後に発見するのではなく、境界で即座にエラーにします。

ペイロードではなく参照を渡す

より強力な方法は、ほとんどデータを渡さず、IDだけを渡すことです。受信側のエージェントが必要なデータを取得します。

この方法には、次のメリットがあります。

  1. 常に最新の状態を参照できる
  2. 引き継ぎを数万トークンではなく数百バイトに保てる
  3. コピーされたテキストではなくAPI呼び出しとして読み取りを記録でき、監査証跡が明確になる

前提は、すべてのエージェントが適切な権限で同じAPIにアクセスできることです。各エージェントには役割に応じた認証情報を与える必要があります。エージェントのための最小権限APIキーで説明されている考え方です。

読み取り専用の研究用トークンでは請求エージェントが返金を発行できません。一方、強力な請求用トークンを研究エージェントに渡すと、被害範囲が広がります。

再取得が遅い、または高価な場合は、オーケストレーターでレコードをキャッシュし、キャッシュエントリへの参照を渡します。受信側が明示的にデータを要求するパターンは変わらず、2回目の読み取りだけを安価にできます。

引き継ぎが破綻する4つのポイント

識別子が失われる

要約が「顧客」としか記述せず、IDを含まないケースです。2番目のエージェントが名前で検索した結果、2つの候補から誤った顧客を選ぶ可能性があります。

引き継ぎ前に、必要なエンティティIDの存在を検証してください。

アクションが重複する

最初のエージェントがメールを送信したにもかかわらず、その記録が引き継がれないと、2番目のエージェントが同じメールを再送します。

引き継ぎオブジェクトにactions_takenを記録し、書き込み操作の前に確認します。さらに、重複実行しても無害になるよう、べき等性キーを使います。

承認が失われる

最初のエージェントの実行中に、人間が返金を承認したとします。2番目のエージェントがその事実を知らなければ、再度承認を求めます。

承認はエージェントではなくタスクにスコープされた制約として保存し、明示的に引き継ぎましょう。

自信を持ってでっち上げる

受信側が必要な値を引き継ぎで受け取れなかったとき、質問せず、状況に合いそうな値を推測することがあります。完了したタスクのように見えるため、特に危険です。

対策は次の2つです。

  • open_questionsを必須フィールドにする
  • 必須の識別子がなければ停止して質問するルールをプロンプトに入れる

ループは、これら4つの問題をさらに悪化させます。エージェントAからB、BからAへ引き継ぐたびに状態が劣化するためです。

  • ホップ数を制限する
  • 各境界で要約を再生成しない
  • 元のタスクオブジェクト全体をすべてのホップで使う

エージェントではなく境界をテストする

引き継ぎは統合ポイントです。単独のエージェントではなく、統合ポイントとしてテストしましょう。

引き継ぎオブジェクトをアサートする

固定シナリオで最初のエージェントを実行し、生成されたオブジェクトを検証します。

  • 必須の識別子があるか
  • 判断が記録されているか
  • 実行済みアクションが一覧化されているか
  • 制約と未解決の質問が存在するか

エージェントの出力自体は非決定的でも、構造化されたペイロードには決定論的なアサーションを適用できます。非決定論的AIエージェントのテストも参考になります。

受信側を単独でテストする

手作業で作成した引き継ぎオブジェクトを請求エージェントに渡し、期待どおりに動作することを確認します。

次に、customer_idを削除した壊れたオブジェクトを渡します。受信側が推測せず、質問して停止することを確認してください。このテストが、でっち上げを検出します。

モックAPIで両方をテストする

実際に返金を発行する引き継ぎテストは、通常一度しか実行できません。両方のエージェントをモックエンドポイントに向け、すべての変更でテストスイートを実行できるようにします。

本番環境ではなくモックに対してエージェントを実行するという原則に従いましょう。Apidogでは、モックを両方のエージェントが使う同じAPI定義から生成できるため、実装とテストの乖離を防げます。

すべての引き継ぎをログに記録する

各境界で、タスクIDと完全な引き継ぎオブジェクトを記録します。

マルチエージェント実行が失敗したとき、ログから次のことが分かります。

  • どのエージェントが情報を持っていたか
  • どの境界で情報が失われたか
  • どのエージェントが誤った値を追加したか

エージェントツール呼び出しのトレースでは、ログに含めるべき追加情報を解説しています。

フレームワークが提供するもの

多くのオーケストレーションフレームワークには、引き継ぎのプリミティブがあります。ただし、実際に境界を越える情報を確認してから利用してください。

OpenAI Agents SDKの引き継ぎドキュメントでは、エージェントが呼び出せるツールとして引き継ぎをモデル化しています。モデル自身が制御を移すタイミングを決めるため便利ですが、非決定性の高い部分に判断を委ねることになります。出力時の検証と組み合わせましょう。

LangGraphのマルチエージェントガイダンスでは、すべてのノードが読み書きする明示的なグラフ状態を使います。構造化された引き継ぎに近い設計で、必要なフィールドを定義することが主な作業になります。

Anthropicのマルチエージェント研究システムの構築も、運用上の詳細、特にサブエージェントが単独で有用に動くために必要な指示の量を考えるうえで役立ちます。

共通するのは、どのフレームワークも何らかの情報を移動させることです。しかし、どの事実が重要かを決めてくれるフレームワークはありません。そのリストを作り、実行失敗時に確認できるようにする必要があります。

タスクオブジェクトを会話の外に置く

多くのバグは、タスク状態をメッセージではなく永続的なタスクオブジェクトに保存することで防げます。

会話は状態を保存するコンテナとして不安定です。要約、切り詰め、書き換えが発生しますが、それらは失ってはいけないフィールドを理解していません。データベースの行や永続オブジェクトなら、状態を明示的に保持できます。

基本的な流れは次のとおりです。

  1. ターン開始時に、エージェントがタスクオブジェクトをロードする
  2. アクション実行後、actions_takenに追加して保存する
  3. 引き継ぎではタスクIDだけを渡す
  4. 受信側が同じタスクオブジェクトをロードする

重要な状態がプロンプト内を移動しないため、要約によって失われません。

また、再開ポイントも得られます。ステップ4で停止しても、最初の3ステップで確立した状態がタスクオブジェクトに残ります。再試行はそこから開始できます。

プラットフォームに状態を保持させる

エージェントを開発者のマシン上のCLIランタイムとして実行する場合、永続的なタスクオブジェクトは自分で構築します。一部のエージェント作業管理プラットフォームは、このモデルを標準で提供しています。

Sharklyは、人とエージェントの作業をタスク単位で管理するシステムです。タスクには、次の情報が保持されます。

  • 目標
  • ステータス
  • 責任者
  • 実行を割り当てられたエージェントまたはクルー
  • コメント
  • エージェント実行の状態と結果

クルーは、リーダーエージェント、他のエージェント、人を組み合わせます。複数の専門家が必要なタスクを、プロンプト経由ではなく再利用可能なグループに割り当てられます。

Claude CodeやCodexなどのランタイムは、登録済みのコンピューター上で作業を実行します。プラットフォームは、タスクレコード、割り当て、レビューサイクルを提供します。

独自の永続タスクパターンを構築する場合は、Sharklyのドキュメントもフィールド設計の参考になります。

引き継ぎチェックリスト

  • [ ] 引き継ぎスキーマを定義し、境界で検証する
  • [ ] エンティティ識別子を必須フィールドにする
  • [ ] 判断に根拠を含め、受信側が推論し直さなくて済むようにする
  • [ ] 実行済みアクションを記録し、書き込み前に確認する
  • [ ] 承認と予算をエージェントではなくタスクに紐付ける
  • [ ] 未解決の質問を明示し、受信側が推測せず質問するようにする
  • [ ] 再取得が安価ならデータではなく参照を渡す
  • [ ] ホップ数を制限し、元のタスクオブジェクトを使い続ける
  • [ ] すべての引き継ぎをタスクIDとともにログに記録する
  • [ ] 不完全な引き継ぎを含む境界テストをモックAPIでCI実行する

マルチエージェントの失敗の多くは、推論そのものの失敗ではありません。あるエージェントには存在した事実が、次のエージェントには存在しなかったことが原因です。

引き継ぎをスキーマとテストを備えたインターフェースとして設計すれば、2番目のエージェントが最初のエージェントの回答済みの質問を繰り返すことを防げます。両方のエージェントが使うAPIの隣にモックと境界テストを置くには、Apidogをダウンロードしてください。

よくある質問

2つのエージェントでも構造化された引き継ぎは必要ですか?

短いタスクでエージェントが2つだけなら、会話全体を渡しても通常は問題ありません。

構造化オブジェクトの価値が大きくなるのは、次のケースです。

  • 3つ以上のエージェントを使う
  • タスクが長い
  • プロセスや実行の境界を越えて引き継ぐ

引き継ぎオブジェクトはモデルとコードのどちらが作るべきですか?

可能な限りコードで構築します。

識別子、実行済みアクション、承認は、モデルの記憶ではなく実際の実行結果からオーケストレーターが入力すべきです。モデルに任せるのは、summaryと未解決の質問に限定するのが安全です。

ループによるコンテキスト劣化をどう防ぎますか?

各境界で要約を再生成せず、1つのタスクオブジェクトを実行全体で更新します。さらにホップ数を制限してください。

少数のホップを大幅に超える設計なら、タスク分解そのものを見直すべきです。

組み込みの引き継ぎ機能があるフレームワークは使うべきですか?

使って構いません。ただし、実際に何が転送されるかを確認してください。

多くの実装はメッセージ履歴だけを渡します。その場合、識別子はテキストに現れたときだけ残ります。フレームワークが運ぶ情報に加えて、構造化されたペイロードを渡しましょう。

サブエージェントごとにAPI認証情報が必要ですか?

はい。各エージェントの役割に合わせて、必要最小限の権限を付与します。

強力なキーを複数のエージェントで共有すると、被害範囲を制限できず、どのエージェントが呼び出したかも追跡しにくくなります。エージェントのための最小権限APIキーで具体的な設定方法を確認できます。

summaryにはどの程度の情報を入れるべきですか?

構造化フィールドでは表現できない意図やニュアンスを、数文で補足します。

ID、金額、ステータスなどを列挙し始めたら、それらは検証可能な構造化フィールドに移してください。

Top comments (0)