ADVISORYBrotli decoder flaw · check by handBrotli flaw · check by hand eMudhra certificate keys compromised · free checkeMudhra keys compromised · free check CL-61 · dfc2197 · Last deploy · 0 cookies · 0 third-party requestsLight-time to Deimos 11.4 min at
Sheet 7.02Series 7 · GuidesGuide · Deimos AI LLCInstrument Ratchet (ONCE)Terms Glossary

Guides · Deimos AI LLC

How to stop an AI agent from doing something twice.

The short answer

Give every real-world action a key that names it, write that key down somewhere durable before the action starts, and make every retry ask that record before it acts. Then a retried charge, email or deploy is started at most once. Decide in advance what happens when the agent crashes mid-action and nobody knows whether it ran, and put a ceiling on how many actions of a kind can start in an hour, because a loop of different actions is not a retry.

AWhy it happens

Why an AI agent charges a card twice.

An agent sends a charge. The connection drops before the answer comes back. Did it go through? The agent can’t tell, so it does what software has always done and tries again. The same thing happens when a supervisor re-runs a task that looked stuck, when two workers pick up the same job, when a process restarts after a crash with no memory of what it did a minute ago, and when the model simply plans the same step again.

None of these is a bug in the usual sense. Retrying is how reliable software copes with a network it can’t trust. The trouble is that a charge, an email, a deploy or a published package leaves your system. Once it has happened, trying again does not undo it; it does it again.

BThe steps

Make a retried action start at most once.

  1. Step A Give the action a key that names it.

    An idempotency key is a unique key attached to an action so that every repeat of it can be recognised as the same action. Derive it from the work itself, so a retry, a restart or a second worker arrives at the same key: Ratchet’s documentation gives refund:<order>:full as the pattern, and calls random or time-based keys broken (Ratchet docs ↗).

    Do

    • Make the key from what the action is: the customer, the order, the amount, the kind of action.
    • Create it before the agent acts, and carry it through every retry.

    Don’t

    • Mint a fresh key on each attempt; every retry then looks like a new action.
    • Reuse one key for different arguments. A good gate refuses that outright.
  2. Step B Pass the key to the vendor, where it accepts one.

    Many payment APIs accept an idempotency key on the request. Stripe, for example, saves the response to a request made with a key and returns it to any retry with the same key, and it rejects a retry whose parameters differ (Stripe, idempotent requests ↗). Use it. But a vendor’s key has limits: it is kept for a limited time, it is not saved when a request fails validation or collides with one already running, and many services, email among them, offer nothing like it.

    Do

    • Send the same key the agent uses, so your record and the vendor’s agree.

    Don’t

    • Treat the vendor’s key as the whole answer. It covers one API, for a while.
  3. Step C Record the key before you act, and make every attempt ask.

    Put a gate in front of the action. Before acting, the agent asks the gate about the key; the gate writes a durable record and hands back one answer. When the gate has never seen a key, the answer is execute. Every later ask, whether a retry, a restart or a racing worker, gets the recorded answer instead of a second attempt: duplicate with the stored result, or in flight while another worker holds it. That is what at most once means: the same real-world action is started no more than once, however many times it is retried, restarted or raced.

    Do

    • Ask before acting, report after: two calls around the action.
    • Keep the record outside the agent, where a restart can’t wipe it.

    Don’t

    • Rely on the agent’s memory or the model’s context to know what already ran.
  4. Step D Decide now what happens when nobody knows.

    The hard case is the crash in the gap: the agent acted, then died before it could report. Nobody on your side can know whether the charge went through. Report a failure when you are sure the action never left your system, and otherwise report nothing. In Ratchet the lease then runs out and the effect is marked indeterminate, a known unknown, and the policy you set for that kind of action decides what happens next: block it until someone checks with the vendor, allow a retry you chose on purpose, or check before retrying. Never a silent retry.

    Do

    • Block by default for money and for anything people receive.
    • Allow a retry where the vendor is idempotent, or a duplicate does no harm.

    Don’t

    • Guess. A system that promises more than at most once across that gap is quietly guessing or quietly retrying (Field Note 01).
  5. Step E Put a ceiling on loops of different actions.

    Keys stop the same action happening again. They can’t stop an agent stuck in a loop that makes a new, different charge each time, because every one of those charges has its own key. For that you need a limit on the kind of action: a surge ceiling on how many may start in an hour, and a spend limit per day or per run. In Ratchet the hourly ceiling is a surge breaker: when it trips, the gate denies the action or holds it for a person’s approval.

CThe run

A runaway loop, stopped at the gate.

In a published run against the live service, a simulated runaway agent fired 100 concurrent charge attempts against a surge ceiling of 20 per hour; 20 executed, 80 were denied at the gate, and all settled in 341 ms. Each attempt carried its own key, so idempotency could not have helped. The ceiling did.

Concurrent attempts100
Executed20
Denied at the gate80
All settled in341 ms

Source · Ratchet note: "What happens when step five fails" (section: Two: a loop that will not stop) · as of VERIFIED

DLimits

What this does not promise.

  • At most once is not “it happened”. A gated action can still fail at the vendor; the record tells you it was started once, and the report tells you how it ended.
  • Nobody can promise more for an effect that leaves your system. Between “I asked the vendor” and “I learned it was done” there is always a gap a crash can land in. The honest answer there is “unknown”, and the policy you chose.
  • A gate does not decide whether an action is right. For that, an action can wait for a person: Ratchet can answer approval required and hold it until someone says yes. See human approval in the checklist.
  • Keys protect nothing that goes around them. A second integration that charges the card without asking the gate is outside the record.
FQ&A

Debrief.

The questions this page answers, every answer open.

Q-01

Why did my AI agent charge a card twice?

Almost always a retry. The connection dropped before the answer came back, a supervisor re-ran the task, two workers took the same job, or the agent restarted after a crash with no memory of what it had done. Each behaved correctly; together they repeated an action that had already left your system.

Q-02

How do I prevent duplicate actions by an AI agent?

Give each action an idempotency key derived from the work, record the key durably before acting, and make every retry ask that record and get the recorded answer instead of a second attempt. Ratchet does this with one call before the action and one after, so a retried action is started at most once.

Q-03

What is idempotency for AI agents?

An action is idempotent when doing it again changes nothing. For agents, an idempotency key names an action so every repeat of it is recognised as the same action. The key must come from what the action is, not be minted fresh on each attempt, or every retry looks new. See idempotency key in the glossary.

Q-04

Isn’t the payment provider’s idempotency key enough?

Use it, and know its limits. A provider keeps a key for a limited time, may not save a request that failed validation or collided with another (Stripe’s documentation describes both), and many services, email among them, offer nothing like it. A record of your own covers every kind of action.

Q-05

What if the agent crashes in the middle of an action?

Then nobody can know whether it ran. Ratchet doesn’t guess: the effect is marked indeterminate, a known unknown, and the policy you chose for that kind of action decides: block it until someone checks with the vendor, or allow a retry you chose on purpose. Never a silent retry.

Q-06

Why “at most once” and not something stronger?

Because an effect that leaves your system has a gap between asking the vendor and learning it was done, and a crash can land in it. At most once, with an honest unknown, is the strongest promise that can be kept there. Field Note 01 explains.

Q-07

How do I stop a runaway agent loop?

Keys can’t, because each new action in a loop has its own key. A limit on the kind of action can: a surge ceiling per hour and a spend limit. In a published run against the live service, a simulated runaway agent fired 100 concurrent charge attempts against a surge ceiling of 20 per hour; 20 executed, 80 were denied at the gate, and all settled in 341 ms.