Missing Idempotency on Agent Calls
also known as Non-Idempotent Tool Calls, Duplicate Side-Effect Anti-Pattern
Anti-pattern: retry state-mutating agent tool calls without idempotency keys, so retries multiply real-world side effects.
Context
An agent calls external tools that have side effects (charge card, send email, create ticket, post message). The orchestrator retries on timeout or transient error. The tool wrapper does not enforce idempotency keys and the backing service treats each call as distinct.
Problem
A timeout that retried succeeds twice on the backend even though the client saw one logical operation. Cards get charged twice, emails get sent twice, duplicate tickets appear. The agent has no way to know which calls already committed. Worse: the retried calls often come from a different attempt loop and use different parameters (a regenerated email body), so deduplication after the fact requires fuzzy matching of natural language.
Forces
- Network and tool flakiness make retries unavoidable.
- LLMs regenerate the call arguments on retry — the same logical action looks different at the call site.
- Idempotency requires cooperation from the backing service; not all providers support keys.
Example
A billing agent calls `charge_card(amount, customer)` and the call times out. The orchestrator retries. The backend had actually committed the first charge; the retry commits a second. The customer sees two charges. Postmortem finds no idempotency key on the tool wrapper. Fix: derive `idempotency_key = sha(run_id + step_id + amount + customer)` at the planning layer.
Diagram
Solution
Therefore:
Generate idempotency keys at the planning layer (hash of plan-step id + arguments) and pass them through the tool wrapper. For backings without native idempotency, maintain a client-side dedupe table keyed by (run id, step id). Treat idempotency as a property of the *plan step* not the call, so regenerated arguments still collapse to the same key.
What this pattern forbids. No useful constraint; the missing constraint is that every state-mutating call carry a stable idempotency key tied to the logical plan step.
And the patterns that stand alongside it, or against it —
- complementsNaive Retry Without Backoff✕— Anti-pattern: retry failed model or tool calls immediately, amplifying load on systems that are already failing.
- alternative-toCircuit Breaker★★— Stop calling a failing dependency for a cooldown period after error rates exceed a threshold.
- complementsCompensating Action★★— Pair every irreversible-looking agent action with a compensating action that can undo or counteract it.
- complementsDurable Workflow Snapshot★— Capture workflow execution state as a snapshot in a pluggable storage provider so a paused run can resume across deployments, process restarts, and host crashes.
- complementsException Handling and Recovery★★— Catch and react to predictable failure modes (tool errors, rate limits, validation failures) with structured recovery paths.
- complementsRace Conditions on Shared Tool Resources✕— Anti-pattern: let concurrent agents perform read-modify-write on shared external resources without locking, producing silent data corruption.
- complementsHidden State Coupling✕— Anti-pattern: agent workflows read or write undeclared shared state (caches, env vars, process globals) instead of explicit inputs and outputs.
- complementsScatter-Gather Plus Saga★— Distribute tasks across worker agents and aggregate results while maintaining distributed-transaction semantics via compensating actions on partial failure.
Neighbourhood
Click any neighbour to follow the language. Scroll to zoom, drag to pan.