No idempotency key, no safe retry for your agent

Agents retry, re-plan, and run steps in parallel. Without an idempotency key the API honors, one logical action becomes two: a duplicate charge, a double email, a record written twice.

B

Balagei G Nagarajan

5 MIN READ


Even at temperature zero, a frontier model returns dozens of different answers to the same prompt.Send one identical request a thousand times and you get back dozens of distinct outputs (Thinking Machines, Defeating Nondeterminism in LLM Inference, 2025). that's exactly why your agent re-issues and re-plans the same action, and without an idempotency key every one of those retries is a second charge.

A single glowing action splitting into two identical ghosted copies as it passes through a retry, one labeled intended and the other an unintended duplicate
Two parallel branches both decide to send the confirmation email.
— from “No idempotency key, no safe retry for your agent”

Key facts.

  • Stripe's idempotency saves the status code and body of the first request for a given key and returns that same result on any retry, so a timed-out request can be retried with the same key and no second charge is created; the saved result is retained for about 24 hours and incoming parameters are compared to the original (Stripe API, idempotent requests).
  • Frontier models are non-deterministic even at temperature zero, so an agent re-issues the same action across retries and re-plans, the exact condition under which a non-idempotent call duplicates (Thinking Machines, Defeating Nondeterminism in LLM Inference, 2025).
  • The mechanism is generic: an idempotency key plus a server-side dedup store turns execute again into return the prior result, which is the only retry that's safe for a mutating action.

Why are agent retries so dangerous?

Because retries in an agent are neither rare nor careful. A tool call times out, so the runtime retries it. A plan partially fails, so the agent re-plans and re-issues a step it already ran. A durable executor restarts a workflow from a checkpoint. Two parallel branches both decide to send the confirmation email. In ordinary software you reason about exactly-once on purpose. An agent's control flow is probabilistic, so the same logical action can be attempted several times without anyone writing a loop. If the underlying tool is a mutating call, create, charge, send, update, every attempt is a real side effect. The intelligence is fine. The plumbing duplicates.

What an idempotency key actually does

The client generates a unique key per logical action and sends it with the call. The server records the key and the first result. Any later request with the same key gets the stored result back, no re-execution. Stripe does exactly this: saves the first response, replays it on retries including failures, retains it about 24 hours, and rejects a reused key with changed parameters so you can't slip a different action under an old key (Stripe). The key has to be stable across retries, it belongs in the tool contract, generated once where the action is decided, passed through every attempt.

# One key per logical action, generated upstream
POST /v1/charges
Idempotency-Key: 7b2f-...-c91
{ "amount": 4900, "customer": "cus_123" }

# Timeout, agent retries with the SAME key: # server replays the first result, no second charge.

Making agent actions idempotent end to end

Every mutating tool gets an idempotency key required in its schema. Generate the key upstream, where the action is decided, pass the same key through every retry. Honor it server-side with a dedup table so the API enforces exactly-once even if the client misbehaves. For multi-step work, run on a durable execution framework that caches each step's result, a restart replays completed steps, doesn't re-run them. For actions that can't be made idempotent, gate behind an explicit confirm step and a no-retry rule. What you want: every retry is safe because the system can tell it's a retry.

A swimlane diagram with three lanes, agent, runtime, payment API; on the left a timeout triggers a retry that produces two charges; on the right the same flow carries an idempotency key and the API returns the first result, producing one charge

With and without an idempotency key

TriggerWithout idempotencyWith idempotency key
Timeout retrySecond chargeFirst result replayed
Re-plan repeats a stepDuplicate recordDeduped by key
Parallel branchesTwo emails sentOne send, one dedup hit
Workflow restartAction re-runsStep result cached
Reused key, new paramsSilent wrong actionRejected by the server

No idempotency key, a retry fires twice, a duplicate charge; a newer model retries the same way. (thinkingmachines.ai)

The pattern is that an agent will attempt the same action more than once as a normal part of how it works, so safety has to live in the contract, not in hoping it retries gently. A stable idempotency key the server honors makes every retry idempotent, and a durable runtime makes every step exactly-once. Treating the agent-to-tool boundary as a place you verify and dedupe, rather than trust, is what VibeModel builds as the Pattern Intelligence Layer.

Frequently asked questions

Where should the idempotency key come from?
Generate it once where the action is decided, upstream of any retry, and pass the same key through every attempt. A key generated inside the retry loop changes on each try and defeats the purpose.

What if the API has no idempotency support?
Add a dedup layer in front of it keyed on the action, or gate the call behind an explicit confirm and a do-not-retry rule. Never auto-retry a mutating call the server can't dedupe.

Do durable workflow engines solve this?
They make completed steps replay instead of re-run, which covers restarts. You still need server-honored idempotency keys for the external mutating calls themselves.


Share this post

Join the discussion

Have a take, a war story, or a question? Sign in with GitHub to comment and react. Comments are powered by GitHub Discussions, ad-free and yours to moderate.

Continue Reading

Find where your agent breaks, before you build it

Faultmap maps where your agent will fail from the goal and your data, then hands you the first test suite it has to pass.