# Idempotency keys for agent write APIs: safe retries without duplicate writes

## TL;DR

Generate one key per logical write, before the first attempt, and reuse that exact key with the exact same payload on every retry. Derive the key deterministically from the write's identity, for example a hash of the canonical payload plus a stable item identifier, so a crashed and restarted agent rebuilds the same key instead of inventing a new write. The server stores the first result under the key and replays it for identical retries.

## When to use this

- Any create, publish, post, or resolution call that could be retried after a timeout, a 503, or a dropped connection.
- Long-running agent pipelines where a worker can crash between sending a write and recording its result.
- Fan-out publishing where several workers might attempt the same logical write.

## When not to use it

- Reads. GET requests are naturally safe to retry without keys.
- Two writes that are meant to be distinct. Reusing a key across different payloads is a client bug and many servers reject it as a key conflict.

## Steps

1. Define the logical write first: the target, the operation, and the canonical payload bytes. Success check: two runs of your builder produce byte-identical payload for the same logical write.

2. Derive the key from that identity: a readable prefix for the pipeline, the stable item identifier, and a short hash of the canonical payload. Success check: editing the payload produces a different key, and rebuilding the same write anywhere produces the same key.

3. Send the key in the `Idempotency-Key` header on the first attempt and persist the key with the work item before recording success. Expected behavior: a retry after a transport failure returns either the original success or a clear replay response, never a second object.

4. On an unknown outcome, resend the identical request: same key, same payload, byte for byte. Never fix a typo, bump a field, or reformat under the same key. If the payload must change, it is a new logical write and gets a new key.

5. Treat a key-conflict response (often 409) as a stop signal, not a retry signal. It means the key was already used for a different payload. Read the existing object first and reconcile, instead of forcing the write.

## Variants

### Server-issued keys

Some APIs mint the retry key for you. Vectle's keyless search, for example, has the server create a random retry key for the public search post. When the server issues the key, your job is to store and reuse it, not to invent your own.

### Time-bounded keys

Keys are not eternal. Servers expire them, commonly after hours or days. A retry that arrives after expiry is a fresh write, so pair keys with a read-back check for very old items.

## Why deterministic keys beat random keys

A random key generated at send time dies with the process that generated it. If the agent crashes after the server commits but before the agent records the result, recovery code with a fresh random key creates a duplicate. A deterministic key rebuilt from the work item lets the recovery path replay safely and learn the original outcome.

## Edge cases

- Verify a replayed success before trusting it. A replay can return an identifier for an object that later failed verification; read the object back and confirm it exists.
- Keys are usually scoped per account or per credential. The same key under a different credential is a different write.
- Canonicalization matters: key order, whitespace, and Unicode normalization in the payload all change the hash. Serialize once, hash the bytes you actually send.
- Concurrent identical writes are fine; that is the point. Concurrent writes with the same key and different payloads are the bug to hunt.
