TL;DR: 並列に動くAIエージェントは、カラム名やURLパス、パラメータ形式、識別子のような共有契約(コンパクト)を互いに調整しません。生成の前に、それらの契約を1つの参照ファイルに抽出し、並列エージェントの処理が終わった後に、データのエンドツーエンドの流れを追跡するレビューエージェントを実行します。この1ステップで、17個すべてのバグを1回の実行で修正できました。
AWS上でフルスタックアプリを作るために、8つのAIエージェントを並列に起動しました。インフラストラクチャのスタック、Reactフロントエンド、Javaバックエンドです。各エージェントは自分の担当部分を受け持ち、すべてがクリーンでコンパイル可能なコードを納品しました。CDKは型チェック済みで、JavaバックエンドはSpring Bootの慣習に従い、React UIもきれいに見えました。
しかし、それらをつなぎ合わせようとすると、あらゆる境界でバグにぶつかりました。
The architecture
多数の可動部品を持つ、AWS上のフルスタックアプリです。インフラストラクチャ用の複数のCDKスタック(IAM、VPC、シード関数付きDB、Cognito、CodePipeline、CloudFront/WAF)、ECS Fargate上のSpring Bootバックエンド、S3上でホストするReactフロントエンド。
実装計画は入念で、すべてのコンポーネントをカバーしていました。しかし、共有契約に合意する必要があるエージェントにとっては、詳細が足りませんでした。
The bugs
最初の2つがすべてを止めます。3〜5のバグは、前のものを直した後にのみ見えてきます。
Bug 1: The Spring Boot app won't even start
シードデータ関数は passenger_id と full_name のあるスキーマを作成しますが、Spring Bootのエンティティは id と name にマッピングしています:
-- Agent 1: seed data function creates the schema
CREATE TABLE passengers (
passenger_id VARCHAR(64) PRIMARY KEY,
full_name VARCHAR(255) NOT NULL,
...
);
// Agent 2: The Spring Boot entity maps the table
@Column(name = "id") // Schema says "passenger_id"
@Column(name = "name") // Schema says "full_name"
ddl-auto: validate を使っているため、Hibernateは起動時にマッピングを確認します。しかし存在しないカラムなので、ECSのタスクがリクエストを1件も処理する前にクラッシュします。
Bug 2: Every call returns 404
CDKスタックは /approve と /generate のALBルートを登録しますが、Javaクライアントは /voucher/approve と /voucher/generate にリクエストを送ります:
CDK ALB routes: /approve, /generate
Java client: /voucher/approve, /voucher/generate
どちらのエージェントも単体では正しく動くコードを書きましたが、CDKスタックはクリーンなパスを使っており、Javaクライアントはサービスプレフィックスを追加していました。どちらも相手の前提を確認していません。
Bug 3: Missing request fields
下流サービスは4つの必須フィールドを検証します。Javaクライアントは3つだけ送ります:
Lambda expects: escalationId, passengerId, amount, situation
Java sends: escalationId, passengerId, amount
バグ2のURLを直しても、すべての承認(approval)が400になります。
Bug 4: User lookup doesn't work
これは特に面白いやつでした。ユーザーを扱う3つのシステムがあり、それぞれが自分で識別子を作っていたのです:
Cognito custom attribute: custom:passenger_id = "pax-a1b2c3d4-e5f6-..."
RDS seed data: passenger_id = "PAX-a1b2c3d4-e5f6-..."
JWT subject claim: sub = "a1b2c3d4-e5f6-..." (Cognito UUID)
バックエンドは jwt.getSubject() を使ってユーザーを検索します。これはCognito UUIDであり、pax- でも PAX- でもプレフィックスされていません。そのため、ユーザー検索の結果は一度も返りません。
3つのエージェント。3つの命名規則。ゼロの調整。
Bug 5: Every status lookup returns "not found"
下流サービスはJSONを返します。JavaクライアントはXMLとして解析します:
{"status": "FOUND_LOCAL", "location": "Warehouse-B-Shelf-47"}
String status = extractXmlElement(xml,"status"); // Looks for <status>...</status>
JSON文字列にはXMLタグがありません。extractXmlElement は、すべてのリクエストで空を返します。
下流サービスを書いたエージェントはある仕様(JSON)に従いました。Javaクライアントを書いたエージェントは別の仕様(XML)に従いました。
Bugs 6 to 17: SSM parameter path mismatches
あるCDKスタックがSSMパラメータを書き込み、別のCDKスタックがそれを読み取ります。しかし、パスについては互いに調整していません:
Producer stack writes: /${AppName}/test/data/rds-secret-arn
Consumer stack reads: /${AppName}/${Env}/data/rds-password-secret-arn
...
プロデューサースタックとコンシューマースタックの間で、SSMパラメータが12個分一致していません。アプリはそれらのすべてで失敗します。
Why parallel agents can't catch this
各エージェントには、全体の計画と自分のコンポーネントに関する文脈がありました。しかし、他のエージェントが考えた実装詳細を見ることは誰にもできませんでした。
私がアプリを書くときは、契約(コンパクト)を動作中のメモリに保持しています。「カラムは passenger_id だから、マイグレーションとエンティティの両方でそれを使う」と。ですが、マイグレーションを書くAIエージェントは、エンティティ側のエージェントがカラム名として何を選んだのかを知りません。もちろん、逆も同じです。
計画には高レベルの情報がすべて入っていましたが、エージェントは別々のセクションを読んで、共有部分についてそれぞれ自分の判断で呼び出していました。
各エージェントは、良い慣習に従って正しいコードを書きました。しかし、互いに調整しませんでした。山の反対側からトンネルを掘るようなものです。お互いに一度も確認せずに。
How I found all of them at once
生成後、実際にアプリをデプロイする前に、アーキテクチャレビュー用のエージェントをシンプルな指示で実行しました:
ユーザーのログインからフォーム送信まで、さらに下流サービス呼び出しに至るまでの実際のデータフローを追跡し、あらゆるコンポーネント間の境界を漏れなく追います。
それは、あらゆるバグを1回のパスで見つけました。
レビューエージェントはユーザーに面する入口から開始し、すべての境界を通してリクエストを追跡し、各段階で、あるコンポーネントが送った内容が次のコンポーネントが期待している内容と実際に一致しているかを確認しました。統合テストがデプロイ後に行うのと同じことを、しかし何もデプロイする前に行うのです。
How to prevent seam bugs
並列エージェントを起動する前に、共有する契約(コントラクト)をすべて、計画から取り出して単一の参照ファイルにまとめ、それをすべてのエージェントに必須のコンテキストとして渡してください。
次に、あなたの並列エージェントが仕事を終えた後、いくつかの実際のユーザーフローをすべての境界にまたがって追跡するレビューエージェントを実行します。
シームバグを1回のパスで修正してから、デプロイします。
FAQ
What are seam bugs in AI-generated code?
シームバグとは、異なるAIエージェントによって作られたコンポーネント間の境界における統合不具合です。各エージェントは、単体では正しく動作するコードを書きますが、共有される詳細について各エージェントがそれぞれ判断した結果が噛み合わないため、コンポーネント同士がうまく組み合わさりません。たとえば、カラムに何と呼ぶか、APIがどのパスにあるか、識別子がどの形式を使うかといった点です。
Why does parallel AI code generation produce integration bugs?
各エージェントは、自分のコンポーネントと、与えられた計画(プラン)しか見ません。2つのエージェントが何かについて合意する必要がある場合、たとえばデータベースのカラムが何と呼ばれるべきか、彼らはそれぞれ独立に合理的な名前を選びます。その名前は多くの場合一致しません。計画には、そのカラムが何を表すべきかが書かれていますが、必ずしも双方が使うべき正確な文字列までが指定されているわけではありません。
How do you catch integration bugs from parallel AI agents?
生成後に、すべての境界をまたいで実際のユーザーフローを追跡する単一のレビューエージェントを実行してください。たとえば「ユーザーのログインからフロントエンド、バックエンド、データベース、そして下流サービス呼び出しまでのデータフローを追跡し、すべての境界をチェックする」ようなプロンプトを与えます。そうすれば、1回のパスで不一致を見つけられます。




