2026 年にマルチエージェント システムを構築している人は、今でも直線的な処理を書いています。ステップ 1、次にステップ 2、そしてステップ 3 — それぞれが前のステップを待っています。ここでは、なぜそれが遅いのか、そしてその修正方法を説明します。

誰もチェックしていない問題
あなたはマルチステップ エージェントを構築しました。それは動作します。しかし、遅いです。
あなたはモデルがボトルネックだと思い込んでいます。しかし、そうではありません。
ボトルネックは、あなたが描いた形状です。チェーン — ステップ 1 はステップ 2 を待ち、ステップ 2 はステップ 3 を待つ — は、それらのステップの半分が互いに何の関係もない場合でも、逐次実行を強制します。
「このドキュメントを要約して、天気を確認する」というのは、2 つの独立した仕事が 1 つのワークフローとしてまとめられたものです。天気のタスクは要約を必要としません。これまで一度も必要としたことはありません。しかし、それをチェーンとして書くと、とにかく待つことになります。
この無駄な待ち時間が、数十のステップにわたって増幅され、ランタイムの大部分を消費しているのです。
第1章 - ループ vs グラフ
ループは、自己改善の 1 つの単位です。
1何かを試す → 結果を確認する → 調整する → もう一度試す
それが原子です。1 つのエージェント、1 つのメトリクス、収束するまでサイクルします。
ループには既知の障害モードがあります。それは、測定したものだけを最適化し、それ以外は無視することです。チケットを早くクローズするように調整されたサポート ボットは、チケットを早くクローズします — その間、満足度は静かに低下します。ループは自身のメトリクスの外側を見ることができません。これが、エージェント アーキテクチャに現れるグッドハートの法則です。
グラフは設計によってこれを修正します。1 つの数値を追いかける 1 つのループの代わりに、互いに監視し合い、修正し合うループのネットワークを構築します。ノード A の出力はノード B に供給されます。ノード C は独立して実行され、両方をチェックします。システム全体を駆動する単一のメトリクスはありません — 構造が駆動します。

エージェント システムにとって、これは 1 つの具体的なシフトを意味します。すべてを上から下まで行う 1 つのエージェントを書くのをやめることです。最初に作業の形状を設計します — 何が何の前に起こる必要があるか、何を同時に実行できるか、何が実際に待つ必要があるか。
第2章 - ノード、エッジ、そしてそれらを区別するテスト
グラフには正確に 2 つのコンポーネントがあります。
ノード — 1 つの作業単位。1 つのエージェント、1 つのジョブ、1 つの入力、1 つの出力。
エッジ — 実際の依存関係。ノード B の入力にはノード A の出力が必要です。
ほとんどの人が犯す間違いは、デフォルトで「そして」をエッジとして扱うことです。
1「このコードベースを読んで、変更ログを書いて」2「価格ページを取得して、競合他社の機能を要約して」
ワークフロー内のすべての「そして」に対して、1 つの質問をしてください。
次のステップは実際に前のステップの出力を読み取りますか?
はいの場合 → 実際のエッジ。順序を維持します。
いいえの場合 → エッジなし。待機は無駄です。並列実行します。
2 つのタスク間の境界をデータが通過しない場合、それらは独立しています — そして、逐次実行している独立したペアはすべて、無料で捨てているランタイムです。
以下は、コードに適用されたテストです。
1from dataclasses import dataclass23@dataclass4class TaskNode:5 id: str6 prompt: str7 depends_on: list[str] # IDs of nodes this one actually needs89def has_real_edge(node_a: TaskNode, node_b: TaskNode) -> bool:10 """11 コア グラフ エンジニアリング テスト:12 node_b のプロンプトは実際に node_a の出力を必要としますか?13 """14 return node_a.id in node_b.depends_on1516# 例:ほとんどの「チェーン」は 2〜3 の実際の依存関係グループに集約される17nodes = [18 TaskNode("audit_routes", "List all API route files", []),19 TaskNode("check_auth", "Check auth middleware coverage", []),20 TaskNode("fetch_weather", "Get today's weather", []),21 TaskNode("summarize", "Summarize route + auth findings",22 depends_on=["audit_routes", "check_auth"]),23]2425# audit_routes、check_auth、fetch_weather の間にはエッジがない26# これらは並列実行される。実際のエッジを持つのは "summarize" だけです -- 待機が発生します。
あなたの現在の「A を実行、次に B、次に C」エージェントは、技術的にはすでにグラフです。それは、可能な限り最悪のグラフ — C が停止すると、下流の何も実行されない単一のチェーン — にすぎません。
第3章 - 最初のグラフを構築する

