{
  "id": 14112930,
  "title": "Why Your AI Agent Produces Duplicates (It’s a Modeling Error, Not a Logic Error)",
  "url": "https://urgent.news/2026/10/13/why-your-ai-agent-produces-duplicates-its-a-modeling-error-not-a",
  "topic": "ai",
  "section": "AI",
  "published": "2026-10-13T04:02:06.000Z",
  "source": {
    "name": "Dev.to",
    "slug": "dev-to",
    "url": "https://dev.to/starebrain/why-your-ai-agent-produces-duplicates-its-a-modeling-error-not-a-logic-error-349h"
  },
  "original_language": "en",
  "account": "Duplicate-dispatch bugs in AI agents stem from a modeling error, not a logic error. The problem arises when two independent questions are collapsed into a single state field. The question \"did we get evidence of dispatch?\" and the question \"does that evidence satisfy the intended outcome?\" are not the same. When forced into a single SUCCESS/FAILED enum, the information needed to determine the next steps is lost, leading to duplicate dispatches.\n\nTo prevent duplicates, StareBrain uses a Two-Axis Model. Every action that produces an observable artifact tracks two independent states: DeliveryState and AcceptanceState. DeliveryState represents whether the artifact has been confirmed by the provider (PENDING, CONFIRMED, or FAILED), while AcceptanceState represents whether the artifact matches the intended outcome (PENDING, ACCEPTED, or REJECTED). The combined status is derived, never stored directly.\n\nA Structural Invariant ensures this approach is safe. The tryAccept() function only evaluates acceptance if delivery is confirmed. This prevents the acceptance state from moving out of PENDING until a provider lookup has confirmed the artifact exists. Without this separation, the system could mark a failed dispatch as succeeded and cause duplicates. For example, an agent dispatching an SMS action would result in a duplicate if the dispatch times out before confirmation arrives and the system retries the failed dispatch.\n\nWith the Two-Axis Model, a different set of rules applies. The shouldRedispatch() function determines whether to retry a dispatch based on the current states of DeliveryState and AcceptanceState. If the delivery state is PENDING, the dispatch should not be redispatched. If the delivery state is FAILED, the dispatch should be redispatched. If the acceptance state is REJECTED, the dispatch should also be redispatched. In all other cases, the dispatch should not be redispatched.\n\nWhen reconciling a dispatch, the reconcile() function checks the delivery state. If it's not PENDING, the function returns the current record. If the artifact is found via provider lookup, the delivery state is set to CONFIRMED, and the acceptance state is evaluated. If the artifact is not found, the delivery state is set to FAILED. This recovery path ensures that only confirmed artifacts are considered for evaluation, preventing duplicates caused by unconfirmed dispatches.\n\nPersisting the dispatch information in an ActionRecord class allows for querying the status of the dispatch at any point, including across retries and process restarts. The class includes fields for actionId, dispatchId, providerObjectId (null until confirmed), delivery state, acceptance state, the time of dispatch, and the times of confirmation and acceptance (which are null if not yet confirmed or accepted).\n\nIn practice, for StareBrain's SMS dispatch on Android, a new ActionRecord is created with a unique actionId, a dispatchId based on the pendingIntent creatorUid, and initial values for delivery and acceptance states set to PENDING. After the accessibility service confirms the send button tap, the providerObjectId is set to the sent message ID, delivery is set to CONFIRMED, and confirmedAt is set to the current instant. Finally, after verifying the recipient and content match, the acceptance state is set based on the result of messageMatchesIntent(). This workflow ensures that duplicates are prevented by maintaining separate records for each dispatch attempt and accurately tracking the delivery and acceptance states.",
  "summary": "Most duplicate-dispatch bugs in AI agents aren’t caused by missing retry guards or broken idempotency keys. They’re caused by collapsing two independent questions into one state field. The question “did we get evidence of dispatch?” and the question “does that evidence satisfy the intended outcome?” are not the same question. When you force both into a single SUCCESS / FAILED enum, you lose the…",
  "key_points": [
    "Duplicate-dispatch bugs stem from modeling error, not logic error.",
    "Two-Axis Model separates DeliveryState and AcceptanceState to prevent duplicates.",
    "ShouldRedispatch() function determines dispatch retries based on state conditions."
  ],
  "editors_take": "Separating delivery status from outcome verification transforms agent reliability by preventing the duplicate dispatches that occur when developers collapse distinct operational states into a single, insufficient data field.",
  "illustration": null,
  "coverage": {
    "outlets": 1,
    "also_reported_by": []
  },
  "ai_generated": true,
  "disclaimer": "Summaries, key points and the editor’s take are written by software from other outlets’ reporting and may contain errors — always check the linked original."
}