Idempotencia en agentes de IA: reintentos seguros sin duplicar acciones

Un agente puede repetir una tool call aunque la primera ejecución sí haya llegado al proveedor. Esta guía diseña el contrato de ejecución que evita duplicados y hace recuperable la incertidumbre.

Compartir
Idempotencia en agentes de IA: reintentos seguros sin duplicar acciones

La idempotencia en agentes de IA es el contrato que permite recibir dos o más intentos de la misma acción y producir como máximo un efecto de negocio. No significa que la red no falle: significa que tu sistema puede reconocer el mismo intento, devolver su resultado durable y reconciliar lo que no sabe.

La idempotencia tampoco es rollback. Un proveedor puede aceptar un cargo y después fallar tu proceso; deshacerlo requiere una operación compensatoria, no una transacción distribuida mágica.

Arquitectura de ejecución idempotente para agentes: intención con clave determinista, registro durable, aprobación, worker de reintentos, proveedor externo, reconciliación tras timeout y auditoría
El modelo propone una intención; el ejecutor durable decide si se autoriza, se ejecuta, se reconcilia o se compensa.

¿Te está sirviendo? Hay una dosis cada semana

Te resumo herramientas de IA para devs, agentes, MCP, seguridad y workflows en un email de 5 minutos. En español y sin ruido.

Suscribirme gratis

Checklist

TypeScript: intent durable y clave determinista

Este pseudocódigo concentra el contrato. `insertIfAbsent` debe tener una restricción única sobre la clave; `compareAndSet` evita que dos workers ejecuten el mismo intent a la vez. El proveedor recibe la misma clave cuando ofrece soporte nativo.

import { createHash } from "node:crypto";

function keyFor(x: { tenant: string; actor: string; tool: string; resource: string; args: unknown }) {
  const canonical = canonicalJson(x.args);
  return createHash("sha256").update([x.tenant, x.actor, x.tool, x.resource, canonical].join("|")).digest("hex");
}

async function execute(input: ToolInput) {
  const key = keyFor(input);
  const hash = hashPayload(input.args);
  const row = await db.insertIfAbsent({ key, hash, status: "received", input });
  if (row && row.hash !== hash) throw new Error("idempotency_conflict");
  if (row?.status === "succeeded" || row?.status === "failed") return row.result;
  if (!(await policy.allows(input)) || !(await approval.granted(input))) {
    await db.transition(key, "rejected"); return { status: "rejected" };
  }
  if (!(await db.compareAndSet(key, ["received", "unknown"], "running"))) return db.read(key);
  try {
    const result = await provider.mutate(input.args, { idempotencyKey: key });
    await db.transition(key, "succeeded", { result, providerId: result.id });
    return result;
  } catch (error) {
    const kind = classify(error); // transient | permanent | unknown
    await db.transition(key, kind === "transient" ? "failed_transient" : "unknown", { error: safe(error) });
    throw error;
  }
}

Un worker recoge `failed_transient` con backoff y límite; no conserva una petición HTTP abierta. Si el proceso muere después de que el proveedor acepta pero antes de guardar `succeeded`, el siguiente worker empieza en `unknown` y reconcilia antes de mutar otra vez.

Errores, reintentos y timeout

Reintenta solo errores transitorios como `429`, `503` o un corte previo al envío, con backoff, jitter, máximo de intentos y ventana total. Schema inválido, autorización denegada, saldo insuficiente y conflicto de clave son permanentes. Un timeout es distinto: no sabes si el servidor recibió la orden y queda en `unknown`.

Puntos a revisar

Lo que conviene comprobar

AWS Step Functions separa errores recuperables, backoff y destinos de fallo; adopta esa disciplina aunque uses una cola propia. Para workflows largos, una máquina durable suele ser más segura que esconder reintentos dentro de HTTP. Las [evals de trayectorias](/evals-agentes-ia-tools-trayectorias/) deben comprobar que el agente no insiste con `unknown` sin reconciliar.

Aprobación, compensación y rollback

La aprobación debe ligarse a la intención concreta: actor, tenant, recurso, payload normalizado, clave, expiración y consecuencias. Se persiste como transición `approved`; si cambia el hash, crea un intent nuevo. Una aprobación genérica de “permitir pagos” no es evidencia suficiente.

Rollback solo existe cuando controlas todas las escrituras en una transacción. En pagos, emails, reservas o permisos diseña una compensación (`refund`, `release`, `revoke`) como acción de primer nivel, con clave nueva, motivo y auditoría. Puede fallar y necesitar otra revisión.

Preguntas frecuentes

¿Un `call_id` del modelo sirve como clave?

No; puede cambiar. Deriva la clave de la intención de negocio.

¿Reintento después de cualquier timeout?

No. Marca `unknown`, consulta por clave o referencia y solo entonces resuelve.

¿La idempotencia evita todos los duplicados?

Evita repeticiones de la misma intención cuando el ejecutor y proveedor respetan el contrato; no evita dos intenciones legítimas distintas.

¿Guardo el resultado completo en el prompt?

No. Guárdalo durablemente y devuelve una vista mínima, validada y redactada.

¿Qué hago si falla la compensación?

Mantén `compensation_pending`, reintenta con su propia clave y alerta a un operador.

Cómo envolver una tool mutante sin duplicar efectos

  1. Define intención. fija actor, tenant, tool, recurso y argumentos normalizados; calcula clave y hash.
  2. Persiste antes de ejecutar. inserta con restricción única y devuelve el resultado existente si ya terminó.
  3. Autoriza y aprueba. aplica política fuera del modelo y liga la aprobación al hash con expiración.
  4. Ejecuta con lease. usa compare-and-set, pasa la misma clave al proveedor y registra request id.
  5. Clasifica. persiste éxito o error; un timeout queda `unknown`, nunca éxito inventado.
  6. Reconcilia y compensa. consulta por clave, resuelve `unknown` y crea compensación separada si hace falta.
  7. Evalúa y observa. simula duplicados, caídas y payload conflictivo; alerta por `unknown` y compensaciones pendientes.

Checklist

Conclusión

La unidad fiable de un agente no es el mensaje ni la llamada de función: es la intención durable que puede autorizarse, ejecutarse, repetirse y reconciliarse sin perder identidad. Si no distingues “no se envió” de “se envió pero perdimos la respuesta”, todavía no estás listo para mutar datos reales.

Fuentes y referencias

También te puede interesar

OpenAI Responses API y function calling fiableEvals de agentes de IA: tools y trayectoriasn8n y agentes de IA: workflows fiablesMCP outputSchema y structuredContentOpenTelemetry GenAI para agentes de IA

Recibe una lectura semanal de herramientas IA para devs

Cada semana te resumo herramientas de IA para devs, agentes, MCP, seguridad y workflows en un email de 5 minutos. En español y sin ruido.

Suscribirme gratis

Lo mejor de la IA para desarrolladores, cada martes

Newsletter en español, gratis. Las herramientas, modelos y trucos de IA para devs que de verdad importan — sin ruido.