要件:
- Claude Code(Dynamic Workflows をサポートする最新バージョン)
- Max、Team、または Enterprise プラン — ワークフローはデフォルトでオン。Pro の場合は手動で有効化。
実際のリポジトリを開いてください。おもちゃの例ではありません — その効果は実際の規模でのみ現れます。
最初のグラフを開始するプロンプト:
1このコードベース内のすべてのルート ファイルを監査するワークフローを作成します。23各ルート ファイルについて、独立してチェックします:4- 認証ミドルウェアが存在する5- すべてのパラメータに入力検証がある6- レート制限が設定されている7- エラーハンドリングがスタック トレースを漏洩しない89これらのチェックをすべてのルート ファイルにわたって並列実行します —10互いに依存していません。1112すべてのファイルのチェックが完了したら、重要度(critical、warning、info)ごとにグループ化された131 つの統合レポートを生成します。1415統合ステップは、すべてのチェックが完了するのを待つ必要があります。16その前のすべてのステップは待つべきではありません。
プロンプト自体に埋め込まれた構造に注目してください。並列作業が明示的に呼び出され、唯一の実際の依存関係(すべてのチェックが完了するのを待つ統合)が明示的に名前付けられています。エージェントがグラフを推測することを期待しているのではなく、あなたがそれを説明しているのです。
内部で何が起こっているか — オーケストレーションの簡略版:
1import asyncio2from anthropic import Anthropic34client = Anthropic()56async def audit_route_file(filepath: str) -> dict:7 """1 つのノード。他のすべてのルート ファイルから独立して実行されます。"""8 response = await client.messages.create(9 model="claude-sonnet-5",10 max_tokens=1000,11 messages=[{12 "role": "user",13 "content": f"""Audit this route file for:14 - auth middleware, input validation,15 rate limiting, error handling1617 File: {filepath}1819 Return JSON: {{"file": "", "issues": [], "severity": ""}}"""20 }]21 )22 return {"file": filepath, "result": response.content[0].text}2324async def consolidate(results: list[dict]) -> str:25 """唯一の実際のエッジ -- すべての監査ノードが完了するのを待ちます。"""26 response = await client.messages.create(27 model="claude-opus-4-8",28 max_tokens=2000,29 messages=[{30 "role": "user",31 "content": f"""Consolidate these {len(results)} route audits32 into one report grouped by severity:3334 {results}"""35 }]36 )37 return response.content[0].text3839async def run_graph(route_files: list[str]):40 # ファンアウト -- すべての独立したノードが並行して実行される41 audit_tasks = [audit_route_file(f) for f in route_files]42 results = await asyncio.gather(*audit_tasks)4344 # ファンイン -- 実際の依存関係を持つ唯一のノード45 report = await consolidate(results)46 return report4748# 40 個のルート ファイル、1 つのプロンプト、1 回の並列パス49results = asyncio.run(run_graph([50 f"routes/{f}.py" for f in ["auth", "users", "billing", "orders"]51 # ...36 more
40 回のシーケンシャルな API 呼び出しがそれぞれ約 8 秒かかると、5 分以上になります。同じ 40 回の呼び出しを並列にファンアウトすると、15 秒未満になり、すべての合計時間ではなく、最も遅い単一ファイルによって制限されます。
第4章 - グラフが実際に壊れる場所
グラフ エンジニアリングは、予測可能な 3 つの場所で失敗します。発生する前に知っておきましょう。
コンテキストの崩壊。 1,000 個のノードをファンアウトし、その 1,000 個すべての出力を 1 つの統合ステップにフィードしようとすると、合成が始まる前にコンテキスト ウィンドウを超えてしまいます。
修正方法: ファンインを階層化します。ノードを 20〜50 のバッチにグループ化し、各バッチを要約してから、生の出力ではなく要約を統合します。
1async def layered_consolidate(results: list[dict], batch_size: int = 30):2 """階層的なファンイン -- 大規模な生の出力を合成しないでください。"""3 batches = [results[i:i+batch_size]4 for i in range(0, len(results), batch_size)]56 batch_summaries = await asyncio.gather(*[7 summarize_batch(batch) for batch in batches8 ])910 # 最終的な統合は、1,000 の生の結果ではなく、要約に対して機能します11 return await consolidate(batch_summaries)
誤った独立性。 2 つのノードのプロンプトが互いに参照していないため、独立していると想定しますが、両方が同じファイルに書き込んだり、同じレート制限のある API にヒットしたりします。それは隠れたエッジです。
修正方法: 共有データだけでなく、共有リソースも監査します。書き込み競合がある 2 つのノードは、データ依存関係がゼロでもエッジが必要です。
サイレント ノード障害。 チェーンでは、1 つの障害ですべてが停止します — 煩わしいですが明らかです。グラフでは、200 個中の 1 つの障害ノードが、完全に見えるレポートに紛れ込む可能性があります。
修正方法: すべてのファンイン ステップは、合成前にノード数を期待数と照合し、不完全なデータで静かに処理するのではなく、ギャップを明示的にフラグします。
1async def safe_consolidate(results: list[dict], expected_count: int):2 if len(results) < expected_count:3 missing = expected_count - len(results)4 print(f"WARNING: {missing} 個のノードがサイレントに失敗しました。"5 f"レポートは不完全になります。")6 return await consolidate(results)
第5章 - 実際のフリートへのスケーリング

パターンが 40 ノードで機能すれば、数百へのスケーリングは、第 2 章以降で正しくグラフを構築していれば、再設計ではなく設定変更です。
完全なプロダクション形状:
1 オーケストレーター2 |3 +--------+-------+-------+--------+4 v v v v v5 ノード 1 ノード 2 ノード 3 ... ノード N6 (並列、相互にエッジなし)7 | | | |8 +--------+-------+-------+-------+9 v10 バッチ要約 <- 階層的ファンイン11 (30個ずつのグループ)12 v13 最終レポート <- 唯一の真のエッジ
オーケストレーターの唯一の仕事は、タスクをノードに分解し、実際のエッジを特定し、ディスパッチすることです。それ自体は作業を行いません — グラフを描くだけです。
1async def orchestrate(task: str, resources: list[str]):2 """3 オーケストレーター ノード -- 分解は行うが、実行はしない。4 """5 plan = await client.messages.create(6 model="claude-opus-4-8",7 max_tokens=2000,8 messages=[{9 "role": "user",10 "content": f"""Task: {task}11 Resources available: {resources}1213 Decompose into a graph:14 - List each independent node (no shared edges)15 - List any real dependencies between nodes16 - Group nodes into fan-in batches if count > 501718 Return JSON with: nodes, edges, batch_groups"""19 }]20 )2122 graph = parse_plan(plan.content[0].text)2324 # 独立したノードを並列実行25 node_results = await asyncio.gather(*[26 execute_node(n) for n in graph["nodes"] if not n["depends_on"]27 ])2829 # 次に、実際のエッジのみを尊重して依存ノードを実行30 final = await execute_dependent_chain(graph["edges"], node_results)3132 return final
これが、グラフ エンジニアリングが表す実際のシフトです。あなたはすべてのステップを書く人ではなくなり、依存関係構造を設計する人になります。エージェントがノードを埋めます。あなたはエッジを所有します。
直線ではなくグラフで考えると何が変わるか
40 ステップのリニア エージェントには、40 のシーケンシャルな障害ポイントと、最も遅い単一ステップの 40 倍のレイテンシがあります。
同じ 40 の作業単位を持つグラフには、実際の依存関係の数(通常はほとんどのワークフローで 3〜5)の並列障害ポイントがあり、レイテンシは総ステップ数ではなく、最も遅いレイヤーによって制限されます。
それはわずかな高速化ではありません。5 分かかるワークフローと 15 秒かかるワークフローの違いであり、まったく同じ基盤となる作業を実行しています。
モデルは決してボトルネックではありませんでした。あなたが描いた線がボトルネックだったのです。
これは、2026 年 7 月時点のマルチエージェント オーケストレーション パターンの技術的な内訳です。コード例は説明用です — 大規模にデプロイする前に、エラーハンドリング、レート制限、およびリトライ ロジックを運用環境に合わせて調整してください。
お読みいただきありがとうございます。





