このレッスンで学ぶこと
- 複数エージェントの協調パターン(並列・直列・階層)を区別し、状況に応じて選べるようになる
- エージェント間でデータをどう受け渡すか、設計レベルで判断できるようになる
- 「調査 → 実装 → レビュー」という3エージェント構成のパイプラインを、CLI・Agent SDKの両方の視点で構築する
前提条件
- 応用コース Lesson 7(サブエージェントの設計と活用)を修了し、タスク分解・結果統合の基本パターンを理解していること
- 応用コース Lesson 9(Agent SDKでカスタムエージェントを構築する)で、セッション管理・カスタムツールの実装方法を理解していること
Lesson 7では「1つのメインエージェントが複数のサブエージェントに仕事を振る」という構図を扱いました。このレッスンでは視点を一段上げ、複数のエージェントが役割を持って連携する仕組み全体をパターンとして整理し、実際にパイプラインとして組み立てます。
複数エージェントの協調パターン
複数のエージェントを組み合わせる方法は、エージェント同士の依存関係によって大きく3つに分類できます。
graph TD
subgraph "並列パターン"
P1["メイン"] --> PA["エージェントA"]
P1 --> PB["エージェントB"]
PA --> PR["結果統合"]
PB --> PR
endgraph LR
subgraph "直列パターン"
S1["エージェントA"] --> S2["エージェントB"] --> S3["エージェントC"]
endgraph TD
subgraph "階層パターン"
H1["統括エージェント"]
H1 --> H2["中間エージェント1"]
H1 --> H3["中間エージェント2"]
H2 --> H4["末端エージェントA"]
H2 --> H5["末端エージェントB"]
end| パターン | 特徴 | 向いている場面 |
|---|---|---|
| 並列 | 複数のエージェントが同時に独立して動く | 互いに依存しない複数領域の調査・レビュー |
| 直列 | 前段の出力が後段の入力になる | 調査→実装→レビューのように、工程に順序性がある作業 |
| 階層 | 統括エージェントが中間エージェントに仕事を振り、中間エージェントがさらに末端に振る | 大規模な調査やタスクを、複数レベルの委譲で分割したい場合 |
Lesson 7で扱った並列実行・逐次実行は、この並列パターン・直列パターンの実装そのものです。このレッスンでは、それらを組み合わせた直列パイプラインを中心に、実際に手を動かして構築します。
サブエージェントは自身がさらにサブエージェントを生成できますが、階層が深くなるほど「今どのレベルで何が起きているか」の把握が難しくなり、エラー時の原因切り分けも複雑になります。深さには上限があり、無制限に階層化できるわけでもありません。まずは並列・直列の組み合わせで設計し、本当に必要な場合にのみ階層化を検討しましょう。
エージェント間のデータ受け渡し設計
複数エージェントを連携させるとき、最も設計判断が問われるのが「前段のエージェントの出力を、どう後段に渡すか」です。大きく2つのアプローチがあります。
アプローチ1: メインエージェントが仲介する(CLI向き)
CLIでサブエージェントを使う場合、各サブエージェントの出力はいったんメインエージェント(あなたと対話しているClaude)に返ります。メインエージェントが内容を理解し、次のサブエージェントへの指示として再構成してから渡します。
graph LR
A["調査サブエージェント"] -->|"調査結果(生データ)"| M["メインエージェント"]
M -->|"要点を整形した指示"| B["実装サブエージェント"]
B -->|"diff"| M
M -->|"diff + レビュー観点"| C["レビューサブエージェント"]
style M fill:#fff3e0,stroke:#e65100このアプローチの利点は、メインエージェントが「次に何を渡すべきか」を都度判断できる柔軟性です。一方で、すべてのデータがメインのコンテキストを経由するため、各段階の出力が大きいとコンテキスト消費がかさみます。
アプローチ2: 構造化データで直接受け渡す(Agent SDK向き)
Agent SDKでパイプラインをプログラムとして組む場合は、各エージェントのquery()呼び出しの結果(テキストやstructuredContent)を変数に保持し、次のquery()のプロンプトに直接埋め込めます。メインの「対話エージェント」が介在しないため、各段階の入出力を明示的にコードで制御できます。
graph LR
A["query() #1<br/>調査"] -->|"result文字列"| Code["呼び出し側のコード"]
Code -->|"プロンプトに埋め込み"| B["query() #2<br/>実装"]
B -->|"result文字列"| Code2["呼び出し側のコード"]
Code2 -->|"プロンプトに埋め込み"| C["query() #3<br/>レビュー"]
style Code fill:#e8f4fd,stroke:#1a73e8
style Code2 fill:#e8f4fd,stroke:#1a73e8この方式では、各段階の出力をログに残す、必要な部分だけ抽出する、フォーマットを検証してから次に渡す、といった処理をコードとして明示的に書けます。CI/CDのような自動化パイプラインでは、こちらのアプローチがより堅牢です。
実践: 調査 → 実装 → レビューの3エージェント構成
ここからは、「バグ修正」を題材に、調査・実装・レビューという役割の異なる3つのエージェントを連携させるパイプラインを、CLIとAgent SDKの両方で構築します。
CLIでの構築: サブエージェント定義 + 明示的な指示
まず、3つの役割に対応するサブエージェントを定義します。
---
name: bug-investigator
description: バグの原因を調査する。再現条件・関連コード・根本原因を特定する。バグ報告を受けたときに使用する。
tools: Read, Grep, Glob, Bash
---
報告されたバグについて、関連するコードを調査し、以下を報告してください。
1. 再現条件(どんな入力・操作で発生するか)
2. 根本原因と考えられる箇所(ファイルパス・行番号付き)
3. 修正方針の提案(複数ある場合は選択肢として)
実装はしないでください。調査と原因特定に専念してください。---
name: bug-fixer
description: 調査済みのバグを修正実装する。bug-investigatorの調査結果を受け取って実装する。
tools: Read, Edit, Write, Bash
---
渡された調査結果(根本原因・修正方針)に基づいて、修正を実装してください。
実装後、関連するテストを実行して結果を報告してください。
テストが存在しない場合は、再発防止のためのテストも追加してください。---
name: fix-reviewer
description: バグ修正の実装をレビューする。実装の妥当性とエッジケースの考慮漏れを確認する。
tools: Read, Bash
---
実装されたバグ修正の diff をレビューしてください。
- 報告されたバグが実際に解消されているか
- 修正によって新たな問題が生まれていないか(副作用・エッジケース)
- テストが修正内容を適切にカバーしているか
指摘は「ブロッカー」「推奨」「問題なし」の3区分で報告してください。これら3つを.claude/agents/に配置したうえで、メインエージェントにパイプラインとして実行するよう依頼します。
次の手順でログイン画面のバグを修正して。
1. bug-investigatorサブエージェントで「ログイン後にリダイレクトが2回発生する」
バグの原因を調査する
2. 調査結果をもとに、bug-fixerサブエージェントで修正を実装する
3. 実装結果をfix-reviewerサブエージェントでレビューする
4. レビューでブロッカーがあれば、bug-fixerに差し戻して再修正するこの指示の重要な点は、各サブエージェントへの「何を渡すか」をメインエージェントの判断に委ねつつ、全体の手順と「ブロッカーがあれば差し戻す」という条件分岐を明示していることです。直列パイプラインでは、こうした全体設計をメインエージェントへの指示として明文化することが、安定した実行の鍵になります。
Agent SDKでの構築: query()を3回つなぐ
同じパイプラインをAgent SDKでプログラム化すると、各段階の入出力をコードで明示的に制御できます。
import { query } from "@anthropic-ai/claude-agent-sdk";
async function runResultText(prompt: string, allowedTools: string[]): Promise<string> {
let result = "";
for await (const message of query({ prompt, options: { allowedTools } })) {
if (message.type === "result" && message.subtype === "success") {
result = message.result;
}
}
return result;
}
async function fixBugPipeline(bugReport: string) {
// ステージ1: 調査(読み取り専用ツールのみ許可)
const investigation = await runResultText(
`次のバグについて、原因調査だけを行い、実装はしないでください。\n\nバグ報告: ${bugReport}`,
["Read", "Grep", "Glob", "Bash"]
);
console.log("=== 調査結果 ===\n", investigation);
// ステージ2: 実装(調査結果を明示的にプロンプトへ埋め込む)
const implementation = await runResultText(
`以下の調査結果に基づいて修正を実装し、テストを実行してください。\n\n調査結果:\n${investigation}`,
["Read", "Edit", "Write", "Bash"]
);
console.log("=== 実装結果 ===\n", implementation);
// ステージ3: レビュー(実装結果を渡す。読み取り専用)
const review = await runResultText(
`次の実装内容をレビューし、「ブロッカー」「推奨」「問題なし」の3区分で報告してください。\n\n実装結果:\n${implementation}`,
["Read", "Bash"]
);
console.log("=== レビュー結果 ===\n", review);
return { investigation, implementation, review };
}
fixBugPipeline("ログイン後にリダイレクトが2回発生する");CLIのサブエージェント版との違いに注目してください。Agent SDK版では、各ステージがそれぞれ独立したquery()呼び出しであり、allowedToolsを段階ごとに絞り込むことで「調査段階ではファイルを書き換えられない」「レビュー段階では読み取りしかできない」という制約をコードレベルで強制できます。これはCLIでのtools制限を、より明示的にプログラムへ落とし込んだ形です。
上記の例では、各ステージの結果をプロンプトに文字列として埋め込み直しています。代わりに、Lesson 9で扱ったresumeを使って同一セッションを引き継ぐことも可能です。ただしその場合、後段のエージェントが前段の会話全体(ツール呼び出しの詳細含む)を引き継ぐため、コンテキスト消費は増えます。「結果の要点だけを渡したい」ならプロンプト埋め込み、「文脈をまるごと引き継ぎたい」ならresume、という使い分けが目安になります。
条件分岐を組み込む
CLIの例で触れた「ブロッカーがあれば差し戻す」という条件分岐も、Agent SDK版ではコードのif文として素直に表現できます。
async function fixBugPipelineWithRetry(bugReport: string, maxRetries = 2) {
const investigation = await runResultText(
`次のバグについて原因調査だけを行ってください。\n\nバグ報告: ${bugReport}`,
["Read", "Grep", "Glob", "Bash"]
);
let implementation = await runResultText(
`以下の調査結果に基づいて修正を実装してください。\n\n調査結果:\n${investigation}`,
["Read", "Edit", "Write", "Bash"]
);
for (let attempt = 0; attempt < maxRetries; attempt++) {
const review = await runResultText(
`次の実装をレビューし、ブロッカーがあれば具体的に指摘してください。先頭に「ブロッカーあり」か「問題なし」と明記すること。\n\n実装結果:\n${implementation}`,
["Read", "Bash"]
);
if (review.startsWith("問題なし")) {
console.log("レビュー完了:", review);
return { investigation, implementation, review };
}
// ブロッカーが残っている場合は、レビュー指摘を踏まえて再実装する
console.log(`再修正が必要 (試行 ${attempt + 1}/${maxRetries}):`, review);
implementation = await runResultText(
`以下のレビュー指摘を踏まえて修正してください。\n\nレビュー指摘:\n${review}\n\n元の実装:\n${implementation}`,
["Read", "Edit", "Write", "Bash"]
);
}
throw new Error("最大リトライ回数に達しましたが、レビューを通過できませんでした");
}この形にしておくと、「何回まで自動でリトライするか」「リトライしても通らない場合にどう失敗させるか」といった運用上の判断を、コードとして明確に定義できます。CI/CDパイプラインに組み込む場合は、このような失敗時の挙動の明文化が特に重要になります。
まとめ
このレッスンでは、複数エージェントを協調させる3つのパターン(並列・直列・階層)を整理し、エージェント間のデータ受け渡しを「メインエージェントが仲介する方式」と「構造化データで直接受け渡す方式」の2つの観点で比較しました。そのうえで、調査・実装・レビューという3エージェント構成のパイプラインを、CLIのサブエージェント定義とAgent SDKのquery()連鎖の両方で実際に構築しました。
- 並列・直列・階層は、エージェント間の依存関係に応じて選ぶ。階層パターンは複雑さが増すため必要な場合に限定する
- CLIでは「メインエージェントへの明示的な手順指示」、Agent SDKでは「
query()の結果を次のプロンプトに埋め込む」という形でパイプラインを組む - Agent SDKでは各ステージの
allowedToolsを絞り込むことで、「調査段階では書き込み禁止」のような制約をコードレベルで強制できる - 「ブロッカーがあれば差し戻す」といった条件分岐は、CLIでは指示文として、Agent SDKではコードのif文として表現する
これで応用コースは折り返し地点です。Skills・サブエージェント・Agent SDK・マルチエージェントパターンという拡張の仕組みを一通り学んだので、次のLesson 11からはIDE連携やRemote Control APIなど、Claude Codeをより広い環境で活用する方法に進んでいきます。