Models retry. Timeouts, truncated tool messages, and flaky networks all trigger a second call. If your tool is not idempotent, you get duplicate side effects: two tickets, two charges, two deploys.

Idempotency means the same logical request can run more than once and still leave the system in one correct state. For reads that is easy. For writes you need a stable key the server recognizes.

Pass an idempotency key with every mutating tool call. Generate it from the conversation turn id plus the tool name and a hash of the arguments. Store the key with the result. On a repeat, return the stored result instead of running the action again.

Keep the key window long enough to cover retries and short enough that you do not forever block legitimate new actions. Hours often work better than minutes for agent workflows, because a human may pause mid-run and resume later.

Watch argument drift. A model that retries with slightly different wording is not the same request. Decide whether your key includes the full args. If yes, a tiny change creates a new action. If no, you risk ignoring a real edit. For money and deploys, include the args. For create-or-get patterns, a business key like email or external id may be safer.

Make the tool response honest about replay. Include a field such as replayed=true when you served a cached outcome. That helps debugging and keeps the model from inventing a second success story.

Handle partial failure. If the side effect succeeded but the response never reached the agent, the next retry must find the completed work. Persist the outcome before you acknowledge success to the caller. If persistence fails, fail the tool call so the agent can retry cleanly.

Avoid fire-and-forget writes from tools. Prefer APIs that accept client tokens. If a vendor has no idempotency support, add your own ledger: before calling the vendor, insert a pending row keyed by your token; on success mark it done; on retry check the ledger first.

Test the path deliberately. Kill the process after the write and before the reply. Confirm the second call does not create another record. Also test concurrent retries, because agents and workers sometimes race.

Document which tools are safe to retry and which are not. Put that in the tool description the model sees. Clear guidance reduces panic retries and duplicate prompts from the system layer.

Idempotent tools turn model retries from a liability into a recovery feature. Build the key, store the outcome, and treat a second call as a lookup first.

A practical way to build the key is to concatenate the turn identifier, the tool name, and a SHA-256 hash of the JSON-encoded arguments. The resulting string is short enough for an HTTP header and unique enough for a database index.

Store the key and result together in a fast cache such as Redis with a TTL matching your retry window. When the TTL expires, the entry is removed, allowing a fresh request to proceed.

If you use a relational store, add a unique constraint on the idempotency column. On insert, catch the duplicate-key error and return the existing row instead of creating a new one.

When calling external services, send the key in the standard Idempotency-Key header. Many SaaS APIs already treat that header as a deduplication token, so you get built-in safety without extra code.

Log every attempt with the key, the timestamp, and whether the response was fresh or replayed. This audit trail makes it easy to spot unexpected duplicates and to tune the TTL.

Monitor the rate of replayed responses. A sudden spike may indicate a network issue or a misbehaving client that is retrying too aggressively. Alerting on that metric helps you react before duplicate side effects accumulate.