🗂

設計も実装もレビューもAIがやる個人開発で、精度をどう出しているか

に公開

私は個人で AI coding agent の runtime を作っています。

この製品は、設計も、実装も、実装のレビューも、AI が行っています。

私がやっているのは、AI に渡す最初の指示の精度を上げることと、最終的な成果物が目的に達しているかを確認することです。その間の設計・実装・レビューは、それぞれ別の AI が担います。この記事では、作業指示書を書く AI を設計 AI、コードを書く AI を実装 AI、実装を審査する AI をレビュー AI と呼びます。

こう書くと「それで品質は出るのか」と思われると思います。

出ます。ただし、AI の報告を信じない仕組みがあってはじめて出ます。

この記事は、その仕組みの話です。


AI の「できました」は、普通に間違っている

まず前提から書きます。

実装 AI に実装させると、完了報告が返ってきます。「テストを 26 件追加し、全件通りました」といったものです。

この報告は、普通に間違っています。

実際に私のリポジトリで起きたものを挙げます。

  • 「テスト 26 件追加」と報告されたが、実際に数えると 18 件だった
  • 設計判断の記録に必須のキーが欠けていたが、報告では「記録済み」になっていた
  • 指示書が「必ず明記せよ」と要求した記載が、黙って落ちていた
  • 実装 AI が「原因はこれ」と確信して製品コードに書いたコメントが、まるごと間違いだった

嘘をついているわけではありません。AI は本気でそう思って報告しています。

だから、報告を読んで納得するかどうかで判断すると、必ず通ってしまいます。

報告は、検証の対象であって、検証の結果ではない。

これを運用の起点にしました。


仕組みは 4 層ある

1. 発注: 指示書に「やらないこと」を書かせる

実装は作業指示書で発注します。指示書を書くのは設計 AI です。私が「この機能を足したい」「この不具合を調べたい」といった最初の指示を出し、設計 AI がそれをリポジトリの現状と突き合わせて指示書に起こします。

指示書のテンプレートには、やることだけでなく、次を必ず書く欄があります。

  • 既に実装済みで、作り直してはならないもの(パス名つき)
  • スコープ外(触れないこと)
  • 受入条件(番号つき。1 件ずつテストに対応づけられる粒度で)
  • 修正前のコードで、何が落ちるべきか

最後の項目については後で詳しく書きます。

実装 AI は、指示書に書いていないことを勝手にやり、書いてあることを黙って落とします。両方を後から機械的に照合できるように、指示書の側に境界を書かせておきます。

2. 隔離: 実装 AI と、レビュー AI を分ける

実装した AI に、自分の実装をレビューさせません。

実装は一つのモデルが隔離された作業ツリーで行い、レビューは別のモデルが、実装の作業ツリーの中で行います。

同じモデルが自分の仕事を見直しても、自分が見落としたものは同じように見落とします。

3. 機械確認: スクリプトが「入力」を出す

レビュー AI は、まずスクリプトを走らせるところから始めます。これは手順書で決まっています。

python scripts/review_evidence.py <実装commit> --work-order docs/work-orders/<name>.md

このスクリプトは、機械で確認できる範囲だけを検査します。

  • 設計判断の記録に必須キーが揃っているか、採番が正しいか
  • 契約文書に「実装ファイル名」「commit SHA」などの来歴が混ざっていないか
  • 指示書が「触るな」と宣言したパスに変更が交差していないか
  • 変更されたファイルの分類

そして残りの手順を、実行すべきコマンドを埋めた形で出力します。

ここで一つ、手順書に太字で書いてあることがあります。

スクリプトの出力はレビューの入力であって結論ではない。この出力だけを引用した報告は、何も検証していないのと同じである。

自動化を入れると、その出力を貼って「確認しました」と言いたくなります。人間も AI もそうです。だから、それは検証ではないと明文化してあります。

4. 突合: 手順書どおりに、全部やり直す

