Saltar al contenido
← Cuaderno

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:

  1. Cualquier reinicio de contenedor Docker o caída de red destruye el progreso.
  2. Reintentar la tarea completa vuelve a disparar herramientas con efectos laterales irreversibles (pagos, envíos de mensajes, creación de registros).
  3. 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écnicoQué problema resuelve en trincheraMecanismo de implementación
1. Step CheckpointingEvita 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 KeysImpide 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):

  1. 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.
  2. Suspensión a disco (Dehydrate): El agente escribe su estado completo en la base de datos con status = 'waiting_approval', genera un resume_token firmado y envía una tarjeta interactiva a Telegram o Slack.
  3. 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.
  4. 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_id y 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 un thread_id determinista, permitiendo usar interrupt_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 nodo Wait configurado 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_journal con clave primaria SHA-256 para blindar herramientas MCP destructivas a coste cero.

Track B: Fast-Track SaaS y Escalado Distribuido

  • Inngest (step.run y step.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_key determinista derivado del run_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/529 aplican exponential backoff sobre el paso actual; los errores 400 Bad Schema cortan el bucle inmediatamente sin quemar presupuesto.
#LangGraph SQLiteSaver#n8n Wait Webhook#Inngest#Ruflo Harness#SQLite WAL#Docker#Python