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.

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.

With and without an idempotency key
| Trigger | Without idempotency | With idempotency key |
|---|---|---|
| Timeout retry | Second charge | First result replayed |
| Re-plan repeats a step | Duplicate record | Deduped by key |
| Parallel branches | Two emails sent | One send, one dedup hit |
| Workflow restart | Action re-runs | Step result cached |
| Reused key, new params | Silent wrong action | Rejected 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.

