By Greg Nowak. Last updated 2026-07-25.
An AI agent submits an order. The receiving API accepts it, but the response is lost during a network timeout. From the agent's point of view, the call failed, so it tries again. If the integration cannot recognise both requests as the same piece of work, the customer may end up with two orders.
The same problem appears anywhere an agent causes a real-world change: creating a support ticket, updating a CRM record, sending a message, publishing an event or calling an external tool. A timeout tells you that the caller did not receive an answer in time. It says nothing about whether the action happened.
Retries improve availability, but they can repeat the work
Retries are necessary. Connections fail, services become briefly unavailable and abandoning an operation after one bad response would make an AI workflow brittle. The missing piece is a reliable way to recognise work that has already begun or finished.
The Cloudflare Agents retry documentation shows why this needs deliberate design. Its general retry method retries a function after thrown errors by default, and scheduled or queued operations have retry behaviour of their own. Backoff and jitter can reduce pressure on a service that is struggling. They cannot tell you whether an earlier write already had its intended business effect.
That distinction belongs in the contract for every tool an agent can call. Repeating a product lookup is usually harmless. Repeating a customer creation, notification or purchase is not. A generic error handler cannot make that decision on the tool's behalf.
| Operation | What a timeout may mean | Retry control |
|---|---|---|
| Fetch product or account data | The read did not return | Backoff, jitter and a sensible attempt limit |
| Create or update a record | The write may already be committed | Stable idempotency key and stored status |
| Send a message or place an order | The provider may have accepted the request | Provider retry key or an integration-side ledger |
| Consume a webhook | The same event may arrive again | Atomic event claim before processing |
| Run slow downstream work | The sender may retry while work continues | Durable queue and an idempotent worker |
One business intention needs one stable key
An idempotency key identifies the intended action, not a particular HTTP attempt. If an agent decides to send one renewal reminder, every retry of that decision must use the same key. A genuinely new reminder gets a new one. Generating a fresh value inside the retry loop removes the protection because every attempt then looks like unrelated work.
Create the key with the first request, save it with the workflow state and reuse it without changing the material content of the operation. LINE's retry guidance provides a clear example. Supported messaging requests carry an X-Line-Retry-Key; a later request with a key that has already been accepted receives a conflict response.
LINE also draws an important boundary between acceptance and delivery. A retry key can establish that the provider accepted a request once. It cannot guarantee what happened after the request crossed the provider's boundary.
Treat the ledger as the source of truth
The key becomes useful when it is backed by a durable ledger. A practical record includes the key, operation type, request fingerprint, status, timestamps and either the completed result or a reference to it. Statuses such as received, processing, completed and failed are enough for many integrations, provided the transitions are explicit and queryable.
The claim must be atomic. A separate ācheck, then insertā sequence can let two workers through when they arrive at the same moment. Stripe's thin-event migration guide describes a sturdier pattern: store the idempotency key as the primary key of an idempotency table. Competing handlers attempt the same insert, and the database uniqueness constraint determines which one owns the work.
The Oracle MicroTx Workflows guide applies the same principle at workflow and task level. It covers returning an existing completed workflow instead of starting another, restoring stored task output instead of calling an agent again, and using fenced locking for supported tasks.
There is still a boundary to manage. A custom worker that updates a database, calls another API or publishes a message needs idempotency in the worker or the downstream service. Workflow-level protection cannot automatically make every external side effect safe.
Give the caller the first result again
When a retry finds a completed entry, the most useful response is usually the stored result or a stable identifier for the resource already created. If the entry is still processing, say that the original operation remains in progress. A bare āduplicateā error gives the agent too little information and may prompt it to invent another route to the same outcome.
The ledger should also protect the meaning of the key. If the same key returns with a different recipient, amount or payload, reject the mismatch. Silently attaching new instructions to an old result would make the record unreliable. One key must continue to mean one intention.
Acknowledge webhooks quickly and queue the work
Inbound events have the same problem from the other direction. Stripe's webhook documentation warns that an endpoint may receive the same event more than once. It recommends recording processed event IDs and using asynchronous queues so spikes do not overload synchronous handlers.
A sound handler verifies the event, claims its identity in durable storage, enqueues the work and responds promptly. The worker may also be retried, so a queue is not a substitute for idempotency. The queue separates receipt from execution; the ledger prevents the execution from producing the same side effect twice.
Test the awkward case: success followed by silence
The test that matters most is not simply āthe API returned an error.ā Simulate an API that commits an order or message and then drops the response. Let the agent or queue retry. The expected outcome is one side effect, one ledger entry and a result that can be replayed or recovered.
It is also worth testing two simultaneous requests with the same key, a changed payload under an existing key, a worker crash after the claim and a completed operation retried much later. Logs should connect the workflow ID, tool name, idempotency key, attempt number, provider request ID and final status. Without that chain, investigating a duplicate order becomes guesswork.
Begin with the tools that can cause damage
Start by inventorying tool calls and webhooks. Separate reads from reversible writes and higher-consequence actions. Note which providers support retry keys and where the integration needs its own ledger.
From there, a reusable PHP or Python gateway can issue stable keys, claim work atomically, return completed results and standardise logging across otherwise inconsistent APIs. Greg can help map those failure points, implement the safeguards and leave the team with an operating pattern it can reuse as more tools are added.
Retries should repeat delivery, not execution. Once the integration enforces that distinction, a timeout remains an operational nuisance instead of quietly becoming a second order.
Related on GrN.dk
- Cloudflare Service Keys Stop in September: Find Every Caller
- Cloudflare Workers can become shadow IT without an integration register
- When AI writes JSON, one bad field can break the workflow
Need help with this kind of work?
Make your AI actions safe to retry Get in touch with Greg.