Contact

Retrying AI agent actions without duplicate writes

Print of a busy lithographic workshop, with workers preparing stones and operating presses beneath tall windows.
Interieur van de lithografische drukkerij van Joseph-Rosé Lemercier, Anonymous, 1835–1849. Rijksmuseum, public domain.

An agent submits a purchase order. The supplier’s system creates it, but the connection drops before the reply arrives. The agent sees a timeout and tries again. Now the kitchen has ordered the same delivery twice.

The timeout tells us that the caller stopped waiting. It says nothing about whether the supplier accepted the order. Once an agent can write to another system, its tool needs a way to recognise an earlier attempt and recover the result.

An engraved scene of a clerk checking tokens against an open ledger, with a single parcel tied in orange on the desk and a messenger waiting beside him.
Illustration in the manner of an engraved plate, generated for this page.

Give the action an identity

Create an operation ID when the application accepts a proposed action. Store it with the exact arguments, the account it belongs to and any approval. Every attempt to execute that action uses the same ID. A new model response or a restarted worker must be able to find the existing operation.

This is the purpose of an idempotency key: repeated submissions of one intended action can return an equivalent result without repeating the side effect. The server must also reject a reused key with different arguments. Amazon’s explanation of idempotent APIs covers both the request identity and the distinction between a retry and a new request.

For an agent, keep that identity in application code. Asking the model to invent a fresh key on each tool call defeats the protection. Deriving it only from the arguments also causes trouble: two deliberately separate orders may contain exactly the same items.

Save the receipt

For a tool that writes to your own database, put the operation record, the business change and the saved result in one transaction. Enforce a unique constraint on the account and operation ID. Two workers can then race for the same operation without both creating a purchase order. A repeated call checks the stored arguments and returns the saved receipt.

worker tool orders send A17 create 842 reply lost retry A17 receipt: 842 use saved result one order
The first request creates order 842, but its reply is lost. Retrying operation A17 returns the saved receipt for order 842. It creates no second order.

An external API needs its own protection. If the supplier accepts idempotency keys, pass the stored operation ID through and follow its documented retention period. Keep your operation record for at least as long as your application allows retries, including manual recovery. An old job replayed after the supplier has forgotten the key may become a new order.

If the supplier supports lookup by a unique client reference, save that reference before sending. After a timeout, query for it. A missing search result is only enough to justify another write if the API guarantees that the lookup is complete and current, or enforces uniqueness on that reference. Otherwise, a delayed first request can still arrive after the lookup.

Keep uncertainty visible

A tool response needs to distinguish a rejected request from one whose outcome is unknown. Use explicit states in the operation record, and let the execution code decide which transitions are allowed.

State What the application knows Next step
Prepared The action and arguments are stored; no attempt has started Execute with the stored operation ID
In progress A worker has claimed the action Wait or inspect the existing attempt
Succeeded A receipt identifies the created record Return the saved result
Rejected The destination confirms it did not accept the action Correct the input or report the rejection
Unknown An attempt began, but its outcome cannot be confirmed Reconcile with the destination before another write

A worker crash can leave an operation marked in progress. Expiring the worker’s claim lets another worker investigate, but does not prove that the external write failed. Recovery still needs the supplier’s idempotency support or a reliable status lookup.

If neither exists, hold the action for a person to check. Show the account, arguments, time of the attempt and any external reference. A generic “try again” button would hide the very information that makes the decision possible.

Set a retry budget

Retry temporary failures only when repeating the operation is safe. Use increasing delays, add jitter so workers do not all retry together, and cap attempts and total elapsed time. Check what the SDK already retries before adding another retry loop around it. AWS’s guidance on limiting retries describes these controls and why retries at several layers can multiply the load.

An agent also needs a task-level limit. If a tool exhausts its budget, another model turn must not quietly create a new operation for the same action. Return the existing operation ID and state, then stop or route it to the review queue.

usual ceiling task 1 task 2 task 3 task 4 to a person
Illustrative task budgets: most tasks finish within their allowance. A task that keeps making calls reaches a fixed ceiling and goes to a person.

Changing the supplier, quantity or delivery address creates a different proposal. Require a new approval where the workflow calls for one, and resolve any uncertain earlier attempt before sending a replacement. Otherwise both versions can arrive.

Test the missing reply

The useful integration test accepts the order and then drops the response. Run the tool again with the original operation ID. Check that there is one order at the destination and that both attempts resolve to the same receipt. Then send two attempts concurrently and restart a worker between the external write and the local receipt save.

These tests complement the model’s evaluation set. The model may select the correct tool and supply every argument correctly while the integration still creates duplicate records.

Before granting an agent write access, choose one action and trace its recovery after a lost reply. Write down who can establish whether it happened, which identifier they use and when the system stops trying. That recovery path belongs in the first version of the tool.

More writing

  1. Designing a human review queue for AI output

    How to make AI review useful: show the source beside the proposal, route specific exceptions, plan reviewer capacity and measure errors after approval.

  2. Keeping a RAG index current

    Changed documents, deleted files and revoked access need different handling. How to keep a RAG index current without serving fragments of old revisions.

  3. Testing AI agents without touching production

    Run an agent against disposable records and simulated tools. Check the resulting state, inject failures and keep production credentials out of the test.