Antigravity でシングルエージェントのワークフローを動かし始めると、ある段階で「これ以上は無理だ」と感じる瞬間があります。タスクが複雑になり、コンテキストが膨らみ、エージェントが出力する内容の一貫性が下がってくる。そのとき、マルチエージェント構成に移行するかどうかを判断しなければなりません。
ただし、何でもマルチエージェントにすればよいわけではありません。エージェントを増やすと調整コストが増え、デバッグが難しくなり、壊れ方が複雑になります。マルチエージェント構成に移行すべきタイミングと、移行した後に壊れにくいシステムを作るための設計パターンを順番にご紹介します。
シングルエージェントの限界を認識する
マルチエージェントに移行する前に、本当にシングルエージェントでは限界かを確認してください。多くの場合、適切なプロンプト設計と段階的な処理で解決できます。
移行を検討すべきサインは3つあります。
コンテキストウィンドウの圧迫: 処理する情報量が多く、エージェントの出力品質が中盤から下がり始める。長い処理を途中でリセットしても同じ問題が起きる場合は、そもそも1つのエージェントが処理すべき量を超えています。
責任の混在: 「データ取得→分析→フォーマット→通知」のような異なる性質のタスクを1つのエージェントが担うと、プロンプトが複雑になりすぎて制御しにくくなります。責任を分離すると、それぞれのエージェントの動作が予測しやすくなります。
並列化の必要性: 独立したサブタスクを順番に処理するより、並列実行したほうが明らかに速い。かつ、待ち時間がユーザー体験に影響している場合。
逆に言えば、これらに当てはまらないなら、まずシングルエージェントを磨くべきです。
オーケストレーター・ワーカーパターン
最も基本的なマルチエージェント構成です。1つの「オーケストレーター」が全体の計画を立て、「ワーカー」エージェントに具体的なタスクを委任します。
// オーケストレーターの擬似コード(Antigravity ワークフロー)
const orchestrator = {
name: "research_orchestrator",
prompt: `
あなたはリサーチプロジェクトのオーケストレーターです。
ユーザーのリクエストを受け取り、以下のワーカーに適切なタスクを割り当ててください。
利用可能なワーカー:
- web_researcher: ウェブ検索と情報収集
- data_analyzer: 収集データの分析と構造化
- report_writer: 最終レポートの作成
タスクの割り当ては JSON で返してください:
{
"tasks": [
{ "worker": "web_researcher", "task": "...", "priority": 1 },
{ "worker": "data_analyzer", "task": "...", "priority": 2 }
]
}
`
};このパターンの重要なポイントは、オーケストレーターが具体的な処理を一切行わないことです。オーケストレーターは「何を誰に頼むか」だけを決める。実際の処理はすべてワーカーが担います。
オーケストレーターに実処理を混ぜると、責任が曖昧になり、デバッグが難しくなります。
状態管理: マルチエージェントの最大の落とし穴
マルチエージェントシステムで一番問題になるのは状態管理です。エージェントAが生成した中間結果をエージェントBが使い、その結果をエージェントCが参照する — このチェーンのどこかで状態が壊れると、最終出力が静かに間違います。
Antigravity でワークフローを設計する際に、私が使っているパターンを紹介します。
イミュータブルな中間状態
各エージェントの出力は上書きせず、ステップごとに保存します。
// NG: 状態を上書きする
workflow.state.data = await researcher.run(query);
workflow.state.data = await analyzer.run(workflow.state.data); // 元データが消える
// OK: ステップごとに保存
workflow.state.research_results = await researcher.run(query);
workflow.state.analysis_results = await analyzer.run(workflow.state.research_results);この設計だと、後から「どのエージェントが何を入力として受け取り、何を出力したか」が追跡できます。バグが発生したときの診断が格段に楽になります。
バリデーション層の挿入
エージェント間のデータ受け渡しには、必ずバリデーションを入れます。
const researchOutput = await researcher.run(query);
// バリデーション
if (\!researchOutput.sources || researchOutput.sources.length === 0) {
throw new WorkflowError(
"researcher_output_invalid",
"sources が空です。クエリを調整して再試行してください",
{ query, output: researchOutput }
);
}
const analysisInput = {
sources: researchOutput.sources,
context: researchOutput.summary,
};バリデーションなしでデータを流すと、エラーが何ステップも後になって発生し、根本原因の特定が難しくなります。
エラー回復設計
マルチエージェントシステムでは、「どのエージェントがどんな理由で失敗したか」によって回復戦略が変わります。一律に「リトライ」するのではなく、失敗の性質に応じた戦略を持つ点が肝心です。
async function runWithRecovery(agent, input, options = {}) {
const {
maxRetries = 3,
retryDelay = 1000,
fallbackAgent = null,
onFailure = null,
} = options;
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await agent.run(input);
} catch (error) {
lastError = error;
// リトライ不要なエラー(入力が間違っている場合など)
if (error.type === "invalid_input") {
break;
}
// レート制限は待機してリトライ
if (error.type === "rate_limit") {
await sleep(retryDelay * attempt * 2);
continue;
}
// それ以外は通常のリトライ
if (attempt < maxRetries) {
await sleep(retryDelay * attempt);
}
}
}
// フォールバックエージェントが設定されていれば試みる
if (fallbackAgent) {
try {
return await fallbackAgent.run(input);
} catch (fallbackError) {
// フォールバックも失敗
}
}
// カスタム失敗ハンドラー
if (onFailure) {
return onFailure(lastError, input);
}
throw lastError;
}フォールバックエージェントの設計は特に有効です。例えば、高精度だが遅いエージェントと、精度は低いが速いエージェントのペアを用意しておき、タイムアウト時に後者に切り替えるパターンは実際のプロダクションで効果的でした。
並列実行と同期
独立したサブタスクを並列実行する場合、結果の統合方法を事前に決めておく必要があります。
// 並列実行の基本パターン
async function runParallelResearch(topics) {
const tasks = topics.map(topic => ({
id: topic.id,
promise: runWithRecovery(
researchAgent,
{ topic: topic.query },
{ maxRetries: 2, fallbackAgent: lightResearchAgent }
)
}));
// allSettled で部分的な失敗を許容する
const results = await Promise.allSettled(
tasks.map(t => t.promise)
);
const successful = [];
const failed = [];
results.forEach((result, index) => {
if (result.status === "fulfilled") {
successful.push({ id: tasks[index].id, data: result.value });
} else {
failed.push({ id: tasks[index].id, error: result.reason });
}
});
// 最低限の成功数を確認
if (successful.length < Math.ceil(topics.length * 0.7)) {
throw new WorkflowError(
"insufficient_results",
`並列処理で十分な結果が得られませんでした(成功: ${successful.length}/${topics.length})`
);
}
return { successful, failed };
}Promise.all ではなく Promise.allSettled を使う理由は、1つの失敗で全体を止めないためです。実際の並列リサーチワークフローでは、10トピック中1〜2トピックが失敗しても残りを使って続行できる方が、全件成功を要求するより実用的です。
デバッグ: マルチエージェント特有の問題
マルチエージェントシステムのバグには特有のパターンがあります。
「静かな劣化」問題: 出力が完全に壊れているわけではなく、じわじわと品質が下がる。特定のエージェントがコンテキストを誤解していても、後段のエージェントがある程度補正するため気づきにくい。
診断方法: 各エージェントの入出力を個別にログに記録し、期待する出力形式との差異を計測します。品質メトリクスを1〜2個設定して追跡すると、どのエージェントで劣化が始まるかが特定できます。
「非決定的な失敗」問題: 同じ入力でも5回中1回失敗します。並列実行やタイムアウト絡みの問題によく見られます。
診断方法: 同じ入力で10〜20回実行し、失敗率とタイミングを記録します。特定の条件(負荷が高いとき、特定の入力パターンなど)で失敗率が上がるなら、その条件を絞り込みます。
「コンテキスト汚染」問題: 前のワークフロー実行の状態が次の実行に影響します。特にステートフルなエージェントを使っているときに起きます。
診断方法: ワークフロー開始時に状態を完全にリセットしているか確認します。テストでは、意図的に「汚染された状態」から始めて、期待通りにリセットされるかを検証します。
設計のまとめ
Antigravity でマルチエージェントシステムを作るときに、私が戻ってくる原則が3つあります。
-
責任の境界を明確に: オーケストレーターは計画のみ、ワーカーは実行のみ。混ぜありません。
-
状態は追跡可能に: 中間状態を上書きしありません。バリデーションをエージェント間の境界に入れる。
-
失敗を前提に設計する: リトライ・フォールバック・部分的な成功の許容を最初から組み込む。後から追加するのは難しい。
複雑なワークフローを壊れにくく保つことが、マルチエージェント設計の核心です。エージェントの数を増やすより、それぞれのエージェントの責任を明確にする方が、長期的に管理しやすいシステムになります。
さらに踏み込んだ内容 — 動的なエージェント生成、ワークフローの自己修復、プロダクション監視の設計 — は Antigravity Lab のメンバーシップ記事で扱っています。興味があればぜひご覧ください。