Agentes que pueden deshacer: contratos estrictos con compensación obligatoria y patrón Saga
Garantías transaccionales distribuidas, validación de esquemas en tiempo de definición y recuperación LIFO en LangGraph.
"En sistemas distribuidos sabemos desde hace más de tres décadas que cuando una operación involucra múltiples pasos sobre recursos externos, Two-Phase Commit es inviable y los fallos parciales no son una posibilidad, sino una certeza estadística. Héctor García-Molina y Kenneth Salem formalizaron el patrón Saga en 1987. Si no se diseña una pila LIFO de transacciones compensatorias y no se obliga por contrato a que cada acción reversible declare su función de marcha atrás, cualquier fallo intermedio en un plan multi-paso dejará la infraestructura en un estado inconsistente."
1. Resumen Ejecutivo: La Falacia del Agente Infalible
Los agentes autónomos capaces de razonar en múltiples pasos (Multi-step Planning) representan uno de los mayores saltos evolutivos de la IA aplicada. Sin embargo, su despliegue en entornos corporativos introduce un riesgo sistémico: la falta de atomicidad distribuida. Cuando un agente desglosa una orden de alto nivel en un plan de cinco pasos (por ejemplo: 1. Crear rama Git → 2. Provisionar bucket S3 → 3. Modificar esquema de base de datos → 4. Desplegar contenedor → 5. Notificar por mensajería), cualquier fallo imprevisto en el paso 4 deja la infraestructura en un estado corrupto, inconsistente y parcialmente provisionado.
Los motores de bases de datos relacionales resuelven esto mediante transacciones ACID y ROLLBACK. Pero en el ecosistema de los agentes distribuidos —donde las operaciones involucran APIs de terceros, sistemas de archivos e infraestructuras en la nube— no existe una transacción distribuida global. La respuesta de la ingeniería de software clásica es el Patrón Saga: cada acción directa debe contar con una transacción de compensación semántica capaz de revertir sus efectos colaterales en orden inverso.
En este artículo presentamos la arquitectura de Sagas implementada en el ecosistema jg_agente y jg_agente_sdk: contratos canónicos estrictos basados en Pydantic v2 que fuerzan en tiempo de definición la existencia de una función de compensación (rollback_func), inyección de contexto transaccional en tiempo de ejecución, y un orquestador determinista en LangGraph que, ante cualquier fallo fatal, recorre en orden inverso (reversed(execution_records)) las acciones previamente consolidadas para restaurar el sistema a su estado original.
2. Puntos de Fricción: Planes a Medio Ejecutar y Recursos Huérfanos
Al examinar arquitecturas de agentes en producción, se observan con frecuencia los siguientes compromisos estructurales:
-
Recursos Huérfanos y Facturación Fantasma: Un agente provisiona una máquina virtual en la nube, pero falla al intentar clonar el repositorio de código. El proceso se detiene por una excepción no capturada, dejando la máquina virtual encendida indefinidamente y consumiendo presupuesto de infraestructura.
-
Inconsistencias Semánticas Irrecuperables: El agente renombra una tabla crítica en la base de datos, pero la compilación del código falla en el siguiente paso. Al carecer de una acción inversa programada, el entorno queda inservible para el resto de los servicios.
-
Contratos Informales Basados en Documentación: Confiar en que el desarrollador de una herramienta "recuerde" implementar la compensación sin validación formal en el esquema provoca excepciones en tiempo de ejecución cuando el sistema intenta recuperarse de un fallo y descubre que el método no existe.
3. Arquitectura y Solución Técnica: Contratos Fuertes y el Orquestador de Sagas
El diseño desacoplado de javiergalvez-ia aborda este desafío en dos capas: validación estricta del contrato en el SDK (jg_agente_sdk) y ejecución orquestada en el grafo de estados de jg_agente.
Diagrama del Ciclo de Ejecución y Compensación Saga
┌────────────────────────────────────────────────────────────────────────────────────────┐
│ Fase Directa (Forward Flow) │
│ │
│ Paso 1: Create Branch ──► Paso 2: Provision S3 ──► Paso 3: Database DDL ──► Paso 4: Deploy -│
│ [SUCCESS] [SUCCESS] [SUCCESS] [FATAL ERROR] │
│ Registra Rollback Registra Rollback Registra Rollback │ │
│ │ │
└──────────────────────────────────────────────────────────────────────────────────────┼─┘ │
│ │
Transición Automática │ │
requires_rollback = True ▼ │
┌────────────────────────────────────────────────────────────────────────────────────────┐ │
│ Fase de Compensación (Saga Rollback) │ │
│ │ │
│ Rollback Paso 1: ◄── Rollback Paso 2: ◄── Rollback Paso 3: │ │
│ Delete Git Branch Destroy S3 Bucket Revert DDL Migration │ │
│ [COMPENSATED] [COMPENSATED] [COMPENSATED] │ │
│ │ │
│ Estado Final: Limpio / Sistema Restaurado Determinísticamente │ │
└────────────────────────────────────────────────────────────────────────────────────────┘
Principios Fundamentales del Diseño:
-
Single Source of Truth (SSOT) en Pydantic v2: En
jg_agente_sdk, una herramienta no puede declararse como reversible (is_reversible = True) a menos que defina explícitamente una función o identificador de compensación válido enrollback_func. Si la validación falla, la herramienta es rechazada durante el registro en el catálogo de plugins. -
Historial de Ejecución con Contexto Transaccional: Cada paso exitoso genera un
ExecutionRecordque encapsula los argumentos de ejecución originales y el resultado retornado. Esto permite construir elrollback_contextnecesario para alimentar la función de compensación sin tener que consultar nuevamente al modelo de lenguaje. -
Enrutamiento Ineludible en LangGraph: En el grafo de estados de
jg_agente, el nodo de ejecución (execute_node) detecta si ha ocurrido una excepción fatal en presencia de pasos previos reversibles. Si se cumple la condición, conmuta la banderarequires_rollback=True. La arista condicional del grafo (route_after_execute) intercepta este estado y desvía la navegación obligatoriamente al nodorollback_node.
4. Código Fuente Comentado
Contrato Canónico con Validación de Esquema en Pydantic v2
# jg_agente_sdk/jg_sdk/contracts.py
from typing import Callable, Any
from pydantic import BaseModel, Field, model_validator
class AgentTool(BaseModel):
"""Contrato formal que define una herramienta ejecutable por el agente."""
name: str = Field(..., description="Nombre identificador único de la herramienta")
description: str = Field(..., description="Descripción semántica para el razonamiento del LLM")
is_reversible: bool = Field(default=False, description="Indica si la acción soporta compensación")
rollback_func: Callable[..., Any] | str | None = Field(
default=None,
description="Función o identificador de la acción compensatoria (obligatoria si is_reversible=True)",
)
@model_validator(mode="after")
def validate_reversible_contract(self) -> "AgentTool":
"""Garantiza por contrato que toda herramienta reversible proporcione su compensador."""
if self.is_reversible and not self.rollback_func:
raise ValueError(
f"Contrato inválido en '{self.name}': Las herramientas reversibles "
"deben definir obligatoriamente un 'rollback_func' de compensación."
)
return self
Detección de Fallo y Enrutamiento al Nodo Rollback en LangGraph
# jg_agente/app/nodes/execute.py (Detección de fallo fatal)
if not step_result.success and step_result.is_fatal:
# Verificamos si existen pasos anteriores completados que sean reversibles
has_reversible_steps = any(
rec.get("is_reversible", False) for rec in state.get("execution_records", [])
)
if has_reversible_steps:
# Activamos la señal de orquestación Saga
state["requires_rollback"] = True
# jg_agente/app/core/graph.py (Arista condicional determinista)
def route_after_execute(state: AgentState) -> str:
"""Decide la siguiente transición del grafo tras la ejecución de un paso."""
# Prioridad: si se requiere compensación Saga, transicionar a rollback
if state.get("requires_rollback", False):
return "rollback"
# Si el paso falló pero no requiere rollback, transicionar a fallo terminal
if state.get("current_step_failed", False):
return "failure"
# Si quedan más pasos en el plan, continuar iterando
if state.get("pending_steps"):
return "execute"
return "finalize"
Ejecución de la Compensación en Orden Inverso
# jg_agente/app/nodes/rollback.py
import logging
from app.models.state import AgentState
logger = logging.getLogger("jg_agente.saga")
async def rollback_node(state: AgentState) -> dict:
"""Nodo orquestador que recorre las acciones consolidadas en orden LIFO."""
records = state.get("execution_records", [])
compensation_log = []
# Recorremos en orden estrictamente inverso (LIFO / Stack)
for record in reversed(records):
if not record.get("is_reversible", False):
continue
tool_name = record["tool_name"]
rollback_handler = record.get("rollback_func")
rollback_context = record.get("rollback_context", {})
logger.warning(f"Ejecutando compensación Saga para: {tool_name}")
try:
# Invocación determinista del compensador con su contexto original
result = await rollback_handler(**rollback_context)
compensation_log.append({
"tool": tool_name,
"status": "COMPENSATED",
"result": result,
})
except Exception as e:
logger.error(f"Fallo crítico durante la compensación de {tool_name}: {e}")
compensation_log.append({
"tool": tool_name,
"status": "FAILED",
"error": str(e),
})
return {
"rollback_executed": True,
"compensation_log": compensation_log,
"requires_rollback": False,
}
5. Consideraciones de Producción, Trade-offs y Límites
-
Compensaciones Parciales e Idempotencia Obligatoria: Una función de compensación debe ser estrictamente idempotente. Si el proceso de rollback se interrumpe por un reinicio de red y se reintenta, invocar
delete_s3_bucket()sobre un bucket ya suprimido debe devolver éxito o una excepción manejada (404 NoSuchBucket), sin abortar el resto de la cadena de compensación. -
Límites de la Compensación Distribuida: Efectos Externos No Reversibles: Es indispensable reconocer qué no puede resolver el patrón Saga: ninguna compensación distribuida por software puede deshacer efectos colaterales externos no idempotentes que ya hayan interactuado con el mundo real.
- Si durante un paso intermedio el agente emitió un SMS a un cliente, envió un correo electrónico, despachó un webhook a un sistema externo o liquidó una transferencia bancaria a través de una pasarela de pagos, dicho efecto no se puede "borrar".
- Estrategia ante Puntos de No Retorno: En
jg_agente, las herramientas que producen efectos externos no reversibles deben configurarse obligatoriamente conis_reversible=Falsey nivel de riesgoHIGHoCRITICAL. El planificador clasifica estos pasos como puntos de no retorno y fuerza una parada enhuman_approval. Si a pesar de ello el paso posterior falla, el orquestador Saga compensa todos los recursos técnicos reversibles (archivos, bases de datos, ramas Git), pero emite una alerta crítica de reconciliación con escalado humano inmediato y registro de auditoría para compensación manual o administrativa.
-
Persistencia del Historial para Auditoría: El registro de compensación (
compensation_log) se almacena en el checkpoint de LangGraph y se replica en el Dashboard corporativo. El equipo de operaciones puede auditar con precisión qué pasos se ejecutaron, qué compensadores se activaron y si el sistema quedó en un estado completamente saneado.
6. Reflexión de Arquitectura: La Responsabilidad de la Reversibilidad
La fiabilidad de un sistema no se mide únicamente por su capacidad de completar escenarios nominales (happy path), sino por su disciplina para recuperarse de catástrofes parciales.
En el diseño de agentes de IA, asumir que el modelo "sabrá qué hacer" ante un error es una premisa frágil. Los modelos alucinan, las APIs fallan y las conexiones se interrumpen. La estabilidad en producción se construye con la disciplina de la ingeniería de software clásica: contratos con tipado estricto que no validan si no defines la marcha atrás, y una máquina de estados determinista que sanee los recursos si el plan no llega a buen término.
7. Enlaces y Referencias en el Repositorio
- Contratos y Validación en Pydantic v2:
jg_agente_sdk/jg_sdk/contracts.py - Nodo de Ejecución y Detección de Sagas:
jg_agente/app/nodes/execute.py - Nodo de Compensación Rollback:
jg_agente/app/nodes/rollback.py - Enrutamiento Condicional en LangGraph:
jg_agente/app/core/graph.py - Suite de Pruebas de Sagas y Rollback:
jg_agente/tests/test_nodes_verify_rollback.py
| Título | Agentes que pueden deshacer: contratos estrictos con compensación obligatoria y patrón Saga |
|---|---|
| Archivo | docs/02_jg_agent/articulos/09_reversible_contracts_saga_rollback.md |
| Descripción | Garantías transaccionales distribuidas, validación de esquemas en tiempo de definición y recuperación LIFO en LangGraph. |
| Autor | Javier Gálvez |
| Fecha | 2026-10-17 |
| Etiquetas |
#Artículos
#Arquitectura
#Patrón Saga
#Rollback
#Transacciones Distribuidas
#Pydantic v2
#LangGraph
#LIFO
|