本記事は Zenn にも同内容を公開している: https://zenn.dev/flip451/articles/sotohe-track-workflow
AI エージェントは実装を速く書く。だが「どの順で書き、誰がレビューし、いつコミットしてよいか」を決めるのは依然として人間の暗黙知に頼りがちだ。SoTOHE はこの手順そのものを機械可読なワークフローに落とし込む。設計判断を一枚書けば、あとは仕様化 ⇒ 実装 ⇒ レビュー ⇒ PR まで一本のコマンドで自走する1。プロンプトを打ち続けるのではなく、エージェントが自走する輪そのものを設計する営みは、最近ループエンジニアリングと呼ばれ始めている2。本稿で解剖するのは、その一つの具体的な実装である。
これはシリーズ第 2 回である。第 1 回で紹介した SoT Chain(「ADR ← 仕様書 ← 型契約 ← 実装」を一方向の参照で結ぶチェーン)が、実際の開発手順としてどう回るのかを見ていく。シリーズ全体の地図では、本記事は 📍 の位置、チェーンを積み上げる工程そのものを扱う。
track という開発単位
まず語彙を揃える。track は SoTOHE の開発単位で、1 機能追加や 1 バグ修正に相当する。track ごとに専用ブランチ track/<track-id> と専用ディレクトリが作られる。仕様書、型契約、実装計画、レビュー結果といった各工程の成果物は、そのディレクトリに独立したファイルとして蓄積される。どんな仕様に合意し、どんな型を約束し、レビューで何が指摘されたか。作業が終わったあとも、その track の判断の履歴がまるごと残る。ブランチは設定済みの base branch から自動作成され、以降の commit / push / PR 作成はすべてそのブランチ上で行われる。
もう一つ、本稿を通して使う語を先に置いておく。信号機 (signal) は、下流の成果物が上流を根拠としてきちんと参照できているかを機械が検査した結果だ。🔵 = つながっている、🟡 = つながっているが解消すべき課題が残る、🔴 = 切れている、の 3 色で表す。ただし、その色で作業を止めるかどうかを決めるのはゲートである。🔴 はどのゲートでも止まる。🟡 で止めるかはゲートごとの設定で決まるが、いずれの場合も track の完了までには解消しなければならない。
track 作業の正規フローはたった 2 コマンドだ。
/adr:add <slug> # 設計判断 (ADR) を対話で書く
/track:adr2pr # その ADR を起点に PR まで自走 (merge はしない)
/adr:add は track の外側で行う「事前ステージ」で、恒久的な設計判断を knowledge/adr/ に一枚書く。これが SoT Chain の最上流になる。以降は /track:adr2pr がすべてを引き継ぐ。起点にする ADR は引数で明示してもよいが、省けば直前の会話の文脈から解決される。どちらの場合も、機能名と起点 ADR の組を一度だけ確認してから、自走が始まる。
/track:adr2pr の中で起きること
一本のコマンドの内側で起きることを、順に追う。
- 舞台を作る:track を初期化して専用ブランチを切り、起点となる ADR をレビューにかけ、指摘がゼロになったら最初のコミットとして固定する。設計判断そのものが、この track で最初のレビュー対象になる。
- 計画を積む:仕様書 ⇒ 型契約 ⇒ 実装計画の順に、担当のエージェントがフェーズ 1〜3 の成果物を書き上げていく(この積み上げの中身は次節で見る)。書き上がった計画一式も、実装に入る前にレビューとコミットを通る。
- 実装のループに入る:実装計画に並んだタスク(このループ 1 周で消化する作業単位)を一つ取り、実装し、レビューを受け、指摘ゼロでコミットする。これをタスクが尽きるまで繰り返す。
- PR を開く:ブランチを push して PR を作り、GitHub 上での非同期レビューの往復まで済ませたら、マージの直前で止まる。マージの決断だけは、人間に残されている。
上流の記述が下流でどう展開されるか、例を一つだけ示す。ADR に「在庫が足りない注文は確定させない」という決定が一行あれば、仕様書の担当エージェントはそれを「在庫が足りなければ在庫不足エラーを返す」のような検証可能な受け入れ基準へ展開し、根拠として元の決定への引用を張る。以降のフェーズも同じ要領で、直前の成果物を入力に一段ずつ具体化していく。
指摘ゼロになるまで繰り返す収束サイクルは、単一タスクの内側で閉じている。/track:adr2pr はそれらを束ねて PR まで運ぶ。一方、track を跨いで経験を蓄え、次に置くルールへ反映する仕組みはまだ無い。どんなルールを新しく置くかの提案は、いまのところ人間の仕事だ3。
フェーズ 0〜3 の計画が積み上げる SoT Chain
実装に入る前に、SoTOHE は必ず計画フェーズを通す。難易度に関わらず計画フェーズを必須にするのは、設計を飛ばすと下流のレビューが何度も往復する高コストな失敗になるからだ4。
各フェーズは、その内容を唯一の正とするファイル(SSoT: Single Source of Truth)を生む。ファイルの存在そのものがフェーズ到達の状態を表す5。
| フェーズ | 生成物 (SSoT) | 役割 |
|---|---|---|
| 0 | metadata.json |
識別情報のみ |
| 1 | spec.json |
振る舞いの契約(目的 / スコープ / 制約 / 受け入れ基準) |
| 2 |
<layer>-types.json + tddd-features.json
|
型レベルの契約(TDDD)と、その抽出条件の宣言 |
| 3 |
impl-plan.json + task-coverage.json + task-contract.json + batch-plan.json
|
実装計画 + 仕様カバレッジ + 型契約とタスクの対応 + バッチ編成 |
ここでフェーズ 1 ⇒ フェーズ 2 ⇒ フェーズ 3 という進行は、そのまま SoT Chain の「仕様書 ← 型契約 ← 実装(計画)」に対応する6。下流の成果物は上流を参照して初めて成立し、参照が切れれば CI が止まる。
役割ごとの分業と担当の割り当て
SoTOHE のもう一つの軸が、役割ごとの分業だ。spec-designer / type-designer / implementer / reviewer / researcher といった役割には、それぞれ担当を割り当てる。決めるのはプロバイダ(Claude / Codex / Gemini)だけではない。役割ごとに、使うモデルと推論の強度まで専用の設定ファイルで指定する。担当はコードやドキュメントに埋め込まれず、その一箇所を書き換えるだけで切り替わる。
主な役割と担当する仕事は次の通り。
| 役割 | 担当する仕事 |
|---|---|
orchestrator |
ユーザーとの対話と、track 全体の進行管理 |
spec-designer |
フェーズ 1 仕様契約の作成 |
type-designer |
フェーズ 2 型契約の作成 |
impl-planner |
フェーズ 3 実装計画の作成 |
implementer |
難しい Rust 実装とリファクタ |
reviewer |
コードレビューと正しさ検査 |
researcher |
crate 調査、横断分析、外部調査 |
どの役割にどの担当を当てるかは、設定ファイルの書き換えだけで決まる。最適な対応はモデルの進化や得手不得手で変わりうるので、本稿は特定の割り当てを既定として紹介しない。割り当てをどう変えても、利用者が触れる /track:* インターフェースは安定に保たれる。
独立したサブプロセスとしてのレビュアー
表の一番上にあるオーケストレーター(orchestrator)は、ユーザーと直接やりとりし、/track:adr2pr の流れ全体を統括する役割だ。各フェーズの成果物を書くのは、その下に並ぶ担当の役割である。
reviewer は、オーケストレーターからも実装者からも独立して起動される読み取り専用のサブプロセスであって、オーケストレーターや実装者自身の自己レビューではない。実装時の文脈や思い込みを持たないプロセスが、成果物だけを見て判定する。書いた側が自分のコードを自分で「問題なし」と判定してコミットに進むことは、明示的に禁止されている。
実装フェーズでは、実装バッチごとにレビューが走り、レビュー指摘がゼロになるまでループする。レビュアーが指摘を返せば修正し、再度レビューにかける。この往復が収束して初めてコミットに進む7。
コストは正直に書いておく。外部 LLM を毎バッチ呼ぶため、レビューには相応の実行コストがかかる。しかもこのコストは、差分サイズに対して超線形に増えるというのが筆者の経験則だ8。厳密に実測したモデルではないが、この見立てが正しい限り、同じ量の変更でも小さく分けたほうが総コストは下がる。だから SoTOHE は、レビュー 1 回が読む差分量に上限を置いている。上限の値は設定ファイルで宣言され、実装に着手する前の関門で検査される。
レビュー入場ゲート (task-contract check)
レビュアーが呼ばれる前に、もう一つ関門がある。鍵になるのは フェーズ 3 で作られる task-contract.json だ。「どのタスクが、型契約のどの型を履行するか」を書いた対応表である。レビューの入口では、この表をキーにした合否二択の機械検査が走る。このゲートの要求は一つだ。進行中および完了済みのタスクに帰属する型は、「実装 → 型契約」信号がすべて 🔵 でなければならない。宣言したはずの型が無い、シグネチャの形がずれている。そういう構造的な契約違反を抱えたコードは、そもそもレビューに入れない(未着手タスクに帰属する型は 🟡 を許容し、🔴 は常にブロッカー)。
狙いは前倒し(shift-left)だ。構造レベルの契約違反は、発覚が遅いほど手戻りが高くつく。このゲートはその検出をタスク単位に刻み、レビューの前に済ませる。レビュアーが「型契約書と形すら合わないコード」に時間を消費するのを、入場前に防ぐ。
正直な注記も添えておく。この判定は構造(シグネチャと形)しか見ない。シグネチャは満たすが中身が設計意図と食い違う実装は通ってしまうし、極端に言えば空実装(todo!())でも通る。中身の意味論的な正しさは、この後に入場する reviewer の責務として意図的に残されている。構造は機械の信号、意味論は LLM レビュー、という責任分界である。
git を直接触らせないガード付きコミット
エージェントに git add / git commit を直接叩かせない、というのも一貫した方針だ。これを守っているのは、リポジトリに同梱された git hooks である。commit、ブランチ作成 / 削除、merge、rebase、reset といったローカルの ref 更新すべてを、git のプロセスレベルで捕捉する。そして、正規のガード付き経路が渡す通行証(token)を持たない git 書き込みを、トランザクションごと abort する。
コミットの唯一の経路は、ゲートを通るラッパーコマンドである。ステージングもブランチ操作も同様で、素の git 操作に相当するものはすべてラッパーを経由する。
git hooks を門番にしたことには構造的な意味がある。ツール呼び出しを検査する方式は、検査者であるオーケストレーターの視界の外には届かない。たとえば書き込み可サンドボックスで動く外部サブプロセスがそれだ。だが git hooks は「誰がどこから git を呼んだか」を問わず、git のプロセスそのものに掛かる。オーケストレーターが叩いてもサブプロセスが叩いても素の shell script が叩いても、ローカルの ref 更新は同じ門番に止められ、--no-verify でも迂回できない9。
逆流するエスカレーション
信号機に 🔴 が出たとき、track ワークフローはときとして逆流を余儀なくされる。すなわち、上流の成果物に立ち戻って修正することで、赤信号の解決を図る。例えば型契約書の型宣言が、仕様書に存在しない受け入れ基準 AC-02 を参照してしまった場合、ゲートは次のような出力で止まる。
--- signal check --gate merge ---
[error] catalogue-spec integrity violation on layer 'domain': DanglingAnchor { catalogue_entry: "ConfirmOrder", ref_index: 0, spec_file: "track/items/<track-id>/spec.json", anchor: "AC-02" }
--- signal check --gate merge FAILED ---
どの層の、どの型宣言の、何番目の参照が、どのファイルのどの識別子を指して空振りしたか、までが 1 行で特定される。修正は、原因のある場所へ差し戻される。成果物自身の書き方の問題なら、その書き手が書き直せば済む。原因がさらに上流の決定にあるなら、上流の書き手を呼び直すことになる。この上流へ遡る差し戻しがエスカレーションで、track の流れは前進だけではない。
逆流は計画フェーズ内にとどまらない。実装フェーズ以降で構造的な不整合が表面化することがある。先述のレビュー入場ゲートが Blocked を返す、計画成果物のレビューで指摘が出る、PR 上の外部レビュアーが暗黙の設計原則違反を指摘する、といった場面だ。厄介なのは、どのフェーズまで戻って直すべきかを、症状から機械的には判定できないことだ。「実装が ADR のレイヤ方針と食い違う」という同じ指摘でも、原因は ADR の表現が曖昧で複数解釈を許したのかもしれないし、実装計画のタスク記述が別方針を前提にしていたのかもしれないし、単に実装が契約を破っただけかもしれない。
この診断を場当たりにしないのが /track:diagnose である。SoT Chain を ADR、仕様書、型契約書、実装計画、ソースの順に上から読み、根本原因が発生した最上流のフェーズを特定する。そのうえで、5 分類(ADR 改訂 / 仕様書再作成 / 型契約書再設計 / 実装計画修正 / 実装側の契約違反修正)のどれに差し戻すかを構造化して返す。判定はキーワード規則ではなく LLM の意味判断で行う。診断スキル自身は何も書き換えない。返ってきた差し戻し先の書き手を起動するのは、あくまでオーケストレーターの仕事だ。上流の決定に穴があれば上流を、実装が約束を破っていれば実装を修正する。この診断は、/track:adr2pr のワークフローの中で必要なタイミングで自動的に呼び出されるように設計されている。
ただし、起点の ADR だけは扱いが違う。最初のコミットで内容が固定されるので、以降は直接書き換えられない。設計そのものを変える必要が出たら、変更分を追加の ADR として起草する。下流の成果物はその草案を引用して先へ進められるが、草案は 🟡 のまま残る。採用するか捨てるかを決めるのは、マージ直前の人間である。自走の途中で設計が黙って変わることはない。
パイプラインの全体像
ここまでが、設計判断を一枚書いてから PR が開くまでのパイプラインである。どの段階にも通過条件があり、満たさなければ次へ進めない。その判定を下すのは機械の検査と、書いた本人ではないレビュアーである。
この章で見たものを一枚にまとめておく。破線は前進しない経路で、上流への差し戻しと、追加 ADR の起草を表す。
このパイプラインで実装を縛っているのが、フェーズ 2 で宣言される型契約だ。次回は、その型契約を「型契約書」という SSoT に落とし込む TDDD(型定義駆動開発)を扱う。
シリーズ一覧
- AI エージェントに「仕様どおり」を保証させる — SoT Chain という設計
- ADR から PR まで自走する track ワークフローとマルチエージェント分業(本記事)
- 型契約書を SSoT にする — TDDD(公開予定)
- LLM の判定を CI ゲートにする — hash 凍結 verdict と引用義務(公開予定)
- 「書くべきテストが書かれていない」を検出する — テスト義務ゲート(公開予定)
- SoTOHE を使い始める — テンプレート export と新規プロジェクト実走記録(公開予定)
- SoTOHE を支える設計原則(公開予定)
リポジトリ: https://github.com/Flip451/SoTOHE-core
-
本シリーズでは矢印を書き分ける。⇒ は工程の順序(時間の前後)、→ は参照の向き(参照する側 → 参照される側、つまり下流から上流。実装 → 型契約 など)を表す。← は → の逆から書いた表記で、チェーン全体の列挙(ADR ← 仕様書 ← 型契約 ← 実装)に使う。 ↩
-
プロンプトエンジニアリング、コンテキストエンジニアリングに続く語として 2026 年半ばから広まった。目的を一度定義すれば、仕組みがエージェントへの指示、結果の検証、次のタスクの決定を自動で回し続ける、というループ自体の設計を指す。 ↩
-
ただし端点は見えている。このパイプラインが人間の判断を要求するのは ADR の起草とマージの二箇所だけなので、ループをもう一周外に重ねるなら、そこが継ぎ目になるはずだ。 ↩
-
白状すると、筆者は過去に設計を省いて実装に飛びつき、15 回以上のレビュー往復を食らった。このルールはその反省を機構に変換したものだ。 ↩
-
だから
status: approvedのような状態フィールドは存在しない。CI ゲートも「該当ファイルがあれば検証、なければ skip」という分岐で書かれていて、まだ計画フェーズに達していない track では実装計画の検査自体が発生しない。 ↩ -
型契約は、実装が持つべき型やシグネチャをコードに先立って宣言する成果物である。中身は第 3 回で扱う。 ↩
-
コミット関門はレビュー指摘ゼロだけではない。参照が意味的に整合しているかを検証するゲート(LLM による意味論検証。第 4 回で扱う)と、書くべきテストの存在と内容を確かめるテスト義務ゲート(第 5 回で扱う)も、同じ関門に重なる。 ↩
-
差分の理解に O(N)、指摘の検討にも O(N) かかり、掛け合わせて二乗になる、という内訳の見立て。 ↩
-
残余として、push の
pre-pushフックのみ--no-verifyで迂回できる。この割り切りは正直に記しておく。 ↩