On this page
- The payment timed out. The money didn't wait.
- Is a timeout a failure?
- Five layers of one action
- One operation ID, kept across every retry
- Six mechanisms that aren't synonyms
- Declare how each action behaves under failure
- Idempotency keys: four rules
- "Unknown" is a real state
- What should an agent do after an uncertain outcome?
- A reversal is a new action, not a rollback
- Classify before you choose a retry policy
- Agents make retry ambiguity worse
- Retries must keep identity, policy and approval intact
- Common anti-patterns
- Ten rules for safe retries
- The result: resilience without duplicated reality
- Frequently asked questions
- How Frozion makes every routed call durable
- Sources
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.

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":
| Layer | What it is | Why it matters |
|---|---|---|
| Delivery | A message reaches a consumer | Can repeat, arrive late, reorder or vanish |
| Attempt | One technical try | One action may take several tries, each with its own ID |
| Business operation | One intended action, for one proposal and authority | Its ID must stay the same across every retry and resume |
| Effect | The real change outside your system | Can happen even when you never hear back |
| Confirmation | Authoritative proof of the result | A receipt, record version or reconciled state |

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
| Mechanism | Purpose | What it doesn't prove |
|---|---|---|
| Retry | Try a failed or uncertain call again | That it won't create another effect |
| De-duplication | Recognize a message seen before | That the business effect happened exactly once |
| Idempotency | Make repeats of one operation have one effect | Works only with a stable ID, known scope and retention |
| Reconciliation | Find out what really happened after uncertainty | Needs a way to query the target's state |
| Compensation | Take a new action to offset a completed one | Doesn't erase history; needs its own authority |
| Replay | Reconstruct or simulate a past run | Should 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:
| Dimension | What to declare | Why |
|---|---|---|
| Operation identity | How one authorized action is identified | Stops retries becoming new instructions |
| Effect class | Read-only, recomputable, state-setting, create, reversible, irreversible | Sets the default recovery posture |
| Target behavior | Idempotency support, key scope, retention, concurrency | Says what the target can actually guarantee |
| Timeout meaning | Can "failed before effect" be told apart from "unknown"? | Stops "no response" being read as "no effect" |
| Reconciliation | How to query the real status | The path out of uncertainty |
| Compensation | Which counter-action is allowed, and who authorizes it | Avoids pretending you can roll back the world |
| Confirmation | Which receipt or state proves the result | Links attempts to the outcome |
Idempotency keys: four rules
- Reuse the same key for every retry of the same action.
- Never reuse a key for a materially different action: a new amount, record, authority or payload.
- Know the target's retention window. If a workflow can resume after the target forgets the key, you need another recovery path.
- 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:
- Did any effect occur for this operation?
- If so, does it match the authorized proposal, amount, record and beneficiary?
- Is the state you found authoritative and complete?
- 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.

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 class | Examples | Default posture |
|---|---|---|
| Read-only | Search, read, validate | Retry within cost, rate and privacy limits |
| Recomputable | Pure calculation | Retry with versioned inputs |
| State-setting | Set a record to a declared state | Same operation ID; use preconditions; verify |
| Create or append | Payment, order, notification, ledger entry | Stable ID plus target de-duplication; reconcile after uncertainty |
| Reversible external | Shipment, reservation, access grant | Reconcile first; a reversal is its own action |
| Irreversible | Legal filing, disclosure, destructive change | No 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-pattern | Why it fails | What to do instead |
|---|---|---|
| Timeout means not executed | The effect may have committed | Mark it uncertain and reconcile |
| New idempotency key per retry | The target sees a new instruction | One stable operation ID |
| Payload hash as the operation ID | Identical payments can be distinct, and retries can differ | Derive the ID from business meaning |
| Queue de-dup equals business idempotency | Message identity isn't the real-world action | Separate event, attempt and operation IDs |
| Retry every timeout or 5xx | Some failures happen after the effect | Classify, then reconcile |
| Short de-dup retention | A long wait outlives the key | Match retention to the wait, or reconcile |
| Agent owns recovery | It may repeat or mutate a high-impact action | Keep retry control in the runtime |
| Replay calls live tools | Investigation repeats production effects | Replay as inspection or simulation |
Ten rules for safe retries
- Classify the effect before choosing retry behavior.
- Create one operation ID before the first attempt.
- Keep it across every retry, worker, queue, sub-workflow, provider, wait and resume.
- Bind it to the exact proposal, authority, policy, approval and terms.
- Treat a timeout as uncertain unless you can prove nothing happened.
- Reconcile before retrying an uncertain action.
- Use the target's idempotency support with known scope, retention and concurrency.
- Keep retry, de-duplication, reconciliation, compensation and replay separate.
- Record each action through to the target system's confirmation.
- 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.
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
- RFC 9110, HTTP Semantics, §9.2.2 Idempotent MethodsDon't automatically retry non-idempotent requests unless the semantics are known.
- IETF draft: The Idempotency-Key HTTP Header FieldDesign input, not a published RFC.
- CloudEvents SpecificationEvent source and ID semantics for de-duplication.
- RFC 9457, Problem Details for HTTP APIsTyped, machine-readable error outcomes.
- OWASP Top 10 for Agentic Applications (2026)ASI02 tool misuse, including runaway volume.
- NIST SP 800-53 Rev. 5, CP-10(2) Transaction RecoveryRollback and journaling apply inside a transactional system, not across external systems.
- W3C PROV-ONeutral provenance relationships.
More in Work that finishes
Retries & side effects 2
Durable & event-driven 1
Contracts & routing 3
APIs & integration 1
Other topics