レビュー AI は、7 段階の手順を順番に実行します。

  1. diff を指示書と 1 項目ずつ照合する
  2. テストと lint を、報告の数字を信じずに自分で実行する
  3. 修正前のコードで、新しいテストが落ちることを確認する
  4. 設計判断の記録を検証する(過去の判断を覆すなら、覆すと書いてあるか)
  5. 契約文書の「変更なし」という主張を、ファイルを開いて確認する
  6. 受入条件を 1 件ずつ、どのテストが担っているか対応づける
  7. 可能なら、元のバグ報告と同じ操作を実際のインターフェースで再現する

そして手順書は、レビュー報告に実行しなかった手順を「未検証」と書くことを義務づけています。

「検証済み」と「未検証」を混ぜない。実行していない手順を「問題なし」と書かない。

これは製品側の設計原則(失敗したら成功を主張しない)を、そのままレビュー報告にも適用したものです。


いちばん効いた手順: 修正前で落ちるか

手順 3 について、少し詳しく書きます。

修正と一緒に追加されたテストは、修正前のコードで実行すると落ちなければなりません。 落ちないなら、そのテストは修正の効果を何も証明していません。

これは分かりきった話に見えます。しかし、ここに一つ罠があります。

新しいテストが、修正で追加された新しい関数やクラスを import しているとします。修正前のコードでそのテストを実行すると、当然落ちます。ImportError で。

実装 AI は「修正前で落ちることを確認しました」と報告してきます。実際に落ちてもいます。

でも、それはテストが集められた時点で失敗しているだけで、テストの中身は 1 行も実行されていません。

このパターンを、レビュー AI は初期に見逃していました。今は手順書にこう書いてあります。

それは挙動の証明にならない。代わりに挙動レベルの再現を行う。

つまり、修正前のコードで問題の操作を小さなスクリプトで直接実行し、バグが実在したことを確認してから、修正後に同じ操作が正しく振る舞うことを確認する。

これを手順書に書いてからは、実装 AI も最初から遵守するようになりました。個人の注意力ではなく、手順の問題でした。


「既存の問題です」を実測で覆した話

ここまでの仕組みで、たいていの不備は捕まります。

一つだけ、仕組みをすり抜けかけた事例を書きます。

GUI の改修を担当した実装 AI から、こういう報告が来ました。

全件のテストが断続的に segfault するが、これは既存の脆さで、ベースラインでも再現する。本実装の問題ではない。

説明はもっともらしく、原因の考察までついていました。

この報告を受けた設計 AI が、main と実装側の作業ツリーで全件テストを交互に実行しました。

main   : 4 回中 4 回、正常終了
実装側 : 3 回中 3 回、segfault

既存の問題ではありませんでした。本実装による退行でした。

原因は最終的に、レビュー AI がデバッガで突き止めました。ウィンドウとコントローラがシグナル接続で互いを参照し合っていて、テストで放棄されたウィンドウが参照カウントでは解放されず、循環 GC でしか解放されない。その GC が後続テストのイベント処理中に走ると、まだタイマーを抱えているツリービューが破棄され、解放済みメモリにタイマーイベントが届く。

そして、実装 AI が「原因はこれ」と信じて製品コードに書き込んでいたコメントは、まったく別のことを説明していました。是正で除去しました。

ここから得た教訓は一つです。

AI の「既存の問題です」「環境要因です」は、AI が本気でそう思っている。だから議論では覆らない。実測でしか覆らない。

「本当ですか」と聞き返しても、もっともらしい説明が返ってくるだけです。main で 4 回、実装側で 3 回走らせる。それだけが答えを出します。

「実装 AI の『既存問題』『環境要因』という主張は、必ず両方を交互に実測してから受け取る」。この件は、そういう教訓として設計 AI の申し送りに残っています。


間違えるのは実装 AI だけではない

ここまで読むと、実装 AI だけが間違えるように見えるかもしれません。違います。

