Deep Dive Usecase

An agent calls a payment tool. The request goes out, the network stalls, and the call times out. The agent does what we told it to do with failures: it tries again. Now the customer may have been charged twice, and nobody did anything wrong except retry.

The trouble is that a timeout doesn't say the call failed. It says you don't know. The charge may have gone through and only the reply got lost. Unless the tool is built otherwise, a retry is a second, independent request, and the payment service has no reason to treat it as the same one.

The fix is to design the effect so that doing it twice leaves the world the same as doing it once. The usual mechanism is an idempotency key. The caller creates one key for each intended payment, before the first attempt, and sends that same key with every attempt. The service stores the key next to the result. When a request arrives with a key it has already seen, it returns the stored result and charges nothing.

Where the key comes from matters more than it looks. If the model invents a key inside each call, a retry that follows a fresh round of reasoning can produce a different key, and the protection is gone. Derive the key from the task instead, such as the order ID plus the purpose, or have the tool wrapper create it once and reuse it across attempts. Keep the model out of that decision.

The same idea reaches past payments. "Set the order status to shipped" is safe to repeat. "Add one to the shipped counter" is not. Where you can, give agents tools that state the desired end state ("ensure a refund of 20 exists for order 8123") rather than tools that describe a step ("issue a refund"). A state-style tool absorbs retries by its nature, and a step-style tool needs a key to do the same.

To check your own tools, take each one that changes something and ask: if this ran twice in a row, what would be different afterward? Read the tool's description or code, picture the call repeated, and look for anything that would pile up, such as money moved, rows added or messages sent. If the second run changes nothing, the tool is safe to retry. If you build nothing yourself, ask the same question about any "submit" button that has ever made you wonder whether your first click worked.

Tools

  • dataoad - An alpha Python library for side-effecting agent actions. It scopes idempotency claims by action name and key, records ambiguous outcomes as UNKNOWN, and by default does not auto-retry them. The README says it is not production-ready. (PyPI)

  • deduplex - A pre-release Python idempotency guard for agent tool calls. It derives a key from the call arguments plus a namespace, returns the stored result on repeats, and forwards the key to providers that accept one. Memory, Redis or hosted stores are available. (PyPI)

Try this

Think of the last time you clicked a "submit" or "pay" button and the page hung. Ask what would have happened if your click had gone through twice: would a second charge, row or message have piled up, or would the second one change nothing? Note which it was, and whether the service gave any sign it recognised your repeat. If you build agent tools, do the same with one tool that changes something.

Worth your time

Remote Roles