Idempotency for AI agents: why the second click is the risky one
an operation is idempotent if doing it twice leaves the world in the same state as doing it once. setting a light switch to “on” is idempotent. pressing a toggle button is not. the first press is rarely the problem. the second one, the retry, is where an agent quietly charges a card twice or sends the same email to a customer three times.
i run agents in Singapore for my own sites, and the bugs that cost me real time were almost never “the agent did the wrong thing”. they were “the agent did the right thing twice”. if you are new to this, this article is the short version of what idempotency means, how it works in practice, and what to check before you let an agent touch anything with side effects.
what it is
idempotency is a property of an operation, not of a system. an operation is idempotent when repeating it with the same input produces the same end state as running it once. the response may differ (the second call might say “already done”), but the effect on the world does not.
some plain examples:
- setting a field: “set the order status to shipped” is idempotent. run it ten times and the status is still shipped.
- adding to a counter: “add 1 to the stock count” is not idempotent. run it ten times and you have added ten.
- creating a record: “create an invoice for order 4411” is not idempotent unless something stops the second invoice from being made.
- deleting a record: “delete invoice 88” is idempotent in effect, because after the first call it is gone and later calls change nothing, even if they return a 404.
the HTTP spec draws the same line. RFC 9110, section 9.2.2, says PUT and DELETE are idempotent methods, as are the safe methods like GET. POST is not on that list (RFC 9110, idempotent methods). that is why “submit a payment” is almost always a POST and almost always needs extra protection.
the reason agents make this matter more than ordinary code is simple. a normal script runs once and you read its logs. an agent loop decides, acts, observes, and decides again, often with a retry layer, a model that can misread a timeout, and a checkpointing system that can replay steps. every one of those is a way to run the same action twice without anyone typing it twice.
how it works
there are two ways to get idempotency. you either pick operations that are naturally idempotent, or you add a mechanism that makes a non-idempotent operation behave like one.
the common mechanism is the idempotency key. the caller generates a unique value for one logical action and sends it with the request. the server remembers the key and the result. if the same key arrives again, the server returns the saved result instead of running the action again.
Stripe’s API is the best-known example. you send an Idempotency-Key header on a POST request, and if the connection drops and you retry with the same key, Stripe returns the original result rather than creating a second charge. Stripe’s docs also note that GET and DELETE requests are idempotent by definition, so keys are for POST. as of October 2026 the docs describe keys being pruned after at least 24 hours and a length limit of 255 characters, but check Stripe’s idempotent requests page for the current numbers before you build around them.
there is also a standards effort to make this a common header. the IETF HTTP API working group has an Internet-Draft called “The Idempotency-Key HTTP Header Field” (datatracker page). it was still a draft when i last checked, so treat it as a direction rather than a rule, and note that not every API honours it.
the flow looks like this:
- the agent decides to do something with a side effect, say “refund order 4411”.
- it generates a key tied to that decision, for example a hash of the order id plus the action name, or a UUID stored before the call is made.
- it sends the request with the key.
- if the call times out, the agent retries with the same key, not a new one.
- the server either runs the action once or returns the stored result.
the part people get wrong is where the key comes from. if the agent generates a fresh random key on every retry, you have the header but none of the protection. the key has to be created once per intended action and persisted before the first attempt, so a crash and restart reuses it.
when the other side has no idempotency support, which is true for most websites a browser agent touches, you build the check yourself. read the state first (“does this order already show as refunded?”), act only if it does not, then read again to confirm. it is slower and still has a gap between the check and the act, but it beats blind retries.
why it matters
four situations where i have seen this decide whether an agent is safe to leave running.
- retries after timeouts: a request that times out may have succeeded. the agent sees an error, retries, and now there are two. this is the classic double charge. without a key or a state check, “retry on failure” is a loop that multiplies side effects.
- resuming from a checkpoint: frameworks like LangGraph save state so a run can pick up after a crash. that is good, but a step that crashed halfway through may re-execute from its start on resume. if that step sent a message before dying, the message goes out again. i wrote about the setup side of this in why a LangGraph run can vanish with no error if it crashes before its first checkpoint, and the same checkpointing that rescues a run is what makes replay-safe steps necessary.
- model-driven repetition: a language model does not remember that it already clicked “submit” unless that is in its context. after a slow page load it may look at the screen, see the form still there, and click again. this is the “second click” in the title. browser agents are especially exposed because a page that looks unchanged does not prove the action failed.
- parallel runs and fleets: if you run many sessions at once, two workers can pick up the same task. a shared queue with no claim step means both do it. idempotency keys at the action level are your last line of defence against that.
there is also a cost angle. duplicate actions are not just risky, they burn model calls and browser time. cost engineering for computer use agents covers the spending side, and avoiding repeated work is one of the cheaper wins there.
common misconceptions
“idempotent means the agent only ever runs once.” no. it means running again is safe. you still expect retries. the whole point is that you can retry freely because the result does not change.
“retries are safe because the request failed.” a failed response is not the same as a failed action. a timeout tells you the caller did not hear back, not that the server did nothing. treat any error after the request was sent as “unknown outcome” and check state or reuse the key.
“GET is safe so my agent is fine.” reading is safe in the HTTP sense, but a lot of real websites break the rule. a “GET” link that marks a message as read, confirms a subscription or triggers an export has side effects. the spec says safe methods should not have them, and the web does not always comply. look at what the page actually does.
“my agent framework handles this for me.” most do not. a framework can retry a node or replay from a checkpoint, but it cannot know that your node sent an email. making a tool idempotent is the tool author’s job. the framework only gives you the hook, such as a stable run id you can fold into your key.
“a unique key per request is enough.” one key per request is the mistake from earlier. you want one key per intended action, reused across every retry of that action. generate it before the first attempt and store it somewhere that survives a crash.
where to go from here
if this made sense, these are the next things worth learning, roughly in the order i would tackle them.
- tracing what your agent actually did: you cannot fix duplicate actions you cannot see. best tools for tracing and debugging AI agents compares the options, and a trace that shows every tool call with its arguments is how you spot a repeated click.
- browser session handling: whether your agent reuses a logged-in session or starts fresh changes what state it can read before acting. see attaching to an existing Chrome session vs launching a fresh browser for your agent.
- checkpointing and replay: read how your framework resumes a run, and list which of your steps have side effects. the LangGraph article linked above is a good place to start if that is your stack.
- the blog index: the rest of the agent explainers and how-tos are on the blog index, and i add new ones most days.
a simple rule to take away: before you give an agent any tool that spends money, sends a message or changes someone else’s data, ask what happens if it runs twice. if the answer is “something bad”, add a key or a state check first. that one question has saved me more cleanup than any model upgrade.
Written by Xavier Fok
disclosure: this article may contain affiliate links. if you buy through them we may earn a commission at no extra cost to you. verdicts are independent of payouts. last reviewed by Xavier Fok on 2026-10-17.