設計 AI も間違えます。

ある改善で、指示書には「この挙動の差は既知で、意図的な修正」と書いてありました。実装 AI はそのとおりに実装しました。ところがその差は、別の経路を伝わって、変更を適用してよいかどうかの判定を変えていました。設計 AI が、その値を誰が読むかを追わなかったのが原因です。

このときの是正は、実装を差し戻すことではありませんでした。実装は指示書の明示的な許可に従っていて、間違っていたのは「記録が要らない」と判断した指示書側だったからです。欠けていた記録を作ることで是正しました。

もう一つ。外部の評価で「未実装」とされた項目を、設計 AI が鵜呑みにして指示書を書き始めたことがあります。コードを見たら、既に実装済みでした。同じことを 2 回やりました。今は「外部評価の『未実装』主張は必ずコードで裏取りする」と手順書にあります。

レビュー AI も間違えます。

先に書いた ImportError の罠は、レビュー AI が初期に見逃していたものです。

誰かが正しいという前提を置かない。全員が間違える前提で、間違いが通り抜けられない配線にする。


気づいたことは、その場で手順書に書く

ここまでに出てきた教訓 — ImportError の罠、「既存問題」の主張、外部評価の鵜呑み — は、どれも見つけた時点で、手順書か指示書テンプレートに書き足しています。

書き足す先は、チャットの履歴ではなく、リポジトリの開発文書です。

理由は 2 つあります。

一つは、人間にも AI にも、同じものが見えること。次のサイクルで指示書を書く設計 AI も、レビューする AI も、同じ文書を読んでから始めます。私が毎回「前にこういうことがあったから気を付けて」と伝える必要がありません。

もう一つは、担当が交代しても手順が残ること。手順書とスクリプトと指示書テンプレートが互いを参照していることを検査するテストもあります。配線が腐らないようにするためです。

そして、これがいちばん大事なのですが、ルールや手順が積み重なっても、私がそれを全部覚えている必要がなくなります。

覚えるべきものは文書にあり、読むべきタイミングは手順に組み込まれている。私は「前にもこんなことがあった気がする」と思い出す仕事から解放されて、最初の指示と最終確認に集中できます。


人間は何をしているのか

改めて、私が何をしているかを書きます。

最初の指示の精度を上げること。

作業指示書を設計 AI がゼロから書くわけではありません。コードの作成、機能の変更、調査。何をしたいのかを最初に伝えるのは私です。その最初の指示が曖昧だと、指示書も、実装も、レビューも、曖昧さをそのまま引き継ぎます。ここが私の主戦場です。

ただし、このとき一つ気を付けていることがあります。

指示によって、AI の能力を縛らないこと。

「こう実装しろ」「この方法でレビューしろ」と細かく指定するほど、AI は私の想定の範囲でしか仕事をしなくなります。私の想定より良い設計、私が思いつかない検証手段があるなら、それを使ってほしい。だから指示は「何を達成するか」と「何を守るか」に留めて、「どうやるか」は各 AI に委ねます。設計・実装・レビューのそれぞれで、その AI が最高の能力を発揮することが、精度の源だからです。

最終的な成果物が目的に達しているか確認すること。

不足や過ちがあれば、そこで終わりにせず、続けて修正・改善に入ります。

コードは書いていません。設計文書もレビュー報告も、AI が書いたものを読んでいます。

私がやっているのは、AI に任せられない判断を見極めることと、AI が間違えている箇所を見つけることです。

そして、後者の多くは、個人の注意力ではなく手順にできます。一度捕まえたら手順書に書く。次からは、誰がレビューしても捕まります。私が覚えていなくても。

AI に何を任せるかという問いは、結局のところ、任せた結果をどう検証するかという問いでした。


関連記事

このシリーズ(Zenn):

作っている製品(R2 Fugu Agent Runtime)の設計については、Qiita に別のシリーズを書いています:

Discussion