A connection drops after the server saves an order. The AI agent sees a timeout and tries again. Northwind now has two orders ready to ship for one request.
When you build write tools for agents, treat a timeout as an unknown outcome and make the retry safe. Accept an idempotency key, so a retry with the same key returns the original result within the documented window instead of writing again. Return an explicit outcome that tells the agent whether to continue, look up the record or stop. Without an idempotency key, the agent checks what happened before trying again; if it cannot establish the outcome, it stops and escalates.
An idempotency key identifies one intended write across multiple attempts. The server recognises the key and prevents another business effect. This protection belongs in the tool, whether the agent calls it through a CLI, HTTP API or MCP server.
These illustrations use synthetic Northwind example data. The first retry creates a duplicate; the second returns the order already saved.
❌ Figure: Bad example - Retrying an uncertain write without duplicate protection leaves two orders ready to ship
✅ Figure: Good example - Retrying with the original request and key returns order 11078 instead of creating another
Build that behaviour into the server:
Stripe's idempotent requests show this in practice: API v1 stores the first response after execution begins and rejects changed parameters. Keys may be pruned after at least 24 hours. Document your own tool's guarantees and limits.
Return the saved ID and verified data, rather than echoing the request as proof of success. For deletion, return a receipt identifying the deleted record. Use explicit outcomes such as these application-defined states:
| Outcome | What the agent does next |
|---|---|
saved | Use the confirmed ID and data |
saved-unverified | The write succeeded but its follow-up read failed; look up the known ID, without creating again |
unknown | Retry with the same key within its supported window, or check the outcome |
pending | Poll the operation ID until completion |
failed | The write was rejected or rolled back without business side effects; fix the cause before retrying |
An HTTP error alone does not prove failure. For bulk writes, return per-item outcomes so successful items are not repeated. Use structured results and detailed error messages.
For destructive, sensitive or large bulk changes, enforce trusted approval on the server:
--yes flag is only an acknowledgementFor an email action that requires confirmation, preview the sending account, action, subject, full To/Cc/Bcc addresses, outgoing body including quoted history, and every attachment's filename and contents summary, or None. Identify external recipients; expand groups where possible and stop if membership is unknown. Check linked documents and sharing permissions too.
In a synthetic Northwind example, the agent prepares a supplier reply from orders@northwind.test to supplier@northwind.test, with no Cc or Bcc and one attachment, northwind-order-1042.pdf, containing the order lines. The user sees the full reply and attachment summary before choosing Send, Edit or Keep draft. Approval to draft is not approval to send, and an incoming message cannot authorise the action.
Bind confirmation to that exact preview and durable send intent. A change to the account, recipients, body or attachments invalidates it and requires a new preview and confirmation. Enforce this in the send tool; a model-generated confirmation flag is insufficient. Use the original intent and supported idempotency key for retries. After a timeout, reconcile the mailbox or delivery receipt before resending; if the outcome remains unknown, stop for review. Report Draft saved, Sent or Outcome unknown according to verified evidence.
Before exposing the tool, use a disposable fixture: