【ITニュース解説】Why Your AI Agent Produces Duplicates (It’s a Modeling Error, Not a Logic Error)
2026年10月13日に「Dev.to」が公開したITニュース「Why Your AI Agent Produces Duplicates (It’s a Modeling Error, Not a Logic Error)」について初心者にもわかりやすく解説しています。
ITニュース概要
AIエージェントが処理を重複させる原因は、完了と成否を単一の状態にまとめる設計ミスにある。これを防ぐには、処理が届いたか(配送状態)と、内容が意図通りか(受容状態)を独立した二つの軸で管理するモデルが有効。未確認と失敗を区別し、無駄な再実行を防ぐ。
ITニュース解説
AIエージェントが意図せず同じ処理を二度実行してしまう、いわゆる「重複ディスパッチ」の問題は、システムの信頼性を損なう深刻なバグの一つだ。多くの人は、これはリトライの仕組みが不十分だったり、処理のべき等性(同じ操作を何度行っても同じ結果になる性質)が考慮されていなかったりするロジックエラーだと考えがちだが、実はその根本原因は、システムの「状態モデリング」の誤りにある場合が多い。
この問題の核心は、独立した二つの問いを、あたかも一つであるかのように扱ってしまう点にある。その二つの問いとは、「我々が指示した処理(ディスパッチ)が実行されたという証拠は得られたか」という問いと、「その実行結果が、私たちが意図した目標を達成しているか」という問いである。これら二つは全く異なる意味を持つにもかかわらず、多くのシステムでは、これらを「成功」か「失敗」かという単一の状態に押し込めてしまう。
例えば、ある処理を実行するよう指示を出し、一定時間内にその結果が返ってこなかった場合、システムは「失敗」と判断し、再度同じ処理を指示することがある。しかし、「結果がまだ返ってきていない」という状態と、「処理が実際に失敗した」という状態は同義ではない。「まだ返ってきていない」だけなのに「失敗」と決めつけてリトライすると、実は裏側では処理が成功していた場合、結果として同じ処理が二度実行されてしまう。これが重複ディスパッチの典型的なパターンだ。この単一の状態表現では、次に本当に何をすべきかの重要な情報が失われ、誤った復旧経路を選んでしまうことになる。
この問題を解決するため、ニュース記事では「二軸モデル」という考え方を提案している。これは、あらゆるアクションの結果を「配送状態(DeliveryState)」と「受容状態(AcceptanceState)」という、二つの独立した軸で管理する方法だ。
「配送状態(DeliveryState)」は、私たちが指示したアクションがプロバイダ(例えば、外部のAPIサービスやデータベースなど)によって実際に受け付けられ、その結果としての「アーティファクト」(処理の結果として生成されるデータや記録)が存在するかどうかを示す。 具体的には、次のような状態を持つ。
PENDING: アーティファクトがまだプロバイダによって確認されていない状態。CONFIRMED: プロバイダによってアーティファクトが存在することが確認された状態。FAILED: プロバイダによる確認の結果、アーティファクトが見つからないか、エラーが発生した状態。
一方、「受容状態(AcceptanceState)」は、配送状態がCONFIRMEDとなり、アーティファクトが確認された後、そのアーティファクトが私たちの「意図した結果」と意味的に合致しているかどうかを評価する状態である。
こちらは、次のような状態を持つ。
PENDING: まだ評価されていない状態。配送状態がCONFIRMEDになってから評価が可能となる。ACCEPTED: アーティファクトが意図した結果と一致し、受け入れられた状態。REJECTED: アーティファクトは存在するが、内容が間違っているなど、意味的に意図と合致しないため拒否された状態。
この二つの状態は完全に独立して進捗する。最終的なアクションのステータスは、これら二つの状態からその都度導き出されるものであり、直接どこかに「成功/失敗」として保存されることはない。
このモデルが安全に機能するための重要なルールとして、「構造的整合性」がある。これは、「受容状態(AcceptanceState)」をPENDINGから変更する(つまり、評価する)には、必ずその前に「配送状態(DeliveryState)」がCONFIRMEDでなければならない、という厳格な制約を設けることだ。もし配送がまだ確認されていないのに受容状態を評価しようとすると、システムはエラーを発生させる。これにより、未確認のディスパッチが誤って「受け入れられた」と判断されるようなコードパスが存在しないことを保証する。
この分離が、重複ディスパッチをどのように防ぐのかを見てみよう。従来のモデルでは、「確認がまだ来ていない」ことを「失敗」とみなし、すぐにリトライすることで重複を引き起こしていた。しかし、二軸モデルでは、DeliveryStateがPENDING(配送未確認)の間は、システムは安易な再ディスパッチを行わない。この状態のアクションは「未解決」と判断され、リトライではなく「調整(reconciliation)」という別のプロセスに送られる。この調整プロセスでは、プロバイダに対して実際にアーティファクトが存在するかどうかを改めて問い合わせる。もし存在すればCONFIRMEDに更新し、存在しなければそこで初めてFAILEDと判断する。これにより、「確認待ち」の状態で無駄にリトライして重複させる事態を防ぐことができる。
失敗のケースも二つの異なるタイプに分けられ、それぞれ異なる復旧パスを持つ。一つは「意味的ミスマッチ」のケースで、DeliveryStateがCONFIRMEDでAcceptanceStateがREJECTEDの場合である。これは、処理自体は正常に実行され、結果物も存在しているが、その内容(例えば、送信先やメッセージ内容)が意図したものと異なっていた、という状況だ。この場合、元の処理は存在しているので、再実行するのではなく、アクションの定義自体を修正し、修正した内容で新たな処理を実行する必要がある。もう一つは「未確認の配送」のケースで、DeliveryStateがPENDINGの場合だ。この時は、まずプロバイダに問い合わせてアーティファクトの有無を確認し、その結果に基づいて状態を更新するという手順を踏む。
これらの状態をシステムが永続的に管理できるよう、ActionRecordというデータ構造には、actionId(アクションの一意なID)、dispatchId(今回の特定のディスパッチ試行に紐付くID)、providerObjectId(プロバイダが確認した結果物のID)、delivery、acceptance、そして実行時刻、確認時刻、受容時刻といったタイムスタンプを全て保存する。特にdispatchIdとproviderObjectIdをセットで記録することで、たとえシステムが再起動したり、複数回のリトライが行われたりしても、各ディスパッチとその結果の証拠が明確に紐付けられ、異なる実行の証拠を誤って流用してしまうような事故を防ぐことができる。
例えば、スマートフォンのSMS送信をAIエージェントが行う場合、まずActionRecordをDeliveryState.PENDING、AcceptanceState.PENDINGで作成し、送信を指示する。アクセシビリティサービスが「送信ボタンがタップされた」ことを確認したら、そのメッセージの「送信済みフォルダのID」をproviderObjectIdとして記録し、DeliveryState.CONFIRMEDに更新する。その後、実際に送信されたメッセージの宛先や内容が当初の意図と合致するかを検証し、AcceptanceStateをACCEPTEDかREJECTEDに設定する。ここで重要なのは、「メッセージが送信済みフォルダにある」という事実だけでなく、「そのメッセージのIDが、この特定のディスパッチ試行に対応している」という明確な紐付けが証明になることだ。
このように、単一の「成功/失敗」といった抽象的な状態に頼るのではなく、配送と受容という二つの独立した軸でアクションの状態を細かくモデリングし、その間に厳格なルールを設けることで、AIエージェントの重複ディスパッチを防ぎ、より堅牢で信頼性の高いシステムを構築することが可能になる。これは表面的なエラー処理ではなく、根本的な状態設計の改善がもたらす効果である。