XIV · Anti-PatternsAnti-pattern

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 BackoffAnti-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 SnapshotCapture 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 ResourcesAnti-pattern: let concurrent agents perform read-modify-write on shared external resources without locking, producing silent data corruption.
  • complementsHidden State CouplingAnti-pattern: agent workflows read or write undeclared shared state (caches, env vars, process globals) instead of explicit inputs and outputs.
  • complementsScatter-Gather Plus SagaDistribute 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.