複数 worktree での並行 agent デリバリー:スループットのボトルネックは隔離であってモデルではない
背景:一人で agent の一隊を率いた後、ボトルネックはどこへ移ったか
直近の二か月余り、二つのプロダクトリポジトリで 400 本以上の PR をマージした。ピーク時には五〜十の Claude Code セッションを同時に開き、各セッションが自分のタスクに向かって働く。このスループットはモデルが強くなって拾えたものではない——モデルは「コードを書く」ことをボトルネックでなくしただけで、ボトルネックは即座に別の場所へ移った:並行セッション同士の踏み合い、収束しないレビューのループ、CI の行列。振り返って本当に効いたのは四種類の隔離で、すべて普通のエンジニアリング仕事であり、prompt に関わるものは一つもない。
ワークスペースの隔離:worktree =セッション、ガバナンス先行
タスクごとに一つの git worktree と一本のブランチ、セッションは worktree に束縛し、PR を唯一のマージ経路にする。この層は誰でもやる。飛ばされがちなのはガバナンス先行だ:リポジトリの最初の一週間で agent 規範(AGENTS.md はマニュアルではなく索引として書き、タスクごとに具体的なドキュメントへ誘導する)、ADR、CI ゲート、guard スクリプトを立て、README に skeleton 状態を明示する——コンパイルが通りヘルスチェックに合格しても、配線されていない能力は能力ではなく、利用可能と報告してはならない。agent の成果物の品質は「リポジトリが自分をどう記述しているか」に極めて敏感だ:これらのファイルは人間向けの礼儀ではなく、並行 agent へのランタイム制約である。欠けたまま量を増やせば、増えるのは混乱だ。
ランタイムの隔離:ポートを約束事から割り当てへ
複数の worktree が同時にローカルのフルスタックを起動すると、最初にぶつかるのはポートだ。方式はスロット制:slot N はすべてのアプリケーションポートを一括で 10×N ずらし、slot 0 は歴史的なポート表と正確に一致するので、単一 checkout の人は何も気づかない。スクリプトが「兄弟 worktree が占有しておらず、ポートも実際に空いている」最小スロットを自動で確保してローカルファイルに固定し、必要なら環境変数で上書きする。対になるのは冪等な make up 一本:スロット → 設定のレンダリング → データベース → マイグレーション → 基盤 → サービス、すべて準備できて初めて返る。個人サイトのリポジトリでも同じ問題のミニチュア版(audit の固定ポート 3000 を別の worktree に取られる)にとうに衝突していて、当時は手作業で 3001 に変えて回避した——並べて見れば結論は明白だ:並行度が上がる前に、ポートを「約束事」から「割り当て」へ変えて一度で解決する。
事実の隔離:要求をリポジトリへミラーし、revision で錨を打つ
要求ドキュメントはコラボレーションプラットフォーム(飛書)に住み、agent はリポジトリで働く。コンテキストを人力で運べば必ずドリフトする。やり方は全量アーカイブ:十数本の PRD をすべて docs/product/ へミラーし、各ファイルの冒頭に同期時点の revision 番号を記録する。プラットフォームが要求の唯一の真実源であり続け、リポジトリ側は錨付きの読み取り専用ミラーだ。markdown 書き出しでは飛書の埋め込みスプレッドシート(計 109 枚)が落ちるので、規範性のある表——数値、状態の列挙、受け入れ基準、異常系の境界——を一枚ずつ書き出して照合し埋め戻した。この愚直な作業の副産物として、本文と表が互いに矛盾する箇所をいくつも捕まえた——要求ドキュメント自身が自分の誤りを知らなかったのだから、ミラーリングの過程は無料の要求監査だった。デリバリーの追跡は GitHub の epics + sub-issues を使い、PRD を受け入れ項目の粒度まで分解して、セッションは受け入れ項目に直接向かって着工する。
レビューは有界に:agent 相互レビューには終了条件が要る
別の agent に PR レビューをさせると、デフォルトの挙動は収束しない:修正 → push → レビュー依頼 → また一巡の低優先度の指摘 → また修正、どの一歩も終点ではない。ルールを「完了したレビューラウンド」を単位に変えた:P2/P3 級の指摘しか残らないラウンドが二回続いたら汎用レビューを終了し、「具体的な影響の証拠がある高リスク P2」にだけ限定的な例外を一つ残す。「指摘なし」は確認のもう一巡を起動せず、そのまま終了する。このポリシー自体に八つの decision eval を付けた——レビューポリシーもコードであり、変えるなら同じように回帰が要る。
CI:時間はキャッシュで節約する。カバレッジでは節約しない
並行 PR が増えると、CI のレイテンシは直接スループットの上限になる。時間節約の罠は二つ。一つ目、Go のビルドキャッシュが毎回新規作成される gitignored ディレクトリを指していた——つまり毎回、完全なコールドコンパイルを二度やっていた。二つ目、GitHub Actions のキャッシュのスコープは「書き込んだブランチ+デフォルトブランチ」で、CI を pull_request イベントだけに掛けると、各 PR の初回実行は永遠にコールドだ——main からキャッシュを書く warm ジョブが要る。それは何も gate せず、すべての PR に読めるキャッシュを用意するためだけに存在する。カバレッジは逆方向に収縮させた:フルスタックの smoke は毎 PR 必須からオンデマンド呼び出しへ、CI のデータベーススタックは Postgres 単体だけを起動する。速いチェックは毎日回し、遅いチェックは必要時に回す——この層別は個人サイトの audit(quick/deep の二層)と同じトレードオフだ。
移植できる判断
並行 agent デリバリーのボトルネックの順序:隔離 > レビューの終了条件 > CI レイテンシ > モデルの能力。四つの隔離は互いに独立で、どれも欠かせない:ワークスペース(worktree +ガバナンスファイル)、ランタイム(ポートスロット)、事実(要求ミラー+ revision の錨)、レビュー(有界の収束)。これらを終えると、人間の仕事はコードを書くことから生産計画と受け入れに変わる——そのうちスループットに最も効いた一歩は、PRD を受け入れ項目の粒度まで分解することだ:各セッションが人に聞かずに着工できるかどうかを、それが決める。