マルチエージェント開発でハンドオフを取り入れてみてわかったこと
X-tech推進本部 川端背景
レガシーシステムの移行をAI駆動開発で進めており、複数のAIエージェントを連携させるオーケストレーション構成を採用しています。 AIエージェントの役割は次の3つで、それぞれ別のエージェントセッションとして起動し、オーケストレータが各セッションの出力を読んで指示や確認を行います(以下、オーケストレータから作業を任される側をサブエージェントと呼びます)。
| 役割 | 責務 |
|---|---|
| オーケストレータ | 進行管理、委譲、成果物の裏取り、コミット |
| アーキテクト | 機能の実装計画を作成し、インプリメンターへ渡す粒度(スライス)に分割する |
| インプリメンター | 1スライスだけ実装・検証する |
この構成だと、オーケストレータの1セッションが長時間になり、ロングコンテキストになりがちという問題がありました。コンテキストが増えすぎると、応答品質だけでなくコストにも影響します。
Anthropicの記事「Maximizing the value of your Claude Code sessions」でも、長いセッションはコストが増えることや、作業の切り替え時に/clearすることが説明されています。
なぜオーケストレータだけ膨らむのか
原因は、オーケストレータがインプリメンターの出力を読むたびに、その内容がオーケストレータの履歴に積み上がることでした。
- 各インプリメンターからの完了報告(変更内容、テスト結果、差分検証の結果...)が長い
- 報告内容の裏取りのために、テストログや差分ファイルを丸ごと読む
- これがスライスの数だけ繰り返される
インプリメンター側はスライスごとに/clearして履歴を削除していますが、オーケストレータはスライスを横断して把握するため、コンテキストとして残し続ける必要があります。
作業内容を引き継ぐためのハンドオフ
この問題をエージェントに相談したところ、コンテキストを要約してファイルとして保存し、エージェントに引き継ぐ「ハンドオフ」を提案されました。 Anthropicの「How we built our multi-agent research system」でも、完了した作業を要約して外部に保存し、新しいコンテキストのサブエージェントへ、ハンドオフで引き継ぐ方法が紹介されています。今回の取り組みも、この考え方に近いものです。
この記事では、それを実際に約1か月運用して決めたルールと、わかったことを共有します。
運用ルール
基本の形は次のとおりです。
- 報告内容はファイルとして出力し、会話にはファイルのパスと一行要約だけ返させる
- 長いコマンド出力は引き継ぎ用のファイルに貼らず、ログファイルのパスだけを書く
- 引き継ぎ用のファイルは実装と同じコミットに含め、ログはGit管理外の
var/orchestration/に置く
スライス間は次のループで回します。
- 完了待機
- ハンドオフ確認
- 裏取り
- コミット
- インプリメンターを
/clear - 次スライスの指示
サンプルプロンプト
オーケストレータとインプリメンターには以下のようなプロンプトを渡しています。これは実際に使っている役割定義から、ハンドオフに関わる部分を抜き出して一般化したものです。
オーケストレータ
# オーケストレータ
マルチエージェントで開発を進めるときの全体進行役。
## 責務
1. 計画をアーキテクトへ、実装をインプリメンターへスライス単位で委譲する
2. 上がってきた成果物を自分で裏取りする
3. 裏取りが通ったらコミットし、次のスライスへ進む
## 裏取り
委譲先の報告をそのまま信じない。数値や結論は自分で確かめる。
- 通常は引き継ぎ用のファイル、変更箇所と周辺関数、重要な根拠だけを確認し、ファイル全文は読まない
- テストと静的解析は自分で実行する。成功したコマンドは終了コードと末尾要約だけを読み、失敗時だけログのエラー周辺を読む
- テスト失敗、根拠との矛盾、想定外ファイルの変更がある場合だけ、全文確認へ広げる
- 差し戻すときは「何が、どの正本のどこに反するか」を1件ずつ返す。自分で大きな実装をしない
## スライス間の進行
- インプリメンターの完了を待ち、ハンドオフを確認してから裏取りする
- コミット前にインプリメンターのツールで使用量(Claude Codeなら`/cost`)を読み取り、実測値を渡して引き継ぎ用のファイルの「コスト」へ記入させる
- 引き継ぎ用のファイルは実装と同じコミットに含める。1スライス1コミット
- 次のスライスを指示する前に、インプリメンターのコンテキストを`/clear`で初期化する
- 完了待機 → ハンドオフ確認 → 裏取り → コミット → /clear → 次スライスの指示を連続して行い、userへの報告は全スライス完了時か判断待ちのときだけにする
インプリメンター
# インプリメンター
計画の1スライスを実装する役。
## 範囲
- 指示された1スライスだけ実装する。後続スライスへ先回りしない
- 計画から逸脱が必要になったら、勝手に変えず理由を報告して判断を仰ぐ
- commitとpushをしない。作業ツリーの変更で止める
## ハンドオフ
スライス完了時に`docs/handoff/TEMPLATE.md`を読み、その型に沿って`docs/handoff/<対象>-slice-<番号>-impl.md`を作成する(原則100行以内)。差し戻し後は同じファイルを更新する。
- テンプレートの見出しは省略しない。該当なしでも「なし」とする
- 「コスト」の数値は推測せず、オーケストレータから渡された実測値を記入する
- ログは`var/orchestration/<対象>/slice-<番号>/logs/`に保存し、秘密値を残さない
- 検証結果にはコマンド、終了コード、件数・結果要約、ログのパスを書く。長い出力は引き継ぎ用のファイルへ複製しない
- 完了報告は引き継ぎ用のファイルのパスと一行要約だけにする
## 検証
完了後にテストと静的解析を実行し、実測結果を報告する。失敗はそのまま報告する(成功に見せない)。
わかったこと
1. 引き継ぎ
される側のルールも必要
ハンドオフをしても、オーケストレータが全部読み直すなら意味がありません。読み方もルール化しました。
- 通常は引き継ぎ用のファイル、変更箇所と周辺関数、重要な根拠だけを確認し、ファイル全文は読まない
- 成功したコマンドは終了コードと末尾の要約だけを読み、失敗時だけログのエラー周辺を読む
- テスト失敗、根拠との矛盾、想定外ファイルの変更があるときだけ全文確認へ広げる
2. 節約しても裏取りは削れない
インプリメンターの「成功報告」が実態と違うことは、実際にありました。そのためハンドオフ形式でも、オーケストレータの裏取りはやめていません。引き継ぎ用のファイルを最初から全部読むことはせず、読み方のルールに沿って、読む範囲を段階的に広げています。
3. テンプレートは「なし」を書かせる
次のテンプレートをdocs/handoff/TEMPLATE.mdとして保存し、インプリメンターに参照させます。
# <対象> slice <番号> handoff
## 変更点
## 根拠
## 検証結果
| コマンド | 終了コード | 結果要約 | ログ |
## 判断が必要な事項
## 未解決事項
## 学び
## コスト
その上で以下のようなルールを決めています。
- 見出しは全部必須。該当なしでも「なし」と書かせる(書き漏れと「ない」を区別するため)
- 「判断が必要な事項」を独立させる。オーケストレータが承認・差し戻しを決める箇所がひと目で分かり、本文を読み込まずに済む
- 「学び」は横展開できる知見だけ。感想は書かない
4. コストは自己申告させず、実測を記録する
「コスト」欄には、スライスごとの実測トークンと概算料金を記録しています。これは実装ツールやモデルを替えたときの比較材料とするためです(料金はツールが表示した概算なので参考程度に)。ポイントは以下の2つです。
- モデルは自分の使用量を正確に知らないので、自己申告は禁止。オーケストレータが
/costの値を渡して記入させる - ツール名・モデル名・取得時点と、表示されたトークンの内訳(入力・出力・キャッシュ)を残す
5. 報告内容がファイルとしてGitに残る
トークン節約以上に効いたのが、インプリメンターの報告内容がGitに残ることです。約1か月で、引き継ぎ用のファイルは190件を超えました。
- 監査・振り返り:どの根拠で実装し、何を検証し、どの差分を承認したかがコミット単位で追える
- 横展開:「学び」欄がそのまま次の機能・他アプリでのナレッジになる
6. 効き方はサブエージェントの構成で変わる
Claude Code組み込みのサブエージェントのように最終回答だけが親エージェントに返る仕組みなら、途中経過が入らないぶん、節約の効果は小さめです。
一方、今回のように親が別セッションの出力を読みに行く構成では、読み込む量そのものを減らせるので効果が大きくなります。どちらの構成でも、記録として残るという価値は変わりません。
結果とまとめ
オーケストレータのコンテキスト使用率が、ロングセッションでも20%台に収まるようになりました。
※ 導入前後を同条件で比較したわけではないので、あくまで運用上の観測値です。
マルチエージェント構成でコンテキストが圧迫される原因は、サブエージェントの出力をオーケストレータが抱え込むことでした。今回の取り組みで効果が出たポイントは、書かせるルールと読む側のルールをセットにし、読む順番を決めたことだと考えています。結果として、コンテキストの節約に加えて、作業記録がGitに残るという思わぬ副産物も手に入りました。
同じようにオーケストレーション構成で開発している方の参考になればうれしいです。