multi-hop delegation をトレース・デバッグする¶
目的: 1 つの agent のリクエストがピアに fan-out されて統合された返信として戻ってくるとき、何が起きているかを理解する。デバッグ、キャパシティプランニング、チェーンセマンティクスに依存する Skill の作成に有用。
使うべき状況¶
- ユーザーが最終的な返信を受け取ったが、どの agent が貢献したかを知りたい。
- チェーンがハングしており、デリゲートが応答していないと思われる。
safety.loop.max_agent_hopsを調整していて、実際のチェーンを見たい。messages_to_agentsを出力する Skill を構築していて、外部から遅延返信メカニズムを検証したい。
ユーザー視点で何が見えるか¶
ユーザーが開始したチェーンでは、発信元 agent の最初のルーターパスが即座に中間返信を送信します:
すべてのデリゲートが応答した後、発信元 agent のルーターが返信を履歴に入れて再実行し、最終的な統合テキストを生成します:
中間メッセージはストリーミングや部分的な出力の成果物ではありません。独立した完全な LLM ターンです。遅延返信メカニズムは agent が開始した チェーンにのみ適用されます。ユーザーが開始したチェーンでは中間+最終の UX を維持し、「作業中です」をすぐに確認できます。
このウォークスルーのセットアップ¶
reyn agent new lead --role "team lead. Triages and synthesizes."
reyn agent new researcher --role "deep technical research, primary sources only."
reyn agent new archivist --role "verifies historical context (release notes, blog posts)."
Topology なし — 自動管理の _default が 3 者をカバーするので、全員が自由に通信できます。lead にアタッチします:
1 つのチェーンをエンドツーエンドでトレースする¶
すべてのトップレベルのユーザー送信には新しい chain_id(uuid4 hex)が付与され、その後のすべての agent 間メッセージにスレッドされます。1 つのチェーンの chain_id を見つけます:
# ターンが終わった後:
tail -1 .reyn/agents/lead/events.jsonl | jq -r '.data.chain_id'
# → 71d6c8b8e7e04a0d8b6f1e3c8d92a4ab
次に、このチェーンに触れたすべてのイベントをすべての agent にわたって見つけます:
CHAIN=71d6c8b8e7e04a0d8b6f1e3c8d92a4ab
for agent in lead researcher archivist; do
echo "=== $agent ==="
grep "$CHAIN" .reyn/agents/$agent/events.jsonl
done
次のようなものが表示されます:
=== lead ===
{"type":"user_message_received","data":{"chain_id":"71d6...","text":"DuckDB v1 を調査..."}}
{"type":"agent_message_sent","data":{"kind":"agent_request","from_agent":"lead","to_agent":"researcher","depth":1,"chain_id":"71d6..."}}
{"type":"agent_response_received","data":{"from_agent":"researcher","depth":1,"chain_id":"71d6..."}}
=== researcher ===
{"type":"agent_request_received","data":{"from_agent":"lead","depth":1,"chain_id":"71d6..."}}
{"type":"agent_message_sent","data":{"kind":"agent_request","from_agent":"researcher","to_agent":"archivist","depth":2,"chain_id":"71d6..."}}
{"type":"agent_response_received","data":{"from_agent":"archivist","depth":2,"chain_id":"71d6..."}}
{"type":"agent_message_sent","data":{"kind":"agent_response","from_agent":"researcher","to_agent":"lead","depth":1,"chain_id":"71d6..."}}
=== archivist ===
{"type":"agent_request_received","data":{"from_agent":"researcher","depth":2,"chain_id":"71d6..."}}
{"type":"agent_message_sent","data":{"kind":"agent_response","from_agent":"archivist","to_agent":"researcher","depth":2,"chain_id":"71d6..."}}
上から下に読むと: user → lead → researcher → archivist → researcher → lead → user。depth は各ホップがユーザー送信からどれだけ離れているかを示します。
遅延返信がイベントで見えること¶
researcher は archivist からの agent_response_received が届くまで lead への agent_message_sent (response) を出力しないことに注目してください。これが遅延返信メカニズムです: researcher のルーターが messages_to_agents(ここでは archivist へ)を出力すると、レジストリは chain_id をキーとする _PendingChain を保持し、waiting_on のすべてのエントリーが解決されるまで lead への返信を待ちます。
fan-out の場合(researcher が 1 ターンで複数のピアに委任)、researcher のルーターが再び実行されて統合するまでに、すべてのデリゲートが応答する必要があります。遅い 1 つのデリゲートは safety.timeout.chain_seconds(デフォルト 60 秒)まで統合全体を遅延させます。時間を超えると chain_timeout イベントが発生し、上流の agent は統合されたエラーレスポンスを受け取るのでチェーンがハングしません。
/attach でライブ監視する¶
lead がユーザーターンを処理している間、REPL ポインターをデリゲートに切り替えてその進捗を監視できます:
> DuckDB v1 の破壊的変更を調査し、200 語の変更ログサマリーを作成してください。
[lead] (researcher と writer で調査中)
/attach researcher
attached: researcher
[researcher] (archivist で確認中)
[researcher] DuckDB v1 では...
lead の session.run() はバックグラウンドで受信トレイを消費し続けるので、(/attach lead で)切り戻したときには、統合された最終返信がすでに届いています。
max_hop_depth の拒否¶
重複する Topology が safety.loop.max_agent_hops が許可するよりも深いツリーを形成する場合、ランタイムは過度に深い送信を拒否します:
そして監査イベントを発行します:
{"type":"agent_message_refused","data":{"reason":"max_hop_depth","to_agent":"deep_specialist","depth":4,"chain_id":"71d6..."}}
発信元チェーンの上流 agent での保留状態は safety.timeout.chain_seconds(デフォルト 60 秒)を待ち、その後強制的に統合されたエラーレスポンスで解決されます。events リファレンス の chain_timeout イベントを参照してください。上流 agent は自動的にブロック解除されます。プロセスの再起動は不要です。
履歴メタの確認¶
各 agent の history.jsonl は、送受信したメッセージを meta.source で識別しながら(どちら側にいたか)、chain_id と共に記録します:
grep "71d6c8b8" .reyn/agents/researcher/history.jsonl | jq '{role, source: .meta.source, depth: .meta.depth, text: .text[:60]}'
{"role":"user","source":"agent_request","depth":1,"text":"破壊的変更を調べてください..."}
{"role":"agent","source":"agent_request_outgoing","depth":2,"text":"v0.x のリリースノートを確認して..."}
{"role":"user","source":"agent_response","depth":2,"text":"v0.9 には破壊的変更はなかった..."}
{"role":"agent","source":"agent_response_outgoing","depth":1,"text":"DuckDB v1 では..."}
4 つのエントリー、4 つの meta.source 値: 受信リクエスト、送信委任、受信レスポンス、送信返信。この agent 側の完全なチェーンはファイルだけで再構築できます。
アンチパターン: Skill 入力で chain_id に依存する¶
chain_id は監査専用です。ルーター LLM はそれを見ません。それを参照する Phase プロンプトを書かないでください。デバッグの breadcrumb として厳密に扱ってください。Skill コードでの cross-skill の相関が必要な場合は run_id(OS がすでに meta に組み込んでいる)を使用してください。
関連情報¶
- コンセプト: multi-agent — チェーンセマンティクス、遅延返信、fan-out
- リファレンス: events —
chain_idを持つagent_message_*イベントのペイロード - リファレンス: multi-agent config —
max_hop_depth - ハウツー: events によるデバッグ
- ハウツー: agent チームを構築する