Blog
Saved0

Retries and idempotency for AI agents: how a timeout becomes a double payment

The payment timed out. The money had already moved. Why "no response" never means "nothing happened", and how to retry agent actions without doing them twice.

LinkedIn
On this page
  1. The payment timed out. The money didn't wait.
  2. Is a timeout a failure?
  3. Five layers of one action
  4. One operation ID, kept across every retry
  5. Six mechanisms that aren't synonyms
  6. Declare how each action behaves under failure
  7. Idempotency keys: four rules
  8. "Unknown" is a real state
  9. What should an agent do after an uncertain outcome?
  10. A reversal is a new action, not a rollback
  11. Classify before you choose a retry policy
  12. Agents make retry ambiguity worse
  13. Retries must keep identity, policy and approval intact
  14. Common anti-patterns
  15. Ten rules for safe retries
  16. The result: resilience without duplicated reality
  17. Frequently asked questions
  18. How Frozion makes every routed call durable
  19. Sources
The short answer

A timeout means you don't know what happened, not that nothing happened. Give every consequential agent action one stable operation ID before the first attempt, and reuse it on every retry. When the outcome is uncertain, ask the target system what happened before trying again. A reversal is a new, governed action, never a rollback.

The payment timed out. The money didn't wait.

An agent reviews an insurance claim (the file, medical evidence, policy terms and adjuster notes) and proposes a $96,000 settlement. Checks confirm the policy limit and currency. Policy requires senior approval, and an authorized adjuster approves the exact proposal.

The workflow calls the payment service. The service commits the transfer and creates a receipt. Then the network drops before the response arrives. To the caller, the request timed out. To the payment service, the money has moved.

A conventional retry policy kicks in. It builds a new request with a new idempotency key. The payment service sees a new instruction and pays another $96,000. Reconciliation finds two payments days later.

Sequence diagram of a payment that commits, loses its response, and is paid again by a retry with a new key
A lost response plus a new key equals a second payment.

Every part behaved as designed. The failure was treating "no response received" as "no effect occurred."

Is a timeout a failure?

No. A timeout is evidence of uncertainty, not of non-execution. The request may never have left, may have been rejected, may have failed midway, may have committed and lost its response, or may still be running. One symptom covers several very different business states.

HTTP already draws this line: clients shouldn't automatically retry non-idempotent requests unless they know the operation is safe to repeat or can show the original wasn't applied. Agent workflows need the same discipline at the business level, where one action spans proposals, approvals, records and long waits.

Five layers of one action

Retry bugs are easier to reason about once you separate five layers that usually get collapsed into "an API call":

LayerWhat it isWhy it matters
DeliveryA message reaches a consumerCan repeat, arrive late, reorder or vanish
AttemptOne technical tryOne action may take several tries, each with its own ID
Business operationOne intended action, for one proposal and authorityIts ID must stay the same across every retry and resume
EffectThe real change outside your systemCan happen even when you never hear back
ConfirmationAuthoritative proof of the resultA receipt, record version or reconciled state
Five stacked layers of one consequential action, from delivery to confirmation
Five layers that usually get collapsed into 'an API call'.

One operation ID, kept across every retry

An attempt ID answers which try is this? An operation ID answers which single business intention are all these tries for? Create it before the first attempt, and bind it to what was authorized: the tenant, record and action; the proposal and its fingerprint; the amount and beneficiary; the authority, policy decision and approval.

Each retry gets a new attempt ID. The operation ID never changes, because nobody authorized a second payment.

A payload hash isn't a substitute. Two legitimate payments can have the same amount and beneficiary for different invoices. A retry can differ in a timestamp and still be the same operation. The ID has to come from business meaning, not byte equality.

Six mechanisms that aren't synonyms

MechanismPurposeWhat it doesn't prove
RetryTry a failed or uncertain call againThat it won't create another effect
De-duplicationRecognize a message seen beforeThat the business effect happened exactly once
IdempotencyMake repeats of one operation have one effectWorks only with a stable ID, known scope and retention
ReconciliationFind out what really happened after uncertaintyNeeds a way to query the target's state
CompensationTake a new action to offset a completed oneDoesn't erase history; needs its own authority
ReplayReconstruct or simulate a past runShould never repeat production effects by default

Event standards such as CloudEvents let a re-sent event keep its ID so consumers can de-duplicate it. That helps at the event level. It doesn't give a payment or contract the stable business identity it needs across many events and attempts.

