Ingeniería de Agentes
Durable Execution en Agentes de IA: Checkpointing, Idempotencia y Aprobación Asíncrona (HITL) en Producción
Por qué el 85% de los agentes multi-paso fallan al correr en memoria volátil y cómo diseñar un arnés duradero (Durable Harness) con replay determinista, claves de idempotencia y puertas de aprobación humana asíncronas.
- Fecha
- 16 oct 2026
- Lectura
- 9 min
- Resultado
- 0 Efectos Laterales Duplicados • Ahorro 100% Tokens en Reintentos • 0% CPU en Espera HITL • Replay < 3ms
La ola de lanzamientos open source de octubre de 2026 —desde meta-harnesses como Ruflo, AWS Strands Harness y Pizza Bot hasta la adopción masiva de motores de ejecución duradera como Inngest, Hatchet y LangGraph Checkpointers— ha dejado una lección de arquitectura incuestionable en la trinchera: un agente de IA en producción no es un modelo, es un modelo más su arnés (Agent = Model + Harness).
El modelo aporta el razonamiento estocástico; el arnés (harness) aporta la durabilidad del estado, el aislamiento de herramientas, las garantías de idempotencia y las puertas de aprobación humana (Human-in-the-Loop).
Sin embargo, el 85% de los desarrolladores sigue desplegando agentes de larga duración dentro de un simple bucle while en memoria RAM o atados a una petición HTTP síncrona. En este cuaderno técnico de YesWeLab (@Yeswelab_OU) desglosamos por qué esa arquitectura colapsa en el mundo real y cómo construir agentes indestructibles bajo nuestra filosofía Dual Track.
1. La Muerte en el Paso 9: El Desastre de los Agentes Volátiles
Imagina un agente autónomo de operaciones financieras o onboarding comercial que ejecuta un flujo de 10 pasos: investiga una empresa, consulta el ERP, genera un contrato en PDF, emite una factura en Stripe, envía un correo al cliente y actualiza el CRM.
En una arquitectura ingenua basada en memoria volátil (un script Python o un workflow sin checkpoints intermedios), ocurre lo siguiente cuando la red parpadea o el proveedor del LLM devuelve un error HTTP 529 Overloaded en el paso 9:
[ EJECUCIÓN VOLÁTIL EN RAM: EL DESASTRE DEL REINTENTO CIEGO ]
Paso 1: Agente investiga datos fiscales (LLM + Web) ──► ✅ OK ($0.04)
Paso 2: Agente valida NIF en base de datos ──► ✅ OK
Paso 3: Agente redacta borrador de contrato (LLM) ──► ✅ OK ($0.08)
Paso 4: Tool Call -> stripe.create_invoice(490€) ──► ✅ OK [EFECTO LATERAL REAL]
Paso 5: Tool Call -> smtp.send_email(cliente) ──► ✅ OK [EFECTO LATERAL REAL]
...
Paso 9: LLM sintetiza resumen para CRM ──► 💥 ERROR 529 / TIMEOUT DE RED
[ REINTENTO AUTOMÁTICO DEL WORKER DESDE EL PASO 1 ]
├──► Quema de nuevo los tokens de los pasos 1 a 8 (Coste x2)
├──► Ejecuta stripe.create_invoice(490€) POR SEGUNDA VEZ (Cliente cobrado doble)
└──► Envía un segundo email duplicado al cliente
Cuando el estado vive únicamente en las variables locales del proceso:
- Cualquier reinicio de contenedor Docker o caída de red destruye el progreso.
- Reintentar la tarea completa vuelve a disparar herramientas con efectos laterales irreversibles (pagos, envíos de mensajes, creación de registros).
- Se duplica o triplica la factura de tokens regenerando razonamientos que ya habían tenido éxito.
2. Anatomía de un Durable Agent Harness: Checkpointing y Replay Determinista
La Ejecución Duradera (Durable Execution) transforma el bucle del agente en una máquina de estados respaldada por un registro de eventos persistente (Append-Only Step Journal).
┌──────────────────────────────────────────┐
│ TURNO / TOOL CALL DEL AGENTE │
└────────────────────┬─────────────────────┘
│
¿Existe (run_id, step_id, args_hash)
en el Step Journal (WAL)?
│
┌───────────────────┴───────────────────┐
│ SÍ (Replay tras caída) │ NO (Primera ejecución)
▼ ▼
┌───────────────────────────┐ ┌───────────────────────────┐
│ HIDRATACIÓN INSTANTÁNEA │ │ EJECUCIÓN REAL + LOCK │
│ • Lee output desde SQLite │ │ • Invoca LLM o Tool MCP │
│ • Latencia: < 2ms │ │ • Persiste output en WAL │
│ • Coste: $0.00 tokens │ │ • Avanza puntero de grafo │
│ • 0 efectos duplicados │ └───────────────────────────┘
└───────────────────────────┘
Los 3 pilares de ingeniería del arnés duradero
| Pilar Técnico | Qué problema resuelve en trinchera | Mecanismo de implementación |
|---|---|---|
| 1. Step Checkpointing | Evita perder el contexto y los resultados parciales si el contenedor muere a mitad de un trabajo de 15 minutos. | Serialización del estado del grafo en SQLite WAL / Postgres al finalizar cada nodo. |
| 2. Idempotency Keys | Impide que una herramienta externa (Stripe, CRM, Email) ejecute dos veces la misma mutación durante un reintento. | Hash determinista SHA256(run_id + tool_name + canonical_json(args)) verificado antes de invocar la API. |
| 3. Async HITL (Suspend/Resume) | Elimina los procesos bloqueados esperando aprobación humana durante horas. | El agente guarda el checkpoint, envía un webhook firmado a Telegram/Slack y termina el proceso (0% RAM/CPU). |
3. Implementación en Código: Durable Step Runner + Idempotencia en SQLite WAL
No necesitas infraestructura pesada para dotar de durabilidad e idempotencia a tus agentes en Python. Con SQLite en modo WAL puedes implementar un Durable Harness que garantiza ejecución exactly-once sobre tus herramientas MCP:
import sqlite3
import hashlib
import json
from typing import Callable, Any
class DurableAgentHarness:
def __init__(self, db_path: str = "/Users/mortadelo/yeswelab/data/lab.db"):
self.conn = sqlite3.connect(db_path)
self.conn.execute("PRAGMA journal_mode=WAL;")
self.conn.execute("""
CREATE TABLE IF NOT EXISTS agent_step_journal (
idempotency_key TEXT PRIMARY KEY,
run_id TEXT NOT NULL,
step_name TEXT NOT NULL,
result_json TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
""")
self.conn.commit()
@staticmethod
def _compute_key(run_id: str, step_name: str, payload: dict) -> str:
canonical = json.dumps(payload, sort_keys=True, ensure_ascii=False)
raw = f"{run_id}:{step_name}:{canonical}"
return hashlib.sha256(raw.encode("utf-8")).hexdigest()
def step_run(self, run_id: str, step_name: str, payload: dict, fn: Callable[..., Any]) -> Any:
key = self._compute_key(run_id, step_name, payload)
cur = self.conn.cursor()
# 1. Comprobar si el paso ya se ejecutó con éxito (Replay Determinista)
cur.execute(
"SELECT result_json FROM agent_step_journal WHERE idempotency_key = ?",
(key,)
)
row = cur.fetchone()
if row is not None:
print(f"[REPLAY] Paso '{step_name}' hidratado desde disco (0 tokens / 0 side-effects)")
return json.loads(row[0])
# 2. Ejecutar la herramienta o turno del LLM por primera vez
result = fn(**payload)
# 3. Persistir atómicamente antes de avanzar al siguiente paso
cur.execute(
"""
INSERT INTO agent_step_journal (idempotency_key, run_id, step_name, result_json)
VALUES (?, ?, ?, ?)
""",
(key, run_id, step_name, json.dumps(result, ensure_ascii=False))
)
self.conn.commit()
return result
Si el proceso muere en el paso 9 y el supervisor (launchd, systemd o Docker) lo levanta de nuevo con el mismo run_id:
- Los pasos 1 al 8 imprimen
[REPLAY]en menos de 2 milisegundos, devolviendo exactamente los mismos objetos JSON sin tocar la red ni cobrar al cliente dos veces. - El paso 9 se ejecuta limpiamente desde donde se quedó.
4. Human-in-the-Loop Asíncrono: El Patrón “Inbox & Webhook Resume”
El segundo gran antipatrón en agentes de IA es pensar que el usuario está mirando una ventana de chat esperando a que el agente termine. Cuando un agente necesita autorización para una acción crítica (ej. publicar en redes, emitir un reembolso o desplegar código):
- Nunca mantengas un
sleep()ni una conexión abierta en memoria: Si el humano tarda 3 horas en responder desde el móvil, cualquier reinicio del servidor matará la espera. - Suspensión a disco (Dehydrate): El agente escribe su estado completo en la base de datos con
status = 'waiting_approval', genera unresume_tokenfirmado y envía una tarjeta interactiva a Telegram o Slack. - Liberación total de recursos: El worker finaliza su ejecución. El consumo de RAM y CPU durante las 3 horas de espera es exactamente cero.
- Reanudación por evento (Rehydrate): Cuando el usuario pulsa “Autorizar” (o vence el timeout programado con política de auto-approve / auto-reject), el webhook despierta un worker, carga el checkpoint por su
run_idy ejecuta únicamente el paso final.
5. Estrategia Dual Track en YesWeLab
En YesWeLab aplicamos esta arquitectura en dos niveles según el volumen y la infraestructura del proyecto:
Track A: Trinchera Open Source / DIY (Coste $0 y Control Local)
- LangGraph +
SqliteSaver(WAL Mode): Cada transición de nodo en el grafo del agente se guarda automáticamente en un archivo SQLite local con unthread_iddeterminista, permitiendo usarinterrupt_before=["critical_tool"]para pausar y reanudar flujos sin perder estado. - n8n Self-Hosted con Nodo
Wait(Webhook Resume): En flujos híbridos de n8n en Docker, el nodoWaitconfigurado en modo On Webhook Call vuelca automáticamente el estado de la ejecución a la base de datos SQLite/Postgres de n8n, liberando la memoria del contenedor hasta que el botón de Telegram o email invoca la URL única$execution.resumeUrl. - Journal de Idempotencia en SQLite: Tabla local
agent_step_journalcon clave primaria SHA-256 para blindar herramientas MCP destructivas a coste cero.
Track B: Fast-Track SaaS y Escalado Distribuido
- Inngest (
step.runystep.waitForEvent): Orquestación serverless de agentes donde cada paso es una función duradera con reintentos exponenciales automáticos y espera de eventos de hasta 30 días sin pagar cómputo ocioso. - Temporal / Hatchet + n8n Cloud HITL: Para flotas empresariales con decenas de agentes concurrentes, garantizando colas con backpressure adaptativo, dead-letter queues y trazabilidad completa de auditoría SOC 2 / GDPR.
6. Checklist de Trinchera: Auditoría de Durabilidad Agéntica
Antes de conectar un agente a herramientas con impacto real en tu negocio, verifica estos 5 puntos:
- Prueba de Caos (Kill -9 Test): Si matas el contenedor del agente en el paso 4 de 6 y lo relanzas con el mismo
run_id, ¿retoma en el paso 4 sin repetir los pasos 1 a 3? - Claves de Idempotencia Obligatorias: Toda herramienta MCP que escriba datos, envíe mensajes o mueva dinero exige un
idempotency_keydeterminista derivado delrun_id. - Cero Esperas en RAM: Ningún flujo de aprobación humana mantiene un proceso vivo con
sleep()o conexiones HTTP colgadas; todo HITL serializa estado a disco. - Timeouts con Política Explícita: Las puertas de aprobación tienen un SLA definido (ej. auto-publish a las 11:00 si es contenido programado, o auto-abort si es una transacción financiera).
- Separación de Errores Transitorios vs. Terminales: Los errores
429/529aplican exponential backoff sobre el paso actual; los errores400 Bad Schemacortan el bucle inmediatamente sin quemar presupuesto.