Idempotencia para agentes de IA: evite cargos y escrituras duplicadas
Su agente llamó al endpoint de pago. La solicitud se procesó y el cargo se realizó, pero la respuesta agotó el tiempo de espera al regresar. Como el agente nunca recibió un 200, reintentó la operación. Resultado: el cliente recibió dos cargos y sus registros no muestran un error evidente.
Los reintentos hacen que los agentes sean resilientes, pero también multiplican el riesgo de escrituras duplicadas. La solución es la idempotencia: una solicitud repetida debe producir el mismo efecto que una sola ejecución.
Esta guía explica cómo implementar claves de idempotencia, reutilizarlas desde herramientas de agentes, proteger el servidor y probar el comportamiento en CI. Las escrituras duplicadas son uno de los fallos más frecuentes descritos en nuestra guía sobre por qué los agentes de IA fallan en producción.
Apidog ayuda a validar esta capa: puede repetir la misma solicitud, comprobar que el estado no cambia y guardar el escenario para ejecutarlo en CI.
Por qué los agentes duplican operaciones
Tres patrones hacen que los agentes sean especialmente propensos a repetir escrituras:
- Reintentos agresivos. Los frameworks reintentan ante fallos transitorios. El backoff exponencial y los circuit breakers, explicados en nuestra guía de recuperación de errores de agentes, aumentan el número de veces que una solicitud puede alcanzar su servidor.
-
Tiempos de espera ambiguos. Un
504puede significar que la escritura no ocurrió o que sí ocurrió, pero la respuesta se perdió. Un humano suele verificar antes de reintentar; un agente puede no hacerlo. - Reinicio de tareas completas. Si el paso uno crea un pedido y el paso cuatro falla, reiniciar la tarea de forma ingenua puede crear otro pedido. En flujos de varios pasos, el límite del reintento no siempre está definido en código: puede decidirlo el modelo.
El problema no es que los agentes envíen solicitudes incorrectas. Envían solicitudes correctas más de una vez.
Qué garantiza realmente la idempotencia
Una operación es idempotente cuando ejecutarla varias veces tiene el mismo efecto que ejecutarla una vez.
Según RFC 9110, GET, PUT y DELETE son idempotentes. POST no lo es por defecto, por eso las operaciones peligrosas —crear pedidos, cobrar pagos, enviar mensajes o iniciar transferencias— suelen requerir protección adicional.
Dos distinciones importantes:
-
Idempotente no significa seguro.
DELETEes idempotente, pero sigue siendo destructivo: cinco llamadas dejan el recurso eliminado, igual que una sola. Clasifique por separado qué herramientas cambian estado y qué permisos necesitan, como se explica en la guía de claves API de menor privilegio para agentes. - Idempotente no significa respuesta idéntica por definición. El requisito es que el estado final del servidor no cambie. Aun así, devolver la respuesta original suele ser la opción más clara para clientes y depuración.
Use Idempotency-Key para proteger POST
El patrón estándar consiste en que el cliente envíe una clave estable con la solicitud. El servidor guarda esa clave, una huella del cuerpo y el resultado. Si recibe la misma clave otra vez, devuelve el resultado almacenado sin repetir el trabajo.
Stripe popularizó este enfoque en su documentación de idempotencia. También existe el borrador del IETF para el encabezado estándar Idempotency-Key.
POST /v1/payments HTTP/1.1
Host: api.yourservice.com
[REDACTED CREDENTIAL] [REDACTED]...
Idempotency-Key: 9f2b7c14-6d3a-4b18-9d55-1e2a7c0b4f31
Content-Type: application/json
{
"amount": 4900,
"currency": "usd",
"customer_id": "cus_8812",
"description": "Pro plan, August"
}
La clave puede ser un UUID. No necesita significado de negocio: solo debe identificar la misma operación lógica. El servidor debe almacenarla junto con:
- Una huella digital del cuerpo de la solicitud.
- El estado de procesamiento.
- El código de estado y el cuerpo de la respuesta.
- Una fecha de vencimiento.
Genere la clave una vez por operación lógica
El error más común en agentes es generar un UUID nuevo en cada intento HTTP. Si la clave cambia en cada reintento, la idempotencia deja de servir.
Regla: genere la clave cuando el agente decide realizar una acción y reutilícela en todos los reintentos de esa decisión.
import uuid
class PaymentTool:
def __init__(self, client):
self.client = client
self._keys = {}
def charge(self, task_id, step_id, amount, customer_id):
# Una clave por (tarea, paso). Los reintentos del mismo paso la reutilizan.
op = f"{task_id}:{step_id}"
if op not in self._keys:
self._keys[op] = str(uuid.uuid4())
return self.client.post(
"/v1/payments",
headers={"Idempotency-Key": self._keys[op]},
json={"amount": amount, "customer_id": customer_id},
)
Para sobrevivir a reinicios de proceso, derive una clave determinista de la tarea, el paso y la carga útil:
import hashlib
def idempotency_key(task_id: str, step_id: str, payload: dict) -> str:
raw = f"{task_id}|{step_id}|{sorted(payload.items())}"
return hashlib.sha256(raw.encode()).hexdigest()[:32]
No derive la clave de una marca de tiempo ni de un valor aleatorio generado por intento. Si el agente reinicia la tarea y realmente debe crear una operación nueva, el ID de tarea debe cambiar; entonces la clave también cambiará.
Implemente la protección en el servidor
El servidor debe reclamar la clave antes de ejecutar el trabajo. Una implementación completa sigue este flujo:
- Intente insertar la clave con una restricción única.
- Si ya existe y la huella de solicitud es distinta, devuelva
422. - Si ya existe y el primer intento sigue en curso, devuelva
409para que el cliente espere y reintente. - Cuando el trabajo termine, guarde el código de estado y el cuerpo de respuesta.
- Para solicitudes posteriores con la misma clave y carga útil, devuelva la respuesta guardada.
CREATE TABLE idempotency_records (
key TEXT PRIMARY KEY,
request_hash TEXT NOT NULL,
state TEXT NOT NULL, -- en_progreso | completado
response_status INT,
response_body JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
expires_at TIMESTAMPTZ NOT NULL
);
Establezca una expiración. Veinticuatro horas cubren una ventana de reintento realista y evitan que la tabla crezca indefinidamente. Stripe también expira estas claves tras 24 horas.
Pruebe el estado, no solo la respuesta
La prueba esencial es:
- Envíe un
POSTcon unaIdempotency-Keyfija. - Guarde la respuesta.
- Repita exactamente la misma solicitud.
- Confirme que el servidor no realizó el trabajo dos veces.
Dos respuestas exitosas no prueban que la implementación funcione: dos cargos distintos también podrían devolver 200. Verifique el estado:
- La segunda respuesta debe contener el mismo ID de recurso que la primera.
- Un
GETposterior debe devolver un registro, no dos. - Un contador, saldo o inventario debe haberse modificado una sola vez.
En Apidog, cree un escenario de prueba con tres pasos:
- Envíe el
POSTcon una clave fija. - Repita el mismo
POST. - Liste el recurso y compruebe el recuento.
Guarde el ID de la primera respuesta en una variable y confirme que la segunda devuelve el mismo valor. Al guardar el escenario, puede ejecutarlo en CI en cada cambio de la ruta de pago. Este enfoque complementa la guía de pruebas de contratos API.
Incluya también estas pruebas:
-
Misma clave, cuerpo distinto: debe devolver
422, no un éxito silencioso. - Duplicados concurrentes: dispare ambas solicitudes al mismo tiempo y confirme que solo una realiza el trabajo. Esto detecta la ausencia de una restricción única en la base de datos.
Si la API todavía no existe, simúlela con semántica de idempotencia para ejercitar pronto la lógica de reintentos del agente. Consulte por qué los agentes deberían usar simulaciones en lugar de producción.
Si no puede añadir una clave
Cuando una API externa no admite claves de idempotencia, aplique estas alternativas en orden de preferencia:
-
Diseñe una operación naturalmente idempotente. Por ejemplo,
PUT /orders/{client_order_id}. Si el cliente elige el ID del recurso, repetir la llamada no crea otro pedido. - Verifique antes de escribir. Busque un registro con la misma clave natural antes de crear uno. Esto reduce duplicados por timeout, pero no elimina condiciones de carrera.
- Desduplique en el consumidor. Para eventos o mensajes, incluya un ID estable y haga que el consumidor descarte repeticiones. Es una práctica habitual en sistemas orientados a eventos y se alinea con la guía de webhooks fiables.
- Requiera aprobación humana. Si la operación es irreversible y no puede hacerse idempotente, use una puerta de aprobación. Es una barrera adecuada para acciones de alto coste, como se explica en la guía de barreras de seguridad para agentes de IA.
Conserve la trazabilidad de cada ejecución
La idempotencia evita el duplicado, pero no responde qué intento creó el registro durante un incidente.
Registre el ID de tarea, el ID de paso, la clave de idempotencia y el resultado de cada intento. Si el agente se ejecuta en una plataforma como Sharkly, asocie cada escritura con la tarea y la ejecución que la originaron para evitar reintentos anónimos.
Lista de verificación
- [ ] Cada herramienta que cambia estado exige una clave de idempotencia.
- [ ] El envoltorio de la herramienta genera y conserva la clave; el modelo no la inventa.
- [ ] Las claves se derivan de la tarea y el paso, no del intento HTTP.
- [ ] El servidor reclama la clave antes de realizar el trabajo.
- [ ] La misma clave con otra carga útil devuelve un error.
- [ ] Los duplicados concurrentes se resuelven con una restricción de base de datos.
- [ ] Una prueba guardada demuestra que la segunda llamada no cambia el estado y se ejecuta en CI.
- [ ] Las claves caducan y los registros vencidos se limpian.
La idempotencia permite aumentar la agresividad de los reintentos sin aumentar el peligro operativo. Es la base para que un agente sea resiliente sin cobrar, crear o enviar dos veces.
Preguntas frecuentes
¿Necesito claves de idempotencia para herramientas de solo lectura?
No. GET ya es seguro e idempotente. Reserve las claves para llamadas que crean, cobran, envían o cambian estado.
¿Dónde debe generarse la clave?
En el envoltorio de la herramienta, usando los identificadores de tarea y paso. Dejar que el modelo genere claves puede causar valores nuevos en reintentos o colisiones entre tareas.
¿Qué código de estado debe devolver una repetición?
Devuelva el estado almacenado de la llamada original. Si el primer POST devolvió 201, la repetición debe devolver 201 con el mismo cuerpo. Un encabezado como Idempotent-Replay: true puede ayudar a depurar sin afectar a clientes que lo ignoran.
¿Cuánto tiempo deben conservarse las claves?
Veinticuatro horas cubren casi cualquier ventana de reintento. Después de ese periodo, trate una solicitud repetida como una operación nueva.
¿Las claves de idempotencia reemplazan las transacciones?
No. Las transacciones mantienen una solicitud atómica; las claves evitan efectos duplicados entre solicitudes repetidas. Use ambas y, cuando sea posible, reclame la clave en la misma transacción que el trabajo.
¿Cómo pruebo esto sin un proveedor de pagos real?
Apunte el agente a un mock que implemente la clave, incluidas las respuestas 422 por cargas útiles incompatibles. Puede centralizar el mock y las pruebas de reintento en el mismo proyecto al descargar Apidog.


Top comments (0)