アジャイル開発とは?AIで並行させたら同じ機能が2回できた

アジャイル開発とは、短いサイクルで動くものを出しながら進める開発の進め方です。AIを使って並行させると、このサイクルはさらに短くなります。ところが、人のブランチとAIのブランチが同じ通知機能を実装し、本番で通知が2回届く事故が起きました。関数名は5つとも完全一致、違いはトリガ名の一文字だけ。Gitはコンフリクトしていません。バージョン管理がクリーンなまま本番が壊れます。AIの作業単位はリポジトリですが、システムの真の状態はデプロイ済みのデータベースにあります。
Total
0
Shares
アジャイル開発とは?AIで並行させたら、同じ機能が2回実装されていた

学生のころ、友人とレポートを分担したことがあります。前半を私、後半を友人。 話は5分で決まりました。

提出の前夜に突き合わせたら、真ん中の章が2つありました。

私は「前半に入る」と思って書き、友人は「後半に入る」と思って書いていた。中身はほとんど同じで、参考文献まで一致していました。

分担が失敗した理由は、境界を決めなかったからではありません。お互い、自分の担当だと信じていたからです。 疑う理由がどこにもなかった。

先に一行だけ置きます。アジャイル開発とは、短いサイクルで動くものを出し、それを見ながら次を決めていく進め方のことです。最初に全部決めて順番に作るウォーターフォールに対して、途中で変わることを前提にします。

題材は、ホテル向けのゲストサービス基盤です。滞在客が自分の端末から手続きをして、通知を受け取る。小さなアプリが複数ぶら下がっている構成です。

前置きはさておき、本題に入ります。

今日は、この開発にAIを入れて並行度を上げた結果、何が起きたかという話をしていこうと思います。

① 教科書どおりに、短いサイクルで並行させる

教科書どおりに、短いサイクルで並行させる

アジャイルの教科書どおりに進めました。短く区切って、動くものを出し、見ながら次を決める。

そこにAIを入れると、サイクルはさらに短くなります。そして、並行させられるようになりました。

→ 人がブランチを切って、片方の機能を進める
→ AIが別のブランチで、別の機能を進める
→ できたものから順にマージする

きれいに回りました。 作業が重ならないようにブランチを分けているので、衝突も起きません。

ここまでは、教科書どおりです。
この時点の体制を数字で置いておきます。同時に走るブランチが2本、ぶら下がるアプリが5つ、1サイクルは3日。 マージは週に2回でした。

指標導入前並行後
同時進行のブランチ1本2本
1サイクル10日3日
週あたりのマージ1回2回

② そのとおりに進めて、通知が2回届く

そのとおりに進めて、通知が2回届く

本番で、プッシュ通知が2回届くようになりました。

しかも1つのアプリではありません。ぶら下がっている5つのアプリ、全部でです。

引き継ぎ資料に、こう残っています。

「item 15 (order-placed push) was built TWICE」

注文完了の通知が、2回実装されていました。

人のブランチとAIのブランチが、同じ通知機能を、それぞれ独立に作っていたのです。

そして、ここに核心があります。

関数名は、5つとも完全に一致していました。

違っていたのは、トリガの名前にアンダースコアが1つ入っているかどうかだけでした。

③ そして、Gitがコンフリクトしていないと気づく

そして、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年前の宣言に、答えが書いてあった

あとで知った — 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年時点でこう整理されています。

“`

  1. エージェントごとに作業場を分ける(git の worktree 単位で隔離する)
  2. 共有の定義変更は、担当を1名に固定する(移行の所有者を決める)
  3. あるいは、各エージェントが自分の表しか触らないように仕事を割る

“`

2件目が要点です。 定義の変更だけは並行させない。並行度を上げる対象と、上げてはいけない対象を分けるという線の引き方で、これはコードレビューの強化では出てこない発想でした。

⑦ 何が変わったか

何が変わったか

レビューする対象が変わりました。

