· Hernán Pérez Rodal · Engineering · 6 min read
Agentic Compliance System con LangGraph: patterns que funcionan en producción
No todos los multi-agent patterns sirven en dominios regulados. Contamos qué arquitectura de agentes usamos en Darwin, por qué, y qué anti-patterns evitamos.

TL;DR — Los tutoriales de agents hablan de “autonomous AI” como si fuera un superpoder mágico. En producción regulada, la autonomía sin guardrails es una pesadilla legal. En Darwin corremos un sistema multi-agent con LangGraph, y el valor no está en la autonomía — está en la orquestación determinista de componentes LLM-powered.
El problema
Darwin procesa miles de casos de compliance por día sobre datos de trazabilidad alimentaria. Cada caso requiere:
- Interpretar una pregunta regulatoria (texto libre del usuario o sistema)
- Descomponerla en sub-consultas (regulatoria, operativa, de evidencia)
- Delegar cada sub-consulta al agente adecuado
- Sintetizar los resultados en una respuesta con gap analysis + risk scoring
- Validar antes de presentar — especialmente datos numéricos
Intentar que un solo LLM haga todo end-to-end con un prompt gigante falla en producción. Los modelos se confunden, alucinan datos, pierden contexto intermedio. Cuando el output va a una auditoría FDA, “se equivocó un poquito” no es aceptable.
El pattern que usamos: supervisor + workers especializados
La arquitectura es:
[Supervisor]
│
┌──────────┼──────────┬──────────┐
│ │ │ │
[Research] [Validation] [Analytics] [Reporting]- Supervisor — decide qué agentes invocar, en qué orden, y cuándo parar
- Research Agent — busca contexto regulatorio + eventos de trazabilidad (usa el RAG híbrido que describí acá)
- Validation Agent — cruza reglas de negocio determinísticas con hallazgos del Research
- Analytics Agent — ejecuta queries estructuradas + anomaly detection sobre eventos
- Reporting Agent — sintetiza todo en un output consumible (report PDF, JSON API, UI view)
El supervisor es la pieza crítica. Es el que decide, no los workers.
Por qué LangGraph y no otra cosa
Evaluamos varias opciones antes de elegir LangGraph:
| Framework | Por qué no / por qué sí |
|---|---|
| LangChain Agents (AgentExecutor) | Muy mágico. Difícil de debuggear en producción. |
| CrewAI | Bueno para prototipos. Abstracción demasiado alta para patterns específicos. |
| AutoGen | Fuerte en conversaciones multi-agent. Menos ideal para flujos deterministas. |
| Custom state machine | Considerado. Para nuestro caso LangGraph da 80% con 20% del código. |
| LangGraph ✅ | State machine explícito + checkpoints + streaming. Debuggeable, production-grade. |
Lo clave: LangGraph no es “un framework de agents”. Es un state machine con superpoderes para LLM calls. Esa diferencia mental cambia cómo lo usás.
Patterns que funcionan
1. Supervisor determinista, no autónomo
Nuestro supervisor NO le pregunta al LLM “¿qué hago ahora?” en cada step. Eso es autonomía mágica y es un anti-pattern.
Lo que SÍ hace:
def supervisor_node(state: ComplianceState) -> str:
"""Decide next node based on state — deterministic rules first, LLM second."""
if not state.query_classified:
return "classify_query"
if state.classification == "regulatory" and not state.regulatory_context:
return "research"
if state.has_numerical_claims and not state.validated:
return "validate"
if state.ready_for_report:
return "report"
return "END"El LLM entra solo en los nodos individuales (classify, research, validate). El routing es código. Evita que un LLM “decida” sacar del circuito un step de validación crítico.
2. Checkpointing agresivo
Cada step emite un checkpoint. Si algo falla, no reejecutamos todo — retomamos desde el último estado bueno. Crítico cuando un caso toca 5-10 nodos y cada LLM call cuesta tiempo+$.
from langgraph.checkpoint.postgres import PostgresSaver
graph = builder.compile(
checkpointer=PostgresSaver(conn_string=DATABASE_URL),
interrupt_before=["report"], # Pausa antes del reporte final
)3. Human-in-the-loop configurable
Algunos casos son auto-approve (rutinas), otros require review (high-stakes). Usamos interrupt_before en LangGraph para pausar antes de los nodos que decidimos requieren intervención humana.
El operador revisa, aprueba o edita, y el graph continúa desde el checkpoint. Sin perder trabajo.
4. Tool use con validación estructurada
Cuando un agent llama una tool (query DB, buscar documento, computar métrica), todo output es validado contra un schema Pydantic antes de volver al LLM. Si el tool retorna algo inesperado, el error se maneja explícitamente — no se le pasa al LLM que inventará.
class TraceabilityQueryOutput(BaseModel):
events: list[CriticalTrackingEvent]
total_count: int
date_range: DateRange
# Tool response validated. If fails → retry or escalate, NO LLM hallucination.5. Observabilidad end-to-end
Cada node emite tracing con OpenTelemetry:
- Input state
- LLM prompt + response
- Token usage + cost
- Latency
- Tool calls (con inputs/outputs)
Sin esto, debugear un agent system es imposible. Con esto, encontrás por qué un caso salió mal en minutos.
Anti-patterns que evitamos
❌ Autonomía total del supervisor. “El LLM decide cuándo parar” = runaway loops + costos inesperados. Nuestro supervisor tiene max iterations por nodo y timeout por caso.
❌ Agent-to-agent conversations largas. Los tutoriales muestran N agents “discutiendo”. En producción, cada intercambio es $$$ y latencia. Usamos mensaje único por agent, no conversación.
❌ Shared memory global sin estructura. Todos los agents escribiendo en un dict compartido → race conditions + hallucination sobre qué es el estado actual. Nosotros usamos state pydantic-typed con mutations explícitas.
❌ LLM-as-everything. Muchos tutoriales usan LLM para cosas que una regla determinística resuelve. Regla general: si podés escribirlo como código, escribilo como código. El LLM solo para lo que realmente requiere razonamiento natural.
Lo que no funcionó
V0: un solo agent con tool use — probamos con un solo Claude agent con acceso a todas las tools. Falló porque el agent a veces olvidaba validaciones críticas cuando el prompt se ponía largo. Refactoreamos a supervisor + workers.
Streaming continuo en UI — quisimos stream-ear las decisiones del supervisor a la UI en tiempo real. Resultó confuso para el usuario (veía “research → validate → research → validate” sin saber por qué). Cambiamos a mostrar solo los milestones principales y esconder la orquestación interna.
Lo que sí funcionó
Supervisor determinista — zero runaway loops desde que lo implementamos.
Checkpointing en PostgreSQL — podemos retomar cases que quedaron a medias por caída de infra o cuotas de API.
Validation agent con reglas + LLM — combinación de reglas hardcoded + LLM-as-judge para los edge cases da mejor precisión que cualquiera por separado.
Metrics por agent — sabemos exactly qué agent es el cuello de botella en latencia o costo. Nos permitió optimizar selectivamente (ej: swappear el Reporting Agent a un modelo más chico sin tocar Research).
Lessons learned
- Agentic ≠ autonomous — los mejores patterns son deterministas con LLM calls selectivas
- LangGraph es un state machine, no un framework de agents — pensalo así y todo simplifica
- Checkpointing agresivo desde el día 0 — el costo de agregarlo después es alto
- Validá tool outputs con schemas — nunca le pases al LLM algo sin validar
- Tracing + cost metrics por node — sin esto no hay iteración real
¿Y ahora?
Estamos explorando sub-graphs reutilizables — nuestro Validation Agent es suficientemente genérico para usar en otros flujos (no solo compliance). LangGraph soporta composabilidad de graphs, y eso abre la puerta a un catálogo interno de patterns que podemos remix.
Si estás buildeando AI agents en producción y te sorprende que tu sistema es más lento/más caro/más inestable que tu demo — probablemente es falta de orquestación determinista. Empezá por ahí.
¿Construís algo similar? Hablemos — nos interesa compartir arquitectura y patterns.




