最近、AIを使ってかなり大きめのシステム開発を続けています。最初の頃は、ChatGPTとMCPをつないで必要な処理を一つずつ実行していました。それが徐々に大きくなり、いまではCodexを開発側のオーケストレーターに置き、必要に応じてLoopを使いながら、実装、テスト、修正まで回す構成になっています。
かなり便利になりました。一方で、規模が大きくなるにつれて別の問題が目立つようになりました。仕様書を書いているのに、少しずつ仕様からズレていくのです。
今回は、かなり分かりやすい形でそれが起きました。
「完成した」と言われた。でも、完成していなかった
私が目指していた構成では、開発作業をCodex側に渡した後は、基本的にCodexが自分で作業を続けます。実装し、テストし、失敗したら原因を調べて修正し、もう一度テストする。必要であれば計画も変更します。そして、本番環境への反映や認証情報の使用、破壊的な操作など、本当に人間の判断が必要なところまで来たときに初めて私へ戻してくる、という仕組みにしたかったのです。
その思想自体は、最終アーキテクチャの仕様書にも書いてありました。開発もかなり進み、Codexのworkerが動き、実行状態も保存され、途中でChatが切れてもworkspaceやcheckpointは残るようになりました。各段階のacceptance testも通り、「完成」と判断しました。
ところが実際に長時間の開発を動かしてみると、Chatがタイムアウトしたところで開発も止まりました。状態は消えていません。Codexのworkerも生きています。ただし、「次を続ける」という指示をChat側からもう一度出さなければ進みません。
つまり、私が欲しかったものは「ChatがなくてもCodexがOwner判断の必要な地点まで走る仕組み」でしたが、実際に出来上がっていたものは「Chatがなくなっても作業状態を失わず、次のChatから再開できる仕組み」でした。
似ていますが、かなり違います。
なぜ仕様書があるのに、こうなるのか
最初は単純に、AIが仕様を忘れたのかと思いました。しかし調べていくと、もう少し構造的な問題でした。
AIは、その時点で与えられた仕事を片付けることにはかなり強いです。「Codex workerを実装する」「実行状態を永続化する」「このテストを通す」「このフェーズのacceptanceを完了する」といった仕事であれば、かなりきれいに進められます。
問題は、その一つ一つが成功していても、最初に決めた大きな目的まで達成されているとは限らないことです。
今回でいえば、最初の目的は「ChatGPTを開発の実行主体にしない」でした。それが開発を進めるうちに「Codex workerを作る」になり、さらに「Codex workerのacceptance testを通す」へと変わっていきました。
誰かが明示的に仕様を書き換えたわけではありません。作業を細分化して具体的な実装へ落としていく過程で、最初の目的の意味が少しずつ圧縮されていったのだと思います。
人間の開発でも起きる。でもAIでは速く起きる
これはAIだけの問題ではありません。人間の開発でも、「この状態を改善するためにKPIを設定した」はずなのに、いつの間にか「KPIの数字を上げること」自体が目的になってしまうことがあります。
テストでも同じです。本来は品質を保証するためのテストだったはずなのに、開発が進むうちに「テストを通すこと」が目的になってしまいます。
今回もそれに近いものでした。ある開発フェーズのacceptanceをすべて通したとしても、本来は「目標としているシステムに一歩近づいた」という意味にすぎません。しかし途中から、それが「このフェーズを通過したのでシステムは完成した」という判定に変わってしまいました。
AI開発では、この変化が起こりやすいように感じます。理由の一つは単純に開発速度が速いからです。人間なら数日かけて何度も確認するような変更が、短時間で次々と積み上がります。その分、小さな意味のズレも高速で積み上がっていきます。
一つ一つの変更を見ると正しい。テストも通っている。PR単位でも問題はありません。それでも、数十回積み上げた後に最初の設計へ戻ってみると、「あれ、少し違うものになっている」ということがあります。
仕様書は「存在する」だけでは弱い
今回の件で、一つ大きな勘違いにも気づきました。私はどこかで、仕様書を書いておけば、その仕様が開発全体を拘束してくれると考えていました。
実際には、そうではありません。
仕様書に「人間へ戻すのは、本番反映や認証、破壊的操作などの境界だけ」と書いてあったとしても、実際のテストに「開発途中でChatを切断し、その後一度もChatからcontinueを送らなくても、Owner判断の必要な地点まで進むこと」と書かれていなければ、その条件を満たしていない実装でも全テストを通過できます。
考えてみれば当然です。自然言語で書かれた設計思想と、機械が実際に検証できる条件は別物です。
仕様書に書いてあることと、システムが保証していることは同じではありません。
特に大きなシステムになるほど、「仕様書に書いてあるから大丈夫」ではなく、その重要な要求がどこで検証されているのかまで確認する必要があります。
AIは仕様書を抱えたまま走っているわけではない
AIを使った開発では、もう一つ意識しておいた方がよさそうなことがあります。
AIは、一度仕様書を読んだら、その内容を完全な内部状態として永久に保持しながら開発しているわけではありません。その時点で見えている要求、現在のコード、取得したドキュメント、直前の実行結果、テスト結果などから、その都度「次に何をするべきか」を判断しています。
長い開発になると、最初に一番重要だった一文よりも、目の前で落ちているテストや具体的なエラーの方が強くなります。それ自体は自然な動きです。テストが落ちていれば、まず直す必要があります。
ただ、それを何十回も繰り返しているうちに、「そもそも何のためにこの仕組みを作っていたのか」が遠くなっていきます。
最初は「ChatGPTを実行主体にしない」という設計だったものが、「Codex workerを正常に動かす」という課題になり、最後には「このテストスイートを通す」という課題になります。どれも間違ってはいませんが、同じ意味でもありません。
大規模なAI開発で管理すべきなのは、コードだけではなかった
今回の件を受けて、開発方法も少し変えることにしました。
最上位の要求については、仕様書に文章として残すだけではなく、可能な限り実行可能なacceptance testまで落とします。
今回なら、「Codexが自律的に開発を継続する」と書くだけでは不十分でした。実際に開発途中でBrowser ChatGPTを切断し、その後はChatから一切追加指示を出さない状態にします。それでもCodexが実装、テスト、失敗解析、修正、再テストを続け、mainへの統合など、本当に人間の承認が必要なところまで進むことを確認します。
そこまで通して、初めて「完成」とします。
これはかなり重要だと思っています。正常系のテストだけを積み上げると、システム内部の機能は正しくても、最初に欲しかった利用体験が実現していないことがあります。
今回の「Chatを切る」という試験は、コードの機能試験というより、最初に決めたアーキテクチャそのものの試験です。
「目的 → 仕様 → テスト」の意味を保存する
AIコーディングについては、モデルの性能やプロンプトの書き方がよく話題になります。もちろん、それも重要です。
ただ、大きなシステムを長期間AIと開発していると、それとは別の問題が出てきます。コードを書く能力よりも、最初に決めた目的の意味を、何十回もの実装をまたいで保存することの方が難しくなってきます。
目的があり、それを仕様に落とし、実装に分解し、テストにします。そして最後に、「テストが通ったか」だけではなく、「最初の目的を本当に満たしているか」まで戻って確認します。
人間だけで開発していた頃にも必要だったことだと思います。ただ、AIによって開発速度が大きく上がったことで、この確認を怠ったときのズレも速い速度で積み上がるようになりました。
仕様書を書いた。テストも通った。だから完成。
今後は、その間にもう一つ確認を入れた方がよさそうです。
「最初に作ろうとしていたものは、本当に出来上がっているのか?」
今回、それを実運用の中で確認することになりました。

コメント