“`
前:コードの差分を見る → ファイル名が違えば衝突に見えない
後:適用済みの定義との差分を見る → 名前が違っても重複が見える
“`

「Gitがきれいなら安全」という前提を捨てたことが、いちばん大きい変化です。
見る対象を変えてから、同種の重複は0件です。ただし観測期間はまだ3か月なので、断言はしません。

⑧ 現場で使うなら、この2枚

現場で使うなら、この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プロダクト開発のページに整理しています。


並行作業といえば、洗濯機を回しながら料理をして、両方を焦がしたことがあります。

洗濯物は焦げないはずなんですが、乾燥までかけたまま忘れるという形で、ちゃんと焦げました。

以上です。

You May Also Like

LLMとは?検算させたら拾い漏れと「拾いました」が並んだ

LLMとは大規模言語モデルの略で、大量の文章から言葉の続き方を学習したモデルです。指摘を全部反映したかの確認まで任せたところ、拾い漏れと「全部拾いました」という報告が同時に成立しました。判定をモデルに渡す限りこれは消えません。判定を一切LLMに渡さず、退屈な文字列一致で機械が検査する設計に変えました。全角空白の除去まで含めた実装と、判定に使ってよい出力・いけない出力の切り分けを書きます。
View Post

ベクトルデータベースとは?上位10件の壁に当たった話

ベクトルデータベースとは、文章を数値の並びに変えて、意味の近さで探せるようにした保存先です。日本語の全文検索が精度不足だったため、全文検索を避けてベクトル検索に寄せる判断をしました。ところが取得できるのは上位10件までで、そこに入らなかったものは存在しないのと同じになります。全体のナレッジグラフを作るのではなく、各チャンクに関係性のメタデータを持たせて局所的に推論させる方針まで書きます。
View Post

MCPとは?社内ナレッジ検索の画面を作るのをやめて、MCP接続にした話

MCPとはModel Context Protocolの略で、AIと社内の道具やデータを繋ぐための共通の作法です。社内ナレッジを全社に開くために専用のフロントエンドを作ろうとしていましたが、初期フェーズではそれを見送り、MCP接続を主軸にする設計変更をしました。理由は画面が要らないからではありません。画面を作ると、そこに認証も権限も検索体験も全部くっついてくるからです。残したのはAPIキーの発行とフォルダごとの権限管理だけでした。何を作らないと決めたのかを書きます。
View Post

プロンプトインジェクションとは?禁止をプロンプトに書かない

プロンプトインジェクションとは、AIへの指示文に別の指示を紛れ込ませて、本来の制約を外させる攻撃です。対策としてまず思いつくのは、システムプロンプトに禁止事項を書き足すことです。私たちもそうしました。そして守られませんでした。プロンプトによる禁止は確率的で、ツール層のフックは決定的です。44行のフックで守る側へ移した実装と、パスの正規化や失敗時に閉じる設計まで、そのまま書きます。
View Post

プロンプトエンジニアリングとは?AIに組み立てさせるのをやめた

プロンプトエンジニアリングとは、AIから望む出力を得るために指示文を設計する技術です。ところが指示文そのものをAIに組み立てさせると、同じ入力でも毎回わずかに違う文面が生成され、結果が揺れます。原因を追うと、揺れているのはモデルではなくプロンプトのほうでした。CLIが変数を埋め込んだ全文を出力する方式に変えると、プロンプトはバージョン管理できる成果物になります。再現性をどこから取り戻すかを書きます。
View Post

AIエージェントとは?目標を書き換えさせたら根拠を捏造した

AIエージェントとは、指示を待つのではなく、目標に向かって自分で手順を決めて動くAIのことです。ならば目標そのものも書き換えさせられるはずだと考えて、実装しました。動きました。ただし提案の根拠として引用された見出しが、実在しないことがありました。しかも「確信度は高い」と自己申告されています。AIの自己申告を信頼境界にしてはいけません。根拠が実在するかを機械が検証する多段の安全弁を、実装ごと書きます。
View Post