Declare how each action behaves under failure

A schema describes what a payment request looks like. It doesn't say what to do when the response disappears after the payment commits. Each action that changes something outside your system needs a declared side-effect contract:

DimensionWhat to declareWhy
Operation identityHow one authorized action is identifiedStops retries becoming new instructions
Effect classRead-only, recomputable, state-setting, create, reversible, irreversibleSets the default recovery posture
Target behaviorIdempotency support, key scope, retention, concurrencySays what the target can actually guarantee
Timeout meaningCan "failed before effect" be told apart from "unknown"?Stops "no response" being read as "no effect"
ReconciliationHow to query the real statusThe path out of uncertainty
CompensationWhich counter-action is allowed, and who authorizes itAvoids pretending you can roll back the world
ConfirmationWhich receipt or state proves the resultLinks attempts to the outcome

Idempotency keys: four rules

  1. Reuse the same key for every retry of the same action.
  2. Never reuse a key for a materially different action: a new amount, record, authority or payload.
  3. Know the target's retention window. If a workflow can resume after the target forgets the key, you need another recovery path.
  4. Coordinate concurrent attempts. Two workers with the same key can still race if the target doesn't define concurrent behavior.

Record the request fingerprint alongside the operation ID, and reject a different request under the same ID. Don't generate a new ID just because a different worker, model or provider picked up the work.

"Unknown" is a real state

A timeout after a write shouldn't jump straight to failed. The honest state is uncertain, and it stops blind retries. The lifecycle runs from proposed to authorized to in flight, and then ends in one of three outcomes that aren't interchangeable:

  • Committed: a receipt or reconciled state proves the effect.
  • Failed before effect: you can show nothing was applied, so a retry may proceed.
  • Uncertain: you can't yet prove either, so don't invent certainty.

What should an agent do after an uncertain outcome?

Reconcile before retrying: ask the target system what happened to that operation ID, then recover the receipt, retry with the same ID, or escalate. When an outcome is uncertain, find out what happened before doing anything else. Query the target by operation ID or idempotency key, match a later webhook, check the ledger or record version. Then answer four questions:

  1. Did any effect occur for this operation?
  2. If so, does it match the authorized proposal, amount, record and beneficiary?
  3. Is the state you found authoritative and complete?
  4. What may happen next: recover the receipt, retry with the same ID, stop, escalate or compensate?

If the target confirms the original effect, recover the receipt; don't retry. If it proves nothing happened, retry with the same operation ID. If it can't say, send it to a person.

Decision flow for an uncertain outcome: query the target, then recover, retry with the same ID, or escalate
Reconcile first, then decide.

A reversal is a new action, not a rollback

Once an external effect is committed, you can't rewind the world. You can take another action: refund, revoke access, send a correction, post a reversing entry, cancel a shipment. That action has consequences of its own and can fail too. So it needs its own operation ID, a fresh authority and policy check, human approval where the stakes call for it, a link to the original operation, and its own record. Calling it "rollback" hides the fact that you've done something new.

Classify before you choose a retry policy

One global retry policy is unsafe because actions carry different consequences:

Effect classExamplesDefault posture
Read-onlySearch, read, validateRetry within cost, rate and privacy limits
RecomputablePure calculationRetry with versioned inputs
State-settingSet a record to a declared stateSame operation ID; use preconditions; verify
Create or appendPayment, order, notification, ledger entryStable ID plus target de-duplication; reconcile after uncertainty
Reversible externalShipment, reservation, access grantReconcile first; a reversal is its own action
IrreversibleLegal filing, disclosure, destructive changeNo automatic retry; strong confirmation; often a person

The class isn't purely technical. The same API call can be low-stakes in a sandbox and high-stakes in production, depending on amount, sensitivity and reversibility.

Agents make retry ambiguity worse

After a timeout, an agent focused on finishing its goal may try again, switch provider, call a similar tool, replan through another path, reword the request or mint a new ID. Each choice can look reasonable for the goal while breaking the rules for the action. OWASP's agentic risks include exactly this kind of tool misuse, including runaway volume.

So the agent shouldn't own the operation ID. Its tool request arrives as a proposal. The runtime assigns or validates the ID, checks policy and approval, applies the side-effect contract, and decides whether to execute, reconcile or escalate.

