テスト駆動でもAIはずれる ── 要件から逸れない自律開発ループの設計
Claude Code で動く自律開発ループを2つ比べ、そこから設計し直しました。結論はひとつです。
ずれは、最後のレビューではなく「要件 → テスト」の段で止める。判定は機械に寄せる。
この結論を、6つの原則に分けました。
- 原則① テスト駆動だけでは要件からずれる。テスト駆動が保証するのは「実装がテストに合っている」ことまで。要件をID付きで分解し、要件とテストの対応を実装の前に検査する。正本(要件の拠り所になる文書)はループに書き換えさせない
- 原則② 品質はレビューではなくコードに書く。AI の品質レビューをやめ、lint・依存ルール・層別の CI に置き換える。層を分けると、落ちた層がそのまま問題の場所になる
- 原則③ 答えが一意なものは機械に任せ、合わない分だけ AI に1回推論させる。結果を対応表に書き戻すので、運用するほど AI の出番が減る(狙い)
- 原則④ 判定は判定専用のモデルに任せ、LLM は生成と推論に使う。確信度が低ければ慎重な側に倒す。重い画像判定は通常のループから外す
- 原則⑤ 読むのは並列、書くのは直列。並列化は1件の中ではなく、件と件の間で効かせる
- 原則⑥ 1件が止まってもループは止めない。停止理由を分類し、仕様の問題なら次のタスクへ、基盤の問題なら全体を冷却する
本記事は、これから入れる設計の最終形です。まだ全部は運用していません。効果は「狙い」として書いています。
想定読者は、Claude Code などで自律開発ループを作っている人、これから作る人です。
1. 2つのループを作って比べた
2つのプロジェクトで、性格の違う自律開発ループを動かしています。どちらも Claude Code のエージェントが、Issue を起点にテスト・実装・検証・マージまで進めます。
本記事では、計画とレビューを厚くした方を「重厚型ループ(プロジェクトA)」、1本道のスクリプトで回す方を「軽量型ループ(プロジェクトB)」と呼びます。どちらのループも、要件の拠り所になる文書を持っています。要件定義書・WBS・バックログなどです。以下、これを「正本」と呼びます。
1.1 動かし方の違い
| 観点 | 重厚型(A) | 軽量型(B) |
|---|---|---|
| 起動 | Issue にラベルを付けると、ラベルの変化で次の段のワークフローが順に起動する。30分ごとの定時スイーパーが、止まった Issue を拾い、次のタスクも払い出す | 人が手動で起動する(マイルストーンか Issue を指定)。定時起動もイベント起動もない |
| タスクの出どころ | WBS の作業パッケージから Issue を自動で起票し、依存順に払い出す | 特定ラベルの Issue を、マイルストーンの中で番号順に処理する。正本は客先の Excel バックログ |
| 並び方 | Issue ごとに並列(同時に最大3件) | 1件ずつ直列。前の件をマージした後の main から、次の件を始める |
| 段の進め方 | 状態をラベルで持ち、別々のワークフローが引き継ぐ | 1本のシェルスクリプトが、1件の全段を上から順に実行する |
| 人のゲート | リスクが high・個人情報の疑い・要レビューの件は、マージ前に人が承認する。それ以外は自動でマージする | なし。CI が緑なら自動で squash マージする |
| 止まった後 | 停止を3種類のラベル(人の判断待ち・再試行・依存待ち)に分ける。再試行は60分冷ましてから、スイーパーが自動で再開する | 停止のラベルを付けて止める。人がラベルを外すと、次の起動で対象に戻る |
1.2 エージェントとワークフローの違い
| 観点 | 重厚型(A) | 軽量型(B) |
|---|---|---|
| エージェント | 1件を流すのに11体。計画4体(要件定義・リスク判定・調査・計画)、実装2体(テスト設計・実装)、並列のレビュー3体(品質・仕様・個人情報)、修正、報告 | 8段の1本道。トリアージ → テスト設計 → 実装 → 修正(最大3回)→ UI レビュー → リファクタ → lint・ビルド → マージ |
| 呼び出し方 | Claude Agent SDK。段の間の受け渡しは、JSON Schema の構造化出力 |
claude -p(CLI)。段ごとに、許可するツール・ターン上限80・予算上限10 USD を付ける |
| 守りの効かせ方 | エージェントの権限で絞る(ツールの制限・パスの規則・フック)。テストはハッシュで照合する | 差分で絞る。許可したパス以外の変更は元に戻す。テストは sha256 で照合する |
| ワークフロー | 8本(計画・実装・レビューとマージ・次タスクへの送り・スイーパー・CI など)。GITHUB_TOKEN で付けたラベルでは後続のワークフローが起動しないため、PAT が必須 |
4本(ループ本体・マージ後の処理・CI・dev 環境へのデプロイ)。GITHUB_TOKEN だけで動かし、CI は明示的に起動する |
重厚型は、イベント駆動で並列に動きます。計画とリスク判定に厚い構成です。軽量型は、手動で起動する直列のスクリプトです。差分ガードと CI の緑を条件に、自動でマージします。
1.3 6つの観点で評価した
◎ 優位 / ○ 普通 / △ 弱い、の3段階で評価しました。
| 観点 | 重厚型 | 軽量型 | 理由 |
|---|---|---|---|
| トークン効率 | △ | ◎ | 重厚型は、1件あたりのエージェント呼び出しが多い。軽量型は1本道で、ターンと予算の上限もある |
| 要件の正本から逸れない | ◎ | △ | 重厚型は、仕様の節番号から本文とハッシュを引いて固定し、レビューの前にも照合し直す。仕様レビューは、差分を読む前に仕様から要件を列挙する。仕様は書き込み禁止。軽量型は、Issue 本文と親のバックログ項目を渡すだけ。要件と照合する専用のレビューがない |
| 速さ | ○ | ○ | 重厚型は件ごとに並列だが、段の引き継ぎでラベルとイベントを待ち、人のゲートでも待つ。軽量型は1件の中に待ちがないが、件同士は直列で、起動も手動 |
| トレーサビリティ | ◎ | ○ | 重厚型は、WBS ID → Issue → リスクとレビューのマーカー → 状態ファイル(試行の分類とエラー指紋)→ ループのログ → 仮決定の記録、と鎖がつながる。軽量型は、Issue コメント・PR 本文・Artifacts で追う。Artifacts は14日で消える |
| 拡張性 | ○ | ◎ | 重厚型は部品が分かれているが、8本のワークフローの連携が複雑。concurrency の不具合を何度も起こした。軽量型は、段を1行で足せる |
| セキュリティ・品質 | ◎ | ○ | 重厚型は、リスク判定を独立させ、個人情報専用のレビューと人のゲートを持つ。軽量型は、パスの差分ガードと CI 必須はしっかりしている。ただし個人情報と認可を見るレビューがなく、人の承認なしでマージする |
正確さと統制は重厚型、コストと単純さは軽量型が上でした。一言でまとめると、重厚型は正確だが重く、軽量型は軽いが要件からずれます。
1.4 それぞれの弱点
重厚型の弱点は、次の4つです。
- 重い:1件あたりのエージェント呼び出しが多く、トークンと時間がかかる
- 複雑:8本のワークフローがラベルで連鎖している。concurrency による run の追い出しや自己ロックで、何度も壊れた。13日間放置された Issue もある
- 二重管理:GitHub 上で動く版と手元で動く版に、同じ判定ロジックが2つある
- 止まりやすい:マーカーを読めないと安全側(高リスク・指摘あり)に倒すので、空振りの停止が起きやすい
軽量型の弱点は、次の5つです。
- 要件からのずれを検出する仕組みが弱い
- 人の承認なしでマージし、そのまま客先も見る dev 環境に出る
- 個人情報・認可・DB の変更を特別扱いしない
- 手動起動で直列なので、件数が増えると回らない
- 記録が Artifacts にしか残らず、14日で消える
1.5 重厚型のレビューは組みすぎだった
重厚型のレビューは、毎回すべてを回すと過剰でした。層ごとに見ると、次のとおりです。
| 層 | 中身 | 評価 |
|---|---|---|
| 機械チェック | CI・テストのハッシュ照合・仕様のハッシュ照合・パスガード | 安く確実なので残す |
| AI レビュー(3並列) | 品質・仕様・個人情報 | 毎回3体は重い |
| 修正ループ | 最大4ラウンド | 指摘のたびに3体が再レビューするので、コストが膨らむ |
| 人のゲート | 人の承認 | high リスクと個人情報のときだけなので妥当 |
品質レビューは、CI(lint・型・テスト)と役割が重なりやすいです。個人情報レビューは、個人情報に触れない差分では空振りします。そこで、種類を減らすより、差分に応じて出し分けることにしました。
- 仕様レビュー(以下「要件適合レビュー」)は常に回す。重厚型の強みの中核だから
- 個人情報レビュー(以下「privacy レビュー」)は、個人情報・認証・RLS・migration に触れる差分のときだけ回す
- 修正後の再レビューは、指摘を出した種類だけにする
- 修正のラウンドは4回から2回に減らし、超えたら止める
- 品質レビューは、最初は条件付きで回す案だった。最終的には廃止した(原則②)
ここから先は、両方の長所を取り込む形で設計し直した内容です。重厚型からは、要件の固定・仕様レビュー・リスクに連動したゲートを取り入れます。軽量型からは、単純さ・コストの上限・パスガードを取り入れます。
2. 原則① テスト駆動だけでは、要件からずれる
レビューを減らす話の途中で、こう考えました。「テスト駆動で進めれば、要件からずれることはないのでは?」
答えは「絶対とは言えない」です。テスト駆動が保証するのは「実装がテストに合っている」ことだけです。「テストが要件に合っている」ことまでは保証しません。
2.1 ずれの入口は5つある
| ずれの入口 | 例 | テスト駆動で防げるか |
|---|---|---|
| (1) 要件の読み違い | 正本の曖昧な箇所を、エージェントが都合よく解釈する | ✕ 読み違えたまま、テストも実装も「正しく」通る |
| (2) テストの抜け | 要件10個のうち、8個しかテストしていない | ✕ 通っても、残り2個は未検証のまま |
| (3) テストの弱さ | 「エラーが出ないこと」だけを確認し、中身を見ていない | ✕ 実装が手抜きでも通る |
| (4) テストにしにくい要件 | 「分かりやすい表示」「運用上こう使う」 | ✕ 自動テストに落ちない |
| (5) テストの改変 | 実装側がテストを書き換えて通す | ○ テストを固定してハッシュで照合すれば防げる(両ループとも実装済み) |
テスト駆動が効くのは、(5)と「実装の取りこぼし」までです。(1)〜(4)は、テストより手前の段で防ぐしかありません。
2.2 要件をID化し、テストとの対応を実装の前に検査する
テストより手前に、次の段を置きました。丸数字は、§8 のフローの段の番号です。
- ① 要件読み込み(1体):正本の該当する節を取り、内容をハッシュで固定する。そのうえで、要件ID付きのリストに分解する。曖昧な箇所があれば、質問を残して止まる
- ② テスト設計(層ごとに並列):要件IDごとにテストを書く。書けるのはテストだけ
- ③ 対応検査:要件IDとテストを突き合わせる。抜けがあれば、抜けの一覧を渡してテスト設計へ戻す
- ④ 実装(1体):テストと正本は書けない
(1)への対策は、①の「曖昧なら止まる」です。要件読み込みの解釈が間違っていると、後ろの全段がその間違いに沿って一貫してしまいます。だから、解釈が割れそうな箇所は先に人に聞きます。質問には、選択肢と推奨を添えます。
(2)への対策は、③の対応検査です。テスト名や注釈に要件IDを入れる規約にしておくと、突き合わせは機械でできます(原則③)。対応表は「要件ID ↔ テスト名」の機械が読める形で残します。これがそのままトレーサビリティにもなります。
(3)のテストの弱さには、専用の検査を入れていません。「その要件をわざと外した実装でも、テストは落ちるか」を試す案もありました。しかし今のループはうまく回っています。トークンに見合わないと判断しました(§9)。
(4)のようにテストに落ちない要件は、実装の後の要件適合レビューで正本と照合します。
実装の後でテストが疑わしくなったときは、ループは自分で直さずに止まります。このような状況では、要件の解釈がずれている可能性が高いからです。自動で直すより、人に確認する方を選びました。
2.3 正本はループに書き換えさせない
最初の案では、フローの最後に「Issue や正本を修正する」段を置いていました。これは危険です。ループが正本を書き換えると、要件の側が実装に合わせて動き、ずれを検出できなくなります。
そこで、正本は書き換えず、修正の提案だけを起票します。置き場所は、確認事項の一覧か Issue のコメントです。採否は人が決めます。
これで、ずれのチェックを最後のレビューから最初の「要件 → テスト」へ前倒ししました。レビューを減らしても品質を落とさない、という狙いです。
3. 原則② 品質はレビューではなく、コードに書く
品質レビューを外したら、コードの質はどう守るのか。気にしていたのは、次のような点です。
- カプセル化されていて、単純なコードになっているか
- マジックナンバーを使っていないか
- クリーンアーキテクチャのように層を分け、どこでうまくいかなかったかを検出できるか
3.1 ほとんどは静的チェックで判定できる
これらの多くは、AI のレビューではなく静的チェックで判定できます。機械で判定すれば、毎回同じ基準で、トークンもかかりません。CI に入れれば、検証の段の CI がそのまま品質レビューの代わりになります。
| 観点 | 機械での判定方法 | 機械で拾えないもの |
|---|---|---|
| マジックナンバー | ESLint の no-magic-numbers(0・1・-1 などは除外) |
文字列の直書き(ステータス名など)。定数か union 型を使う規約で補う |
| 単純さ |
complexity(循環的複雑度)、max-lines-per-function、max-depth、max-params、sonarjs/cognitive-complexity
|
名前の分かりやすさ |
| カプセル化 |
knip で不要な export を検出する。モジュールの入口を index.ts に限り、内部ファイルの直接 import を禁止する |
責務の切り方そのもの |
| 依存の向き |
dependency-cruiser か eslint-plugin-boundaries で、層の間の import の向きを強制する |
層の分け方そのものの妥当性 |
| 型の甘さ | TypeScript の strict、typescript-eslint の no-explicit-any
|
― |
依存の向きは、例えば次のように決めます。内側から外側への import は禁止です。
domain … 何も import しない(業務ルール)
usecase … domain だけを import する(手順の組み立て)
infra … 内側に依存する(DB・権限。例: Supabase)
app … 内側に依存する(画面。例: Next.js)
AI が担うのは、表の右の列(名前・責務の切り方)だけです。これは、仕上げの段のリファクタ担当に任せます。リファクタ担当には lint の警告一覧を渡し、「警告ゼロ+命名の改善」を目標にさせます。
品質の基準はコード(lint・依存ルール)として書き、AI はそれを満たすように書く側に回します。 トークンを減らしつつ、判定のぶれもなくす狙いです。
3.2 層を分けると、どこで失敗したかが分かる
層を分けておくと、落ちた層がそのまま問題の場所になります。CI のステップを「構造 / domain / usecase / infra / e2e」に分け、それぞれに名前を付けます。
| 落ちたステップ | 意味 | 次の動き |
|---|---|---|
| 構造(依存の向き・複雑度) | 設計の崩れ | 修正へ。どのファイルのどの依存かまで出るので、直す範囲が狭くて済む |
| domain | 業務ルールの誤り。要件の読み違いの可能性が高い | 実装より先に、要件の解釈を疑う。要件IDも一緒に出る |
| usecase | 手順・組み立ての誤り | 修正へ |
| infra(DB・RLS) | クエリ・権限の誤り | 修正へ。privacy レビューの対象にもする |
| e2e だけ | 画面と配線の誤り | 修正へ |
こうすると、止まった理由が「どの層の、どの要件ID」という単位で Issue に残ります。後から追うときの手がかりにもなります。テスト設計を層ごとに分けること(原則⑤)とも噛み合います。
4. 原則③ 答えが一意なものは機械に。合わない分だけ AI に1回
エージェントにするか、ワークフロー(スクリプト)にするか。判断の基準は、ひとつにしました。答えが一意に決まる処理は機械に任せ、解釈が要る処理だけをエージェントにします。
4.1 ワークフローにした処理
| 処理 | 担当 | 中身 |
|---|---|---|
| タスク選び | 機械 | WBS の依存順で、着手できる(ready な)タスクを選ぶ。停止した後の次の候補も同じ |
| 正本の固定 | 機械 | 正本の節を取り、ハッシュで固定する。ハッシュが前回と同じなら、要件IDの一覧をキャッシュから再利用する |
| 層の振り分け(要件ID → 層) | 機械 → 合わない分だけ AI | 対応表で決まるものは機械で振り分ける。表に無い要件IDや、複数の層にまたがる要件IDだけ、エージェントが1回推論して層を決める。結果は対応表に追記する |
| 対応検査(要件ID ↔ テスト) | 機械 → 合わない分だけ AI | 突き合わせは機械で行う。合わない項目だけ、エージェントが1回推論する(本当の抜けか、命名のずれで実は対応しているか)。本当の抜けなら、一覧を付けてテスト設計へ戻す。推論しても判断がつかなければ止まる |
| テストの固定と照合 | 機械 | ハッシュで照合する |
| リスク判定 | 機械 | 判定モデルの結果と、差分のパス・行数のルールで決める(原則④) |
| privacy レビューを回すか | 機械 | 判定モデルの結果と、差分のパス(migration・認証・RLS など)で決める(原則④) |
| 回数の上限・停止の閾値 | 機械 | 修正は2回まで。3件続けて止まったら全体を冷却。判定モデルの確率の閾値も。値は設定ファイルに置く |
| 報告 | 機械 | 状態の JSON から、テンプレートで作る。正本との食い違いが記録されたときだけ、LLM が修正提案を書く |
| PR 作成・CI 待ち・マージ・ラベル・ロック | 機械 | すべて機械 |
エージェントが動くのは、次の箇所だけです。
- 要件の分解(正本が変わった節だけ)
- 層の振り分けの補完(1回)
- テスト設計(層ごと)
- 対応検査の補完(1回)
- 実装
- 要件適合レビュー
- privacy レビュー(該当する差分のときだけ)
- 修正
- UI/UX チェック
- リファクタ
- 正本への修正提案(食い違いがあるときだけ)
4.2 機械で判定し、外れた分だけ AI に推論させる
層の振り分けと対応検査は、同じ形にしました。
機械で判定する
└ 合わない分だけ、エージェントが1回推論する
├ 結果を対応表に書き戻す(次からは機械で判定できる)
└ それでも判断がつかなければ止まる
この形にすると、運用を重ねるほどエージェントの出番が減っていきます。 対応表に書き戻した分だけ、次からは機械で決まるからです。
推論を1回に限るのは、1回で決まらないなら要件の側が曖昧だと見るからです。その場合は、ループで粘らずに止めて人に聞きます。
リスク判定・回数の上限・閾値も、ワークフローに持たせました。重厚型では、リスク判定を LLM のエージェントが担っていました。これを、判定モデルとルールで決める処理に置き換えます。値は設定ファイルに置き、実績を見て調整します。
5. 原則④ 判定は判定モデルに。LLM は生成と推論に
原則③のルールだけでは、決めきれない判断が残ります。「この差分は個人情報を扱うか」「この失敗はバグか、基盤のエラーか」といった判断です。答えの形は決まっていますが、パスや行数のルールだけでは決まりません。
ここに、判定専用のモデル(例: Jev。画像も扱えるものとして d1)を使います。 本記事で言う判定専用モデルは、文章を書かずに、型の決まった判定(はい/いいえ・選択肢)とその確率を返すモデルです。分類で済む判断には LLM を使わないことで、トークンを減らす狙いです。
判定専用モデルの仕様・価格・精度は、まだ一次情報で確認していません。製品の紹介文を前提に設計しています。導入前に、一次情報と実データで確かめる前提です。
5.1 判定モデルを入れる4か所
| 箇所 | 判定すること | 判定の後 | 確信度が低いとき |
|---|---|---|---|
| リスク判定 | high / mid / low | high なら、マージ前に人が承認する | high に倒す |
| privacy レビューを回すか | 差分が個人情報・認証・権限を扱うか | 扱うなら privacy レビューを回す | 回す |
| 失敗の振り分け | 実装のバグ / テストの疑義 / 基盤のエラー / 不安定なテスト | バグは修正、テストの疑義は停止、基盤は冷却、不安定なら再実行 | 止めて人に回す |
| 停止理由の分類 | 仕様(spec)/ 基盤(infra)/ クレジット切れ(credit) | 仕様なら次のタスクへ。基盤とクレジット切れなら、ループ全体を冷却 | 基盤とみなし、全体を冷却 |
4か所とも、画像を使わないテキストの分類です。そのため、テキスト分類のモデル1つで揃えられます。
リスク判定は、重厚型で LLM のエージェントが担っていた判断の置き換えです。privacy レビューを回すかは、差分のパスでの判定に、内容での判定を足します。パスに表れない個人情報の扱いも拾えるからです。失敗の振り分けは、修正に進むか、止めるか、冷却して再試行するかを即決するために使います。
5.2 閾値は帯で決め、迷ったら慎重な側に倒す
運用のルールを4つ決めました。
- 閾値を帯で決める:例えば「確率 0.9 以上なら即決」とし、帯に入らない件は、上の表の「確信度が低いとき」の扱いに回す。帯の幅は実績を見て調整する
- 慎重な側に倒す:privacy とリスク判定は、見逃したときの損失が大きい。閾値を厳しめにし、迷ったら確認が厚くなる側に倒す
- 過去の記録で検証する:ループのログと過去の Issue(リスク区分・停止理由・privacy の指摘)を正解データにして、導入前に一致率を測る
- 確率を記録に残す:判定と確率を、状態ファイルと Issue に残す。後から「なぜレビューを省いたか」を追えるので、トレーサビリティが保てる
5.3 判定モデルに任せないもの
判定モデルは、分類しかしません。次の3つは任せません。
- 生成する作業:要件の分解・テスト・実装・修正・リファクタ。判定モデルはコードを書けない
- 要件適合レビュー:「この実装は要件を満たすか」は、差分と正本を読み込んだ推論が要る。LLM に残す
- マージの最終判断:判定モデル単独では通さない。CI と、high リスクのときの人の承認を残す
5.4 画像の判定は、通常のループから外す
画面のスクリーンショットを撮り、チェック項目ごとに合否を出す判定も入れます。今は目視に回している画面の項目を、自動化する狙いです。
ただし、通常のループでは行いません。画面の起動・撮影・画像の判定は重いからです。通常のループとは別に起動します。
| 項目 | 方針 |
|---|---|
| 起動のタイミング | 手動、スプリントの区切りや客先確認の前、夜間にまとめて、のいずれか |
| 対象 | 前回の判定以降に、差分が入った画面だけ |
| 判定 | 画像を扱える判定モデルで、スクリーンショット × チェック項目ごとに合否を出す |
| 結果の扱い | 不合格の項目を、画面ごとに Issue として起票する。通常のループのタスクとして流れる |
通常のループの UI/UX チェックは、これまでどおりコードと静的スキャンで判定します。重い処理を外に出すので、通常のループの所要時間には響きません。見つかった問題は Issue 経由でループに戻るので、別の仕組みを持たずに済みます。
6. 原則⑤ 読むのは並列、書くのは直列。並列化は件の間で
並列化の基本は「読むだけの処理は並列、書く処理は直列」です。同じファイルを複数のエージェントが書くと競合し、その解消に時間を取られるからです。
6.1 採用した並列化
| 並列にする処理 | 目的 | 注意点 |
|---|---|---|
| テスト設計を層ごとに分ける(domain / usecase / infra / 画面) | 速度。要件IDの塊ごとに書ける | 書く処理なので、書くファイルを層ごとに分けて競合を防ぐ |
| 検証の CI・要件適合レビュー・privacy レビューを同時に回す | 速度。全部読むだけなので、互いに待つ理由がない | 修正は1体が、指摘をまとめてから行う |
| 仕上げの UI/UX チェックとリファクタを同時に回す | 速度 | チェック役は指摘を出すだけで、コードを書かない。直すのはリファクタの後 |
| 報告と正本への提案を、CI 待ち・マージ待ちの裏で行う | 待ち時間の活用 | ― |
| 件をまたぐパイプライン | 一番効く見込み。①〜③の時間がほぼ隠れる | 次の件で先に進めるのは①〜③まで |
件をまたぐパイプラインは、ある件の実装中に、次の件の準備を進める形です。
| 時点 | 件A | 件B |
|---|---|---|
| 件A の準備中 | ①〜③ 要件読み込み・テスト設計・対応検査 | ― |
| 件A の実装中 | ④〜⑦ 実装・検証・修正・仕上げ | ①〜③ 要件読み込み・テスト設計・対応検査 |
①〜③は実装のコードに触れないので、件A の実装と衝突しません。
1件の中の並列化で短くなるのは、待ち時間のうち、レビューとチェックの部分です。大きく効くと見ているのは、件をまたぐパイプラインです。
6.2 採用しなかった並列化
- 要件読み込みを2体で行う:2体に独立に読ませ、解釈がずれた箇所を正本の曖昧な箇所として質問に回す案だった。これは速度ではなく、品質のための並列化。ただ、読む2体に突き合わせ役が1体加わる。コストに見合わないと判断した
- テスト設計と実装を同時に行う:テストを先に固定するから、実装はテストに合わせて逃げられない。並列にすると、テスト駆動の意味がなくなる
- 実装を2体に分ける:何もしないと、共有ファイル(型定義・
package.json・migration・共通部品)、層の間の取り決め、結合後の不具合の3点で競合する。避けるには、取り決めの先行固定、書ける場所の分担、結合後の CI が要る。小さい件では、2体がそれぞれ周辺のコードを読む分トークンが倍近くかかり、むしろ遅くなる - 修正ループ:直した結果を見て次を決める、逐次の処理。並列にはできない
並列化の単位は、件の中より件の間です。 1件の中を割ると、取り決めと結合の手間が、並列化で得た時間を食ってしまいます。速度は件をまたぐパイプラインで稼ぐ方が、安全で効果も大きいと考えています。
7. 原則⑥ 止まっても止まらない
ループが止まること自体は、悪いことではありません。曖昧な要件やテストの疑義で止まるのは、設計どおりの動きです。困るのは、1件が人の回答を待つ間、ループ全体が遊んでしまうことです。
そこで、1件が止まったら、他に着手できるタスクを探してすぐに着手する設計にしました。重厚型では、30分ごとのスイーパーが次のタスクを払い出していました。これを、停止の直後に払い出す形に変えます。
7.1 停止したときの流れ
- 停止理由を Issue に残す
- 作業中のラベルを外し、ロックを解除する
- 停止理由を判定モデルで分類する
- 理由に応じて、次のタスクへ移るか、ループ全体を冷却する(一定時間おいてから再開する)
| 停止理由 | 次の動き | 理由 |
|---|---|---|
| 仕様(spec) | 次の ready タスクへ移る | 人の回答を待つ間も、ループを止めないため |
| 基盤(infra)・クレジット切れ(credit) | ループ全体を冷却する | 次の件も同じ原因で落ちる |
| 分類の確信度が低い | 基盤とみなし、全体を冷却する | 慎重な側に倒す |
| 3件続けて停止 | ループ全体を冷却する | 正本の欠落や環境の不具合など、共通の原因を疑うべき |
7.2 次の候補の選び方
次の候補は、WBS の依存順で ready になっていて、止まった件に依存しないタスクです。止まった件と触るファイルが重なるタスクも、候補から外しません。
止まった件は、人が質問に答えてラベルを外すと、候補に戻ります。
8. 最終的なフロー
ここまでの原則を、1枚にまとめます。図の読み方は次のとおりです。
- 灰色の四角:ワークフロー(機械)
- 灰色に橙の破線の四角:機械で判定し、合わない分だけ AI が1回推論する
- 橙の四角:LLM のエージェント
- 緑の六角形:判定モデル
- 赤の四角:停止・冷却
- 見出しに【並列】とある枠:中の処理を同時に回す
「次の件の①〜③」へ伸びる破線が、件をまたぐパイプラインです。別起動の画面判定は、不合格を Issue にして、通常のループの「次の ready タスク」に合流させます。
9. 検討して捨てたもの
設計の途中で入れかけて、外したものです。やらないと決めた理由は、他のループにもそのまま使える知見だと考えています。
| 捨てたもの | 狙い | 捨てた理由 |
|---|---|---|
| 要件をわざと外した実装での試行 | テストの弱さを見つける。「この要件を外した実装でも、テストは落ちるか」を確かめる | 今のループはうまく回っている。毎回試すのはトークンの無駄 |
| 要件読み込みを2体で行う | 2体の解釈のずれから、正本の曖昧な箇所を見つける | 読む2体に、突き合わせ役が1体加わる。コストに見合わない |
| 実装を2体で行う | 1件の中を速くする | 共有ファイル・層の間の取り決め・結合で競合する。小さい件では、トークンが倍近くかかり、むしろ遅くなる |
| テスト修正専用のエージェント | 実装中に分かったテストの誤り(要件の読み違い・前提データの誤り・不安定なテスト)を直す | 実装の前に直すのは、テスト設計担当の仕事そのもの。別のエージェントにすると、正本とテストを読み直すトークンが二重にかかる。対応検査が返す抜けの一覧を渡せば足りる。実装の後にテストが疑わしくなったら、要件の解釈ずれを疑って止まる |
| AI の品質レビュー | コードの品質を AI が見る | CI と役割が重なる。判定がぶれ、コストもかかる。基準は lint と依存ルールとしてコードに書く |
10. まとめ
- テスト駆動が保証するのは「実装がテストに合っている」ことまで。ずれは、最後のレビューではなく「要件 → テスト」の段で止める
- 品質の基準は、AI のレビューではなく、lint と依存ルールとしてコードに書く。層を分ければ、落ちた層が問題の場所になる
- 答えが一意なものは機械に任せる。合わない分だけ AI に1回推論させ、結果を表に書き戻す
- 分類で済む判断は判定モデルに任せ、LLM は生成と推論に使う。迷ったら慎重な側に倒す
- 読むのは並列、書くのは直列。並列化は件の間で効かせる
- 1件が止まっても、ループは止めない。停止理由で、次のタスクへ移るか、全体を冷却するかを分ける
この設計は、まだ全部は運用していません。残っている課題は3つです。
- 判定モデルの閾値の帯を、運用データで調整する
- 判定モデルの仕様と精度を、一次情報と過去の記録で確かめる
- 1件あたりのトークン量と所要時間を測り、狙いどおりに減ったかを確かめる
参考
- ESLint のルール一覧(
no-magic-numbers・complexity・max-lines-per-function・max-depth・max-params)
https://eslint.org/docs/latest/rules/ - eslint-plugin-sonarjs(
cognitive-complexity)
https://www.npmjs.com/package/eslint-plugin-sonarjs - typescript-eslint の
no-explicit-any
https://typescript-eslint.io/rules/no-explicit-any - knip
https://knip.dev/ - dependency-cruiser
https://github.com/sverweij/dependency-cruiser - eslint-plugin-boundaries
https://github.com/javierbrea/eslint-plugin-boundaries