A human calling an API usually calls it once, notices a timeout, and decides for themselves whether to retry. An agent doesn't have that judgment loop by default — a tool call that times out, returns an ambiguous error, or gets interrupted by a context-window reset gets retried automatically, because "try again" is the agent's whole strategy for anything that didn't obviously succeed. That's fine for a read. It's not fine for "create the invoice," "send the email," or "delete the branch" if a repeat call means a second invoice, a duplicate email, or a branch deleted twice for reasons that no longer apply the second time. The fix isn't teaching the agent to be more careful about retries — retries are the correct behavior for the agent to have. The fix is making the tool itself safe to call more than once for the same intent.
The pattern that payment APIs settled on decades ago is the right one here too: the caller generates a key that represents one intent ("charge this order"), and the server deduplicates on that key regardless of how many times the request physically arrives. An agent-facing tool that creates or mutates anything should take an idempotency key as a required argument — not optional, not inferred — because an optional key is a key the agent will forget to pass under exactly the retry conditions where it matters most. The server, not the agent's prompt, is what has to guarantee the second call with the same key is a no-op that returns the original result.
If a tool has a mode that reports what it would do without doing it, an agent operating with any autonomy should default to that mode when the intent is ambiguous, and a well-designed tool makes the dry-run output detailed enough that "would delete 4,000 rows" is visible before the agent decides whether that number matches its expectation. Tools without a dry-run mode push that judgment entirely onto the agent's ability to reason correctly about scale and consequence from the parameters alone — which works until a plural gets misread as a singular, or a filter that should have narrowed a query silently didn't.
A tool that can delete one row and a tool that can delete every row an unbounded filter matches are different tools even if they share an implementation, because the cost of the agent getting the filter wrong is not the same. Where possible, split the dangerous general case into a narrower default (require an explicit ID, or an explicit small limit) and a separate, more deliberately-gated call for anything unbounded — a hard cap on rows affected per call, a required confirmation parameter that has to be the literal count the caller expects, or a soft-delete that's reversible instead of a hard delete that isn't. None of this is about mistrust of the agent specifically; it's the same reasoning you'd apply to any caller you can't fully verify before the call happens, which describes most real callers, human or not.
| Ambiguous response | What lets a retry be judged correctly |
|---|---|
| Bare success/fail boolean | Return the created resource's ID, so a retry can check "does this already exist" first |
| Timeout with no side-effect information | Make the operation itself idempotent, so timeout-then-retry is always safe regardless of what happened server-side |
| Generic error string on partial failure | Report exactly what completed and what didn't, so a retry (or the agent) can target only the remainder |
| Silent no-op on an already-applied idempotency key | Return the original result with a flag noting it was a duplicate, not a fresh success, so the agent's own accounting stays correct |
Deciding what a headless agent is allowed to run bounds risk by restricting which tools are callable at all; idempotent design bounds risk in the tools you do expose, for the calls you already decided were fine to make. The two are complementary, not substitutes — a tightly-scoped permission set still doesn't help if the one tool it allows isn't safe to call twice. And a tool that reports back clearly what it did is also what makes reviewing an agent's actions after the fact tractable, for the same reason a clear diff is easier to review than a vague commit message — and it's exactly the data an audit trail needs captured, since a tool's own report of what it did is often the only record once the session that called it is gone. None of this makes a wrong call take-back-able, though — a tool can be safe to call twice and still have no path back from the one call that was a mistake, which is a separate property covered in designing reversible actions.
The test for whether a tool is ready to hand to an agent isn't "does it do the right thing when called correctly" — that's the easy case. It's "what happens when it's called twice with the same intent, or interrupted halfway." If the answer requires the agent to have gotten something right on the first call to avoid a bad outcome on the second, the tool isn't done yet.
Idempotency makes a retry safe. It doesn't decide when or how fast to retry — see rate limits and backoff for agent tool calls for the pacing half of this problem.
Idempotent calls are the building block, not the whole answer, for a task made of dozens of them — see checkpointing long-running agent tasks for how to know which calls already happened after the process itself gets killed mid-run.
Idempotency is about the effect of calling a tool twice being safe. It says nothing about whether the second call should happen at all — see caching tool calls for agents for when skipping the repeat call is the better move, and what it takes to do that without serving a stale answer.
Idempotency also says nothing about whether two different calls are safe to run at the same instant — see parallel tool calls for agents for when a batch of concurrent calls races on shared state that no single call's retry contract covers.