TL;DR: 並列のAIエージェントは、列名やURLパス、パラメータ形式、識別子などの「共有契約」について調整しません。生成の前に、それらの契約を1つの参照ファイルに抽出し、並列エージェントが完了した後に、データのエンドツーエンドの流れを追跡するレビューエージェントを実行します。この1ステップで、17個すべてのバグを1回の実行で直せました。
AWS上でフルスタックアプリを作るために、8つのAIエージェントを並列で起動しました。インフラストラクチャのスタック、Reactフロントエンド、Javaバックエンドです。各エージェントは担当する1つの要素を持ち、すべてクリーンでコンパイルできるコードを出力しました。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を直しても、すべての承認が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"); // <status>...</status> を探します
JSON文字列にXMLタグはありません。extractXmlElement は、すべてのリクエストで空を返します。
下流サービスを書いたエージェントは、ある仕様(JSON)に従いました。Javaクライアントを書いたエージェントは、別の仕様(XML)に従いました。
Bugs 6 to 17: SSM parameter path mismatches
1つのCDKスタックがSSMパラメータを書き込みます。別のCDKスタックが読み取ります。しかし、パスについては一度も調整していません:
Producer stack writes: /${AppName}/test/data/rds-secret-arn
Consumer stack reads: /${AppName}/${Env}/data/rds-password-secret-arn
...
プロデューサースタックとコンシューマースタックの間で、12個のSSMパラメータが一致していません。アプリはそれらすべてで失敗します。
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
AI生成コードにおけるシームバグとは何ですか?
シームバグとは、異なるAIエージェントによって作られたコンポーネント同士の境界における統合上の欠陥のことです。各エージェントは、単独であれば正しく動作するコードを書けますが、コンポーネント同士がうまく噛み合いません。なぜなら、共有される細部について各エージェントがそれぞれ独自の判断をしてしまうからです。たとえば、列(カラム)の呼び名、APIが存在するパス、あるいは識別子(ID)がどのフォーマットを使うか、といった点です。
なぜ並列のAIコード生成は統合バグを生みやすいのですか?
各エージェントは自分のコンポーネントと、与えられた計画(プラン)しか見ていません。2つのエージェントが何かを一致させる必要がある場合、たとえばデータベースの列(カラム)が何と呼ばれるべきか、彼らはそれぞれが独立して合理的な名前を選びます。そうした名前はしばしば一致しません。計画(プラン)は、その列が何を表すべきかは示しますが、両者が使うべき「正確な文字列」が何かまでは必ずしも示しません。
並列のAIエージェントから生じる統合バグはどうやって見つけますか?
生成後に1人のレビュー担当エージェントを実行し、すべての境界をまたいで実際のユーザーフローを追跡します。「ユーザーのログインからフロントエンド、バックエンド、データベース、そして下流サービス呼び出しまでのデータフローを追跡し、すべての境界をチェックして」といったプロンプトを与えてください。そうすれば、1回のパスで不一致を見つけられます。
