TL;DR
- マルチエージェント開発の実装パターンは「Hub-and-Spoke」「Pipeline」「Peer-to-Peer」の3つに集約できる
- Hub-and-Spoke は中央オーケストレーターが全制御を持つ。複雑なタスク分解に向く(経験則)
- Pipeline は直列の専門エージェント連鎖。変換・要約・翻訳フローに最適(経験則)
- Peer-to-Peer は疎結合な協調。実装コストが高く、限定的なユースケース向け(経験則)
- rate limit 制御には Semaphore パターンと
Promise.allSettledの組み合わせが実用的(経験則)
本記事はシリーズ「AI駆動開発のプロジェクト計画術」の第4回です。
はじめに:なぜ今マルチエージェントか
単一エージェントで複雑なタスクをこなそうとするとき、2つの壁にぶつかる。
1つ目はコンテキスト上限。Anthropicのコンテキストウィンドウ仕様によれば、Claude 4系でも実用的なワーキングメモリには上限がある。長大な文書を一括処理しようとすると、後半の指示が前半の情報に上書きされたり、重要な前提が「見えなく」なる問題が起きる。
2つ目は直列実行の非効率。調査・執筆・レビューを1つのエージェントが順番にこなすと、並列化できるはずの工程がボトルネックになる。
マルチエージェント設計は、これら2つの問題を構造的に解決するアプローチだ。Anthropicの公式マルチエージェント設計ガイドでは、エージェントを「サブエージェント」として委譲するパターンが標準的な実装として紹介されている。
本記事では、実際のプロジェクトで使える3つのアーキテクチャパターンを、通信トポロジーの視点で整理する。
1. 3アーキテクチャの全体像
マルチエージェント設計を「どのエージェントが誰と通信するか」というトポロジーで分類すると、実装判断が明確になる。ネットワーク設計の経験があるエンジニアには馴染みやすい整理方法だ。
比較表
| 観点 | Hub-and-Spoke | Pipeline | Peer-to-Peer |
|---|---|---|---|
| 複雑度 | 中 | 低 | 高 |
| スケーラビリティ | 中(Hub がボトルネック) | 高(ステージ追加が容易) | 高(ノード追加が容易) |
| エラー耐性 | 高(Hub で一元管理) | 低(前段失敗で全停止) | 中(部分障害が複雑) |
| 実装コスト | 低〜中 | 低 | 高 |
| 並列化 | 可能(Hub が制御) | 限定的 | 自然に並列 |
| 向くタスク | 複雑な分解・統合 | 変換・翻訳・要約 | 協調型創作・議論 |
2. Hub-and-Spoke パターン
構造と通信フロー
中央のオーケストレーター(Hub)がすべての指示を出し、専門エージェント(Spoke)が実行結果を Hub に返す。ユーザーや外部システムは Hub とのみ通信する。
graph TD
U[ユーザー] --> H[Hub: Orchestrator]
H --> A[Spoke A: Researcher]
H --> B[Spoke B: Writer]
H --> C[Spoke C: Reviewer]
A --> H
B --> H
C --> H
H --> U
向いているユースケース
- 記事生成ワークフロー: リサーチ→執筆→レビューを Hub が管理(本プロジェクトの conductor 設計がこれ)
- コードレビュー自動化: Hub がタスクを分解し、セキュリティ・パフォーマンス・品質の各 Spoke に並列委譲
- データ分析パイプライン: Hub が入力を分割して複数 Spoke で並列分析、結果を統合
実装のポイント
// Hub-and-Spoke の最小実装
interface SubAgentTask {
agentId: string;
prompt: string;
context?: string;
}
interface SubAgentResult {
agentId: string;
output: string;
status: "success" | "failed";
}
async function orchestrate(
tasks: SubAgentTask[],
maxConcurrent = 3
): Promise<SubAgentResult[]> {
// Semaphore で並列数を制御(後述)
const semaphore = new Semaphore(maxConcurrent);
const results = await Promise.allSettled(
tasks.map((task) =>
semaphore.acquire().then(async (release) => {
try {
const output = await callAgent(task.agentId, task.prompt);
return { agentId: task.agentId, output, status: "success" as const };
} catch (err) {
return {
agentId: task.agentId,
output: String(err),
status: "failed" as const,
};
} finally {
release();
}
})
)
);
return results.map((r) =>
r.status === "fulfilled"
? r.value
: { agentId: "unknown", output: String(r.reason), status: "failed" }
);
}
落とし穴
- Hub の文脈爆発: Spoke の全結果が Hub のコンテキストに蓄積する。巨大タスクでは Hub が上限超過する。解決策:Spoke の返却を要約させる
- Hub の単一障害点: Hub がタイムアウトすると全体が止まる。べき等な設計(再実行可能)と中断ポイント保存が必須
3. Pipeline パターン
構造と通信フロー
エージェントが直列に連鎖し、前のエージェントの出力が次のエージェントの入力になる。
graph LR
I[入力] --> A[Agent A: 抽出]
A --> B[Agent B: 翻訳]
B --> C[Agent C: 要約]
C --> D[Agent D: 校正]
D --> O[出力]
向いているユースケース
- 文書変換パイプライン: 原稿→構造化→翻訳→校正→公開
- コードレビューチェーン: lint → 静的解析 → AI レビュー → フォーマット
- データエンリッチメント: 取得 → クレンジング → エンリッチ → 保存
Anthropicのエージェントループ設計ガイドでは、ステップをシンプルに保ち、各ステップの出力を明確に定義することが推奨されている。Pipeline はこの原則と相性が良い。
実装のポイント
// Pipeline の最小実装
type PipelineStep = {
name: string;
process: (input: string) => Promise<string>;
};
async function runPipeline(
steps: PipelineStep[],
initialInput: string
): Promise<{ results: string[]; finalOutput: string }> {
const results: string[] = [];
let current = initialInput;
for (const step of steps) {
try {
current = await step.process(current);
results.push(current);
} catch (err) {
// 失敗ステップを記録してスロー(上流でリトライ判断)
throw new Error(`Pipeline failed at step "${step.name}": ${String(err)}`);
}
}
return { results, finalOutput: current };
}
落とし穴
- 前段失敗の全伝播: 途中のステップが失敗すると後続全体が止まる。ステップごとのリトライ上限とフォールバック出力を定義する
- 誤差の累積: 各ステップが少しずつ情報を変形すると、最終出力が原形と乖離する。中間チェックポイントで元情報と照合する仕組みが有効
4. Peer-to-Peer パターン
構造と通信フロー
中央制御なしに複数のエージェントが直接通信し合う。役割は流動的で、互いの出力を参照しながら協調する。
┌─────────────┐ ┌─────────────┐
│ Agent A │◄─────►│ Agent B │
│ (Proposer) │ │ (Critic) │
└──────┬──────┘ └──────┬──────┘
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Agent C │◄─────►│ Agent D │
│ (Verifier) │ │ (Synthesizer│
└─────────────┘ └─────────────┘
向いているユースケース
- 多角的レビュー: 複数の専門家エージェントが同じ成果物を独立に評価し、互いのレビューを参照して深化させる
- ブレインストーミング: 制約なしに複数のエージェントがアイデアを交換する
- 議論シミュレーション: 賛成・反対・中立のエージェントが意見を交わしてコンセンサスを形成する
落とし穴
- 実装コストが高い: 会話の収束条件・ループ防止・状態共有の設計が複雑。明確なユースケースなしに採用しない
- デッドロックリスク: 互いの返答を待ち合うと無限待機になる。タイムアウトと強制終了が必須
- 品質の不安定性: 中央制御がないため、成果物の一貫性が保証しにくい
5. 状態受け渡しの設計
マルチエージェント設計で最も見落とされがちなのが、エージェント間の状態共有だ。
3方式の比較
| 方式 | 特徴 | 向くケース | 注意点 |
|---|---|---|---|
| In-memory | 最速。同一プロセス内でのみ有効 | 短時間の並列処理 | クラッシュで消失 |
| ファイル(共有ストレージ) | シンプル。プロセスをまたげる | Worktree 分離環境 | 競合書き込みに注意 |
| データベース(KV/RDB) | 耐障害性高。クエリ可能 | 長時間・大規模 | インフラ依存が増える |
本プロジェクトでは、doc/Working/ARTICLE-<slug>/status.md をファイルベースの状態ストアとして使う設計を採用している。各フェーズ完了時に status.md を更新し、再起動後も状態を復元できる。
エラー伝播と再試行設計
エラーハンドリングの基本方針は3つだ。
- 局所化: エラーは発生したエージェントで最大限吸収し、上流に伝播させるエラーは「復旧不可能なもの」に限る
- べき等性: 同じ入力で同じ出力を保証する設計にすることで、リトライを安全にする
- 上限設定:
MAX_RETRY=3を鉄則とし、上限超過時は人間エスカレーションに切り替える
async function withRetry<T>(
fn: () => Promise<T>,
maxRetry = 3,
backoffMs = 1000
): Promise<T> {
let lastError: unknown;
for (let attempt = 1; attempt <= maxRetry; attempt++) {
try {
return await fn();
} catch (err) {
lastError = err;
if (attempt < maxRetry) {
// 指数バックオフ
await sleep(backoffMs * Math.pow(2, attempt - 1));
}
}
}
throw new Error(
`Failed after ${maxRetry} attempts. Last error: ${String(lastError)}`
);
}
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
6. 並列度制御と rate limit 対策
Claude API を含む多くの LLM API には、RPM(Requests Per Minute)と TPM(Tokens Per Minute)の制限がある。マルチエージェント設計では並列に複数リクエストが発生するため、無制御で並列実行すると rate limit エラーが多発する。
Semaphore パターン
同時実行数を上限付きで制御する Semaphore は、rate limit 対策の基本ツールだ。
class Semaphore {
private permits: number;
private queue: Array<() => void> = [];
constructor(maxConcurrent: number) {
this.permits = maxConcurrent;
}
async acquire(): Promise<() => void> {
if (this.permits > 0) {
this.permits--;
return () => this.release();
}
return new Promise<() => void>((resolve) => {
this.queue.push(() => {
this.permits--;
resolve(() => this.release());
});
});
}
private release(): void {
this.permits++;
const next = this.queue.shift();
if (next) next();
}
}
// 使用例: 最大3並列でエージェントを実行
const sem = new Semaphore(3);
const results = await Promise.allSettled(
agentTasks.map(async (task) => {
const release = await sem.acquire();
try {
return await runAgent(task);
} finally {
release();
}
})
);
バックオフ戦略
rate limit エラーが発生した場合は、指数バックオフで再試行する。多くの LLM API は 429 エラーに Retry-After ヘッダーを付与するため、その値を優先的に使う。
async function callWithBackoff(
apiFn: () => Promise<Response>,
maxAttempts = 5
): Promise<Response> {
for (let i = 0; i < maxAttempts; i++) {
const response = await apiFn();
if (response.status !== 429) return response;
const retryAfter = response.headers.get("Retry-After");
const waitMs = retryAfter
? parseInt(retryAfter) * 1000
: 1000 * Math.pow(2, i);
await sleep(waitMs);
}
throw new Error("Rate limit: max attempts exceeded");
}
Promise.allSettled を使うことで、一部のエージェントが失敗しても他の結果を破棄せずに収集できる。Hub-and-Spoke での並列 Spoke 実行では、Promise.all(一つ失敗で全体がリジェクト)より Promise.allSettled の方が適している場合が多い。
7. 実際のプロジェクトでの失敗例
事例: conductor 並列起動による git ブランチ衝突
本プロジェクト(Growth Lab)で2026年5月に実証した失敗だ。複数の article-conductor を同一リポジトリに対して並列起動したところ、D-12(PR作成)フェーズでブランチ名の衝突が発生した。
問題の構造:
Session A: main → article/slug-a ブランチを作成して作業
Session B: main → 同じブランチ名でチェックアウト → 衝突
解決策: worktree 分離
# 各 conductor を独立した worktree で起動する
git worktree add .claude/worktrees/agent-<id> -b article/<slug>
# 完了後に worktree を削除
git worktree remove .claude/worktrees/agent-<id>
Git Worktree を使うことで、同一リポジトリの複数ブランチを物理的に別ディレクトリで並列作業できる。これは Hub-and-Spoke パターンで複数 Spoke が同じファイルシステムを使う場合の一般的な解法でもある。
事例: Pipeline の中間失敗でデータ損失
別の事例として、5ステップの Pipeline 中、ステップ3が失敗したときにステップ1〜2の出力が廃棄された。
解決策: 各ステップの出力をファイルに保存し、再実行時は成功済みステップをスキップする。
async function runResumablePipeline(
steps: PipelineStep[],
input: string,
checkpointDir: string
): Promise<string> {
let current = input;
for (let i = 0; i < steps.length; i++) {
const checkpointFile = `${checkpointDir}/step-${i}.json`;
// 既存チェックポイントがあればスキップ
if (await fileExists(checkpointFile)) {
const saved = await readJson(checkpointFile);
current = saved.output;
continue;
}
current = await steps[i].process(current);
await writeJson(checkpointFile, { output: current });
}
return current;
}
8. Claude と Codex を組み合わせる実装例
OpenAI Codex はコード生成に強く、Claude はコンテキスト理解と構造化出力に強い。この特性差を活かした役割分担が有効だ。
役割分担の設計
| タスク | 推奨エージェント | 理由 |
|---|---|---|
| 要件定義・設計文書の分析 | Claude | 長文理解・構造化能力 |
| コードスケルトン生成 | Codex | コード生成特化 |
| テストケース作成 | Codex | コード生成特化 |
| コードレビュー・品質評価 | Claude | 推論・説明能力 |
| ドキュメント生成 | Claude | 日本語・長文生成能力 |
Anthropicのサブエージェント設計ガイドでは、Claude Code がサブエージェントを Task ツールで起動するパターンが公式に文書化されている。
Hub-and-Spoke による実装例
// Claude を Hub、Claude + Codex を Spoke とした設計
const analysisTask = {
agentId: "claude",
prompt: `以下の要件を分析し、実装すべき関数リストをJSON形式で出力してください:\n${requirements}`,
};
const codingTasks = functionList.map((fn) => ({
agentId: "codex",
prompt: `以下の仕様で TypeScript 関数を実装してください:\n${fn.spec}`,
}));
// Hub(Claude)が分析
const analysis = await callClaude(analysisTask.prompt);
const functionList = parseJson(analysis);
// Spoke(Codex)を並列実行(Semaphore で rate limit 制御)
const codeResults = await orchestrate(codingTasks, 3);
// Hub(Claude)が統合とレビュー
const reviewPrompt = `以下のコードをレビューし、問題点を指摘してください:\n${codeResults}`;
const review = await callClaude(reviewPrompt);
パターン選択フレームワーク
どのパターンを選ぶかは、次のフローで判断できる。
タスクを複数エージェントに分割したい
│
├── タスク間に依存関係がある?
│ ├── YES → Pipeline パターン
│ │ (前段の出力が後段の入力になる)
│ └── NO → Hub-and-Spoke パターン
│ (独立タスクを Hub が管理)
│
└── エージェント同士が対話しながら成果を作りたい?
├── YES → Peer-to-Peer パターン
│ (実装コストを許容できるか確認)
└── NO → Hub-and-Spoke パターン(推奨デフォルト)
迷ったら Hub-and-Spoke から始めることを推奨する。実装コストが低く、後からアーキテクチャを変更しやすいためだ。AIエージェントの3層アーキテクチャで制御層の設計を深掘りすることで、このパターンを最大限活用できる。
まとめ
マルチエージェント開発の実装パターンを3つのトポロジーで整理した。
- Hub-and-Spoke: 中央制御による安定性。迷ったらこれを選ぶ
- Pipeline: 直列変換の明快さ。変換・翻訳・要約フローに最適
- Peer-to-Peer: 協調の柔軟性。実装コストが高いため限定利用
実装で最も重要なのは「状態の設計」と「rate limit 制御」だ。Semaphore パターンと Promise.allSettled の組み合わせを最初から組み込んでおくことで、本番環境での安定性が大きく改善する。
次のステップとして、AIペアプログラミングのコンテキスト設計でエージェント単体の活用を深掘りするか、Gate-Driven開発のプロセス設計でマルチエージェントをプロジェクト管理に組み込む方法を確認してほしい。
FAQ
マルチエージェント開発とは何ですか?
複数の AI エージェントがそれぞれ異なるタスクを担当し、連携して目標を達成するシステム設計の手法です。単一エージェントの制限(コンテキスト上限・逐次処理の非効率)を克服するために使います。
Hub-and-Spoke と Pipeline パターンの違いは?
Hub-and-Spoke は中央のオーケストレーターが全エージェントを管理し、並列実行も制御します。Pipeline は前段の出力が後段の入力になる直列連鎖です。タスク間に依存関係があれば Pipeline、独立したタスクを並列処理するなら Hub-and-Spoke が適しています。
エージェント間で状態を共有する最良の方法は?
短時間・同一プロセスなら In-memory、別プロセス・クラッシュ復元が必要なら共有ファイル(JSON/Markdown)、長時間・大規模なら KV ストアや RDB を使います。まずファイルベースから始めると実装がシンプルで、ほとんどのユースケースをカバーできます(経験則)。
rate limit を超えないように並列エージェントを制御するには?
Semaphore パターンで同時実行数を制限し(推奨: 3〜5並列)、rate limit エラー(HTTP 429)時は指数バックオフで再試行します。API の Retry-After ヘッダーがある場合はその値を優先します。
エージェントがエラーになったとき再試行はどう設計する?
MAX_RETRY=3 を上限とし、指数バックオフで再試行します。上限超過時は人間エスカレーションに切り替えます。再試行を安全にするために、各処理をべき等(同じ入力で同じ出力)に設計することが前提です。Pipeline では中間チェックポイントをファイルに保存することで、再実行時に成功済みステップをスキップできます。
本記事はシリーズ「AI駆動開発のプロジェクト計画術」の一部です。