Retries must keep identity, policy and approval intact

  • Events: a duplicate event may trigger another attempt; it must not create another operation.
  • Sub-workflows: parent and child must agree on who owns the ID, retries and confirmation.
  • Versions: a paused operation stays bound to the version and proposal it started under.
  • Identity: every retry keeps or rechecks the authority it runs under. (See AI agent identity.)
  • Approval: a retry of the same operation can rely on the approval; a changed one can't. (See human-in-the-loop approval.)

Common anti-patterns

Anti-patternWhy it failsWhat to do instead
Timeout means not executedThe effect may have committedMark it uncertain and reconcile
New idempotency key per retryThe target sees a new instructionOne stable operation ID
Payload hash as the operation IDIdentical payments can be distinct, and retries can differDerive the ID from business meaning
Queue de-dup equals business idempotencyMessage identity isn't the real-world actionSeparate event, attempt and operation IDs
Retry every timeout or 5xxSome failures happen after the effectClassify, then reconcile
Short de-dup retentionA long wait outlives the keyMatch retention to the wait, or reconcile
Agent owns recoveryIt may repeat or mutate a high-impact actionKeep retry control in the runtime
Replay calls live toolsInvestigation repeats production effectsReplay as inspection or simulation

Ten rules for safe retries

  1. Classify the effect before choosing retry behavior.
  2. Create one operation ID before the first attempt.
  3. Keep it across every retry, worker, queue, sub-workflow, provider, wait and resume.
  4. Bind it to the exact proposal, authority, policy, approval and terms.
  5. Treat a timeout as uncertain unless you can prove nothing happened.
  6. Reconcile before retrying an uncertain action.
  7. Use the target's idempotency support with known scope, retention and concurrency.
  8. Keep retry, de-duplication, reconciliation, compensation and replay separate.
  9. Record each action through to the target system's confirmation.
  10. Let agents propose recovery; let the runtime decide.

The result: resilience without duplicated reality

Retries aren't the enemy. They're how distributed systems survive partial failure. The mistake is letting a technical retry silently become a second business intention. One stable operation ID, an honest "uncertain" state, reconciliation before retry and governed reversals don't promise exactly-once behavior across arbitrary systems. They give you something more defensible: one declared intention, controlled attempts, explicit uncertainty and proof of what actually happened.


Frequently asked questions

What is idempotency for AI agent tool calls?

It means repeating the same intended action has the same effect as doing it once. For agents, that requires one stable operation ID per business action, reused across every retry, worker and resume, so the target system can recognize a repeat.

Should an agent retry after a timeout?

Not blindly. A timeout after a write is ambiguous: the action may already have happened. Reconcile first by asking the target system about that operation ID, then retry with the same ID only if it confirms nothing happened.

Can the agent decide how to recover from a failed action?

The agent can propose recovery. The runtime decides whether another attempt is still the same authorized action and whether retrying is safe, because an agent pursuing its goal may reroute, rephrase or re-issue a high-impact call.

How does this work for agents we already run?

Route the agent's write calls through a governed runtime. Each routed call gets a stable operation ID, an uncertain result is reconciled before any retry, and the outcome is confirmed by your system of record and recorded.

With Frozion

How Frozion makes every routed call durable

Frozion treats an API timeout as an unresolved outcome, not a failure. Retries are yours to control, and uncertain writes are reconciled first, so a retry never becomes a second payment. Work survives a crash or restart of Frozion itself, deploys don't interrupt running cases, and every wait has a timer, deadline and fallback. For agents you already run, every routed tool call is durable, reconciled and on the record.


Sources

  1. RFC 9110, HTTP Semantics, §9.2.2 Idempotent MethodsDon't automatically retry non-idempotent requests unless the semantics are known.
  2. IETF draft: The Idempotency-Key HTTP Header FieldDesign input, not a published RFC.
  3. CloudEvents SpecificationEvent source and ID semantics for de-duplication.
  4. RFC 9457, Problem Details for HTTP APIsTyped, machine-readable error outcomes.
  5. OWASP Top 10 for Agentic Applications (2026)ASI02 tool misuse, including runaway volume.
  6. NIST SP 800-53 Rev. 5, CP-10(2) Transaction RecoveryRollback and journaling apply inside a transactional system, not across external systems.
  7. W3C PROV-ONeutral provenance relationships.
More in Work that finishes

Work that finishes

Retries & side effects 2
Durable & event-driven 1
Contracts & routing 3
APIs & integration 1

Other topics

Pick up where you left off: