
学生のころ、友人とレポートを分担したことがあります。前半を私、後半を友人。 話は5分で決まりました。
提出の前夜に突き合わせたら、真ん中の章が2つありました。
私は「前半に入る」と思って書き、友人は「後半に入る」と思って書いていた。中身はほとんど同じで、参考文献まで一致していました。
分担が失敗した理由は、境界を決めなかったからではありません。お互い、自分の担当だと信じていたからです。 疑う理由がどこにもなかった。
先に一行だけ置きます。アジャイル開発とは、短いサイクルで動くものを出し、それを見ながら次を決めていく進め方のことです。最初に全部決めて順番に作るウォーターフォールに対して、途中で変わることを前提にします。
題材は、ホテル向けのゲストサービス基盤です。滞在客が自分の端末から手続きをして、通知を受け取る。小さなアプリが複数ぶら下がっている構成です。
前置きはさておき、本題に入ります。
今日は、この開発にAIを入れて並行度を上げた結果、何が起きたかという話をしていこうと思います。
① 教科書どおりに、短いサイクルで並行させる

アジャイルの教科書どおりに進めました。短く区切って、動くものを出し、見ながら次を決める。
そこにAIを入れると、サイクルはさらに短くなります。そして、並行させられるようになりました。
→ 人がブランチを切って、片方の機能を進める
→ AIが別のブランチで、別の機能を進める
→ できたものから順にマージする
きれいに回りました。 作業が重ならないようにブランチを分けているので、衝突も起きません。
ここまでは、教科書どおりです。
この時点の体制を数字で置いておきます。同時に走るブランチが2本、ぶら下がるアプリが5つ、1サイクルは3日。 マージは週に2回でした。
| 指標 | 導入前 | 並行後 |
|---|---|---|
| 同時進行のブランチ | 1本 | 2本 |
| 1サイクル | 10日 | 3日 |
| 週あたりのマージ | 1回 | 2回 |
② そのとおりに進めて、通知が2回届く

本番で、プッシュ通知が2回届くようになりました。
しかも1つのアプリではありません。ぶら下がっている5つのアプリ、全部でです。
引き継ぎ資料に、こう残っています。
「item 15 (order-placed push) was built TWICE」
注文完了の通知が、2回実装されていました。
人のブランチとAIのブランチが、同じ通知機能を、それぞれ独立に作っていたのです。
そして、ここに核心があります。
関数名は、5つとも完全に一致していました。
違っていたのは、トリガの名前にアンダースコアが1つ入っているかどうかだけでした。
③ そして、Gitがコンフリクトしていないと気づく

Gitは、何も警告していませんでした。
ファイル名が違い、トリガ名も一文字違うので、衝突として検出されないからです。
つまり、こうなっていました。
→ バージョン管理はきれいなまま
→ レビューも通っている
→ 本番だけが壊れている
対処として、一度はマイグレーションのファイルを削除しています。ところが、その削除に付けられたコメントが、この記事の核心です。
「v2はマイグレーションファイルを削除しただけ。既に適用済みのDBには何の効果もない。古いトリガが新しいトリガと並存し、注文INSERTごとに両方が発火して、5つのミニアプリ全部でプッシュが2回届く」
ファイルを消しても、データベースからは消えません。
④ 原因は「作業単位」と「真の状態」がずれていたこと

原因を一つに絞ると、これでした。
AIの作業単位はリポジトリです。しかし、システムの真の状態はデプロイ済みのデータベースにあります。
エージェントは、ファイルを削除すれば機能が取り消されると推論します。 これはコードについては正しい。ところがデータベースの定義は、一度適用されたら、ファイルの有無とは無関係に残り続けます。
そして、これはAI特有の誤りではありません。人も同じように間違えます。 ただ、並行度が上がったぶん、間違いが本番に届く速度だけが上がりました。
数え直すと、こうなっていました。同じ責務の関数が5つ、トリガが2つ、発火が1注文あたり2回。 影響したアプリは5つ全部です。
⑤ 直してみる — 消すものと、残すものを分ける

やったことは3つです。
1. トリガだけを落として、関数は残した。
二重に発火していたのはトリガです。関数そのものは片方が使われている。全部消すのではなく、発火の口だけを閉じました。 外科的な判断です。
2. ガードレールを、コードレビューから移した。
レビューでは見つかりません。ファイル名も定義名も違うからです。 見るべきなのは、適用済みの定義と、いま書かれている定義の差分でした。
3. 二重実装のうち、AI側を採用した。
人が書いた側を捨てています。理由は、AI版のほうが考慮が多かったからです。
→ 表示言語の解決が入っていた
→ 初期ステータスの制御が入っていた
→ 例外を包んで、決済のロールバックを防ぐ処理が入っていた
書いた主体ではなく、含まれている考慮の数で決めました。
判断に使った表がこれです。
| 観点 | 人が書いた版 | AIが書いた版 |
|---|---|---|
| 表示言語の解決 | 無し | 有り |
| 初期ステータスの制御 | 無し | 有り |
| 決済ロールバックの防止 | 無し | 有り |
| 採否 | 破棄 | 採用 |
運用の側でも、同じ考えの線が引かれていました。社内の質問に対する回答にこうあります。「productionに移行するまでにソースレビューをしたり、ProductionのDBマイグレが破壊的なのでアクセス権を切り替えてDBマイグレをする人を限定しています」。
ところで、ここで限定しているのはコードを書く人ではありません。定義を適用する人です。コードは誰が書いてもよく、適用だけを絞る。 この記事の事故は、まさに適用の側で起きました。
⑥ あとで知った — 25年前の宣言に、答えが書いてあった

素朴な対処のつもりでしたが、調べると同じことを指す一文は、すでにありました。
アジャイルソフトウェア開発宣言は、2001年2月11日から13日、ユタ州スノーバードのロッジに集まった17人の開発者によって書かれています。ケント・ベック、マーティン・ファウラー、ロバート・C・マーティンといった面々です。
そこにまとめられたのは、4つの価値と12の原則でした。有名なのは価値のほうです。
「包括的なドキュメントよりも、動くソフトウェアを」
私たちが速くしていたのは、まさにこの方向でした。ドキュメントを飛ばして、動くものを出す。AIを入れて、さらに速くする。
ところが、12の原則のほうにこう書かれています。
「技術的卓越性と優れた設計に対する不断の注意が、機敏さを高める」
機敏さは、設計への注意の結果として得られる、と書かれているのです。注意を省いた先にあるものではない。
私たちは、速くするために注意を省き、その結果として機敏さを失っていました。 通知が2回届く状態は、機敏でも何でもありません。
25年前の宣言に、順序が書いてありました。
新しい理屈は、ひとつも要りませんでした。
ところで、宣言の12の原則には他にも効く一文があります。「動くソフトウェアこそが進捗の最も重要な尺度です」「シンプルさが本質です」。ただ、私たちが飛ばしていたのは「技術的卓越性への不断の注意」の1件だけでした。とはいえ、飛ばした1件が全部を持っていきました。
あとで知った — この現象には、名前と論文が付いていた
適用済みの定義とリポジトリがずれる、という話を現場の勘で扱っていました。この現象には、すでに名前が付いています。
スキーマドリフトです。そして2026年、PingCAP の研究チームが TINE(TiDB Iterative Non-Destructive Environment for Agentic App Building) という論文で、これを正面から扱っています。データベース分野の国際会議 SIGMOD の2026年 Companion に採択されました。
論文の指摘が、こちらの実感より一段厳しい。エージェントが移行処理を生成して実行し、その途中で実行時エラーに当たると、データベースは「元の定義でも、新しい定義でもない状態」で残ります。
壊れるのは、2つの変更が噛み合わないときだけではありません。1つの変更が途中で止まっただけでも、そうなります。
対処として論文が採る形が、命令ごとに分岐を切る設計です。次の版は、前の版のデータベース分岐のコピーオンライトの複製を出発点として受け取ります。移行が失敗しても、失敗はその分岐の中に閉じ込められます。
運用側の対処としては、2026年時点でこう整理されています。
“`
- エージェントごとに作業場を分ける(git の worktree 単位で隔離する)
- 共有の定義変更は、担当を1名に固定する(移行の所有者を決める)
- あるいは、各エージェントが自分の表しか触らないように仕事を割る
“`
2件目が要点です。 定義の変更だけは並行させない。並行度を上げる対象と、上げてはいけない対象を分けるという線の引き方で、これはコードレビューの強化では出てこない発想でした。
⑦ 何が変わったか

レビューする対象が変わりました。
“`
前:コードの差分を見る → ファイル名が違えば衝突に見えない
後:適用済みの定義との差分を見る → 名前が違っても重複が見える
“`
「Gitがきれいなら安全」という前提を捨てたことが、いちばん大きい変化です。
見る対象を変えてから、同種の重複は0件です。ただし観測期間はまだ3か月なので、断言はしません。
⑧ 現場で使うなら、この2枚

表1:並行させる前に決めること
| 決めること | 決めないと何が起きるか |
|---|---|
| 機能の境界(名前ではなく責務で) | 同じものが二度作られる |
| データ定義を触ってよいブランチ | 適用済みの定義が並存する |
| 削除の意味(ファイルか、適用済みか) | 消したつもりが消えていない |
| 二重実装が出たときの採否基準 | 書いた人で決めてしまう |
表2:Gitで見つからない衝突の探し方
| 見るもの | 方法 | 見つかるもの |
|---|---|---|
| 適用済みの定義一覧 | 本番から取得して比較 | 名前違いの重複 |
| 発火する口の数 | トリガ・フックを数える | 二重発火 |
| 同一イベントの購読者 | 購読側を列挙 | 通知の重複 |
「ファイルを消したから大丈夫」を、この表で置き換えてください。
運用では、次の5件に名前を付けて配りました。名前が付くと、会議の中で指差せるようになります。
1件目、「コンフリクトしない衝突がある」。 バージョン管理が何も警告しないまま、2件の変更が本番で噛み合わなくなります。並行度を上げた日から出ました。
2件目、「システムの真の状態は、適用済みDDLにある」。 リポジトリの中身ではありません。両者がずれた瞬間、レビューは実態を見ていないことになります。
3件目、「ファイルを消しても、定義は消えない」。 削除したのは手順書であって、適用済みの定義そのものではありません。ここを取り違えると、消したつもりの列が残ります。
4件目、「レビュー対象は、コードからスキーマへ移す」。 並行度が上がるほど、事故はコードの外で起きます。見る場所を移さないまま人数だけ増やすと、見落としが増えます。
5件目、「並行度を上げる前に、差分検証を置く」。 適用済みDDLとの突き合わせを自動化してから、同時に走らせる本数を増やします。順番を逆にすると、戻すのに数日かかります。
この5件は、1枚に印刷して配れる分量に収めています。増やすと、誰も覚えません。
⑨ この考え方が効き続ける理由

並行度は、これからも上がります。そして、上がるほどコンフリクトしない衝突が増えます。 これは技術の進歩で解決しません。Gitが見ているものと、本番が持っているものが違う、という構造だからです。
→ 並行度が上がる → 同じ責務に別の名前が付く
→ 名前が違う → Gitは衝突を検出しない
→ 検出されない → レビューも通る
→ だから、本番だけが壊れる
検知にかかった時間も残しておきます。二重発火が始まってから気づくまで6日、原因の特定に2時間、修正そのものは15分でした。見つけるまでが、直すまでの24倍かかっています。
アジャイル開発にAIを入れるとき、増やすべきはレビューの回数ではありません。レビューの対象を、コードから適用済みの定義へ移すことです。
ただし、正直に書いておきます。この差分検証は、まだきれいに自動化できていません。 適用済みの定義を取ってきて比較する仕組みは作れますが、「これは重複か、意図的な並存か」の判断は人が見ています。そこを機械に渡す方法は、まだ持っていません。
ところで、事故のあと引き継ぎ資料に書き足した一文が「ファイルの削除は、適用済みの定義を取り消さない」です。ただ、この一文を読む人がいるかは分かりません。
関連して、内製化しても意思決定は速くならない話と、PoCでは出ない最後の0.1%話を別に書いています。AI時代の要件定義話はすでに公開しています。あわせて読むと、同じ判断が別の場所でも効いていることが分かると思います。
この判断を実際の案件で使う場面については、AIプロダクト開発のページに整理しています。
並行作業といえば、洗濯機を回しながら料理をして、両方を焦がしたことがあります。
洗濯物は焦げないはずなんですが、乾燥までかけたまま忘れるという形で、ちゃんと焦げました。
以上です。