· Hernán Pérez Rodal · Engineering · 6 min read
Agentic Compliance System com LangGraph: patterns que funcionam em produção
Nem todos os multi-agent patterns servem em domínios regulados. Contamos qual arquitetura de agentes usamos na Darwin, por quê, e quais anti-patterns evitamos.

TL;DR — Os tutoriais de agents falam de “autonomous AI” como se fosse um superpoder mágico. Em produção regulada, autonomia sem guardrails é um pesadelo jurídico. Na Darwin rodamos um sistema multi-agent com LangGraph, e o valor não está na autonomia — está na orquestração determinística de componentes LLM-powered.
O problema
A Darwin processa milhares de casos de compliance por dia sobre dados de rastreabilidade alimentar. Cada caso requer:
- Interpretar uma pergunta regulatória (texto livre do usuário ou sistema)
- Decompô-la em sub-consultas (regulatória, operativa, de evidência)
- Delegar cada sub-consulta ao agente adequado
- Sintetizar os resultados em uma resposta com gap analysis + risk scoring
- Validar antes de apresentar — especialmente dados numéricos
Tentar que um único LLM faça tudo end-to-end com um prompt gigante falha em produção. Os modelos se confundem, alucinam dados, perdem contexto intermediário. Quando o output vai para uma auditoria FDA, “errou um pouquinho” não é aceitável.
O pattern que usamos: supervisor + workers especializados
A arquitetura é:
[Supervisor]
│
┌──────────┼──────────┬──────────┐
│ │ │ │
[Research] [Validation] [Analytics] [Reporting]- Supervisor — decide quais agentes invocar, em que ordem, e quando parar
- Research Agent — busca contexto regulatório + eventos de rastreabilidade (usa o RAG híbrido que descrevi aqui)
- Validation Agent — cruza regras de negócio determinísticas com achados do Research
- Analytics Agent — executa queries estruturadas + anomaly detection sobre eventos
- Reporting Agent — sintetiza tudo em um output consumível (relatório PDF, API JSON, UI view)
O supervisor é a peça crítica. É ele que decide, não os workers.
Por que LangGraph e não outra coisa
Avaliamos várias opções antes de escolher LangGraph:
| Framework | Por que não / por que sim |
|---|---|
| LangChain Agents (AgentExecutor) | Muito mágico. Difícil de debugar em produção. |
| CrewAI | Bom para protótipos. Abstração alta demais para patterns específicos. |
| AutoGen | Forte em conversas multi-agent. Menos ideal para fluxos determinísticos. |
| Custom state machine | Considerado. Para nosso caso, LangGraph entrega 80% com 20% do código. |
| LangGraph ✅ | State machine explícita + checkpoints + streaming. Debugável, production-grade. |
O chave: LangGraph não é “um framework de agents”. É uma state machine com superpoderes para LLM calls. Essa diferença mental muda como você o usa.
Patterns que funcionam
1. Supervisor determinístico, não autônomo
Nosso supervisor NÃO pergunta ao LLM “o que eu faço agora?” a cada step. Isso é autonomia mágica e é um anti-pattern.
O que SIM faz:
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"O LLM entra só nos nodes individuais (classify, research, validate). O routing é código. Evita que um LLM “decida” tirar do circuito um step de validação crítico.
2. Checkpointing agressivo
Cada step emite um checkpoint. Se algo falha, não reexecutamos tudo — retomamos a partir do último estado bom. Crítico quando um caso toca 5-10 nodes e cada LLM call custa tempo+$.
from langgraph.checkpoint.postgres import PostgresSaver
graph = builder.compile(
checkpointer=PostgresSaver(conn_string=DATABASE_URL),
interrupt_before=["report"], # Pausa antes do relatório final
)3. Human-in-the-loop configurável
Alguns casos são auto-approve (rotinas), outros require review (high-stakes). Usamos interrupt_before no LangGraph para pausar antes dos nodes que decidimos exigirem intervenção humana.
O operador revisa, aprova ou edita, e o graph continua desde o checkpoint. Sem perder trabalho.
4. Tool use com validação estruturada
Quando um agent chama uma tool (query DB, buscar documento, computar métrica), todo output é validado contra um schema Pydantic antes de voltar ao LLM. Se a tool retorna algo inesperado, o erro é tratado explicitamente — não passamos para o 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. Observabilidade end-to-end
Cada node emite tracing com OpenTelemetry:
- Input state
- LLM prompt + response
- Token usage + cost
- Latency
- Tool calls (com inputs/outputs)
Sem isso, debugar um agent system é impossível. Com isso, você encontra por que um caso deu errado em minutos.
Anti-patterns que evitamos
❌ Autonomia total do supervisor. “O LLM decide quando parar” = runaway loops + custos inesperados. Nosso supervisor tem max iterations por node e timeout por caso.
❌ Conversas agent-to-agent longas. Os tutoriais mostram N agents “discutindo”. Em produção, cada troca é $$$ e latência. Usamos mensagem única por agent, não conversa.
❌ Shared memory global sem estrutura. Todos os agents escrevendo em um dict compartilhado → race conditions + alucinação sobre qual é o estado atual. Nós usamos state pydantic-typed com mutations explícitas.
❌ LLM-as-everything. Muitos tutoriais usam LLM para coisas que uma regra determinística resolve. Regra geral: se você pode escrever como código, escreva como código. O LLM só para o que realmente requer raciocínio em linguagem natural.
O que não funcionou
V0: um único agent com tool use — testamos com um único Claude agent com acesso a todas as tools. Falhou porque o agent às vezes esquecia validações críticas quando o prompt ficava longo. Refatoramos para supervisor + workers.
Streaming contínuo na UI — quisemos streamar as decisões do supervisor para a UI em tempo real. Ficou confuso para o usuário (via “research → validate → research → validate” sem saber por quê). Mudamos para mostrar só os milestones principais e esconder a orquestração interna.
O que sim funcionou
Supervisor determinístico — zero runaway loops desde que implementamos.
Checkpointing em PostgreSQL — podemos retomar cases que ficaram pela metade por queda de infra ou cotas de API.
Validation agent com regras + LLM — combinação de regras hardcoded + LLM-as-judge para os edge cases dá melhor precisão que qualquer um separadamente.
Metrics por agent — sabemos exatamente qual agent é o gargalo em latência ou custo. Nos permitiu otimizar seletivamente (ex: trocar o Reporting Agent por um modelo menor sem tocar em Research).
Lessons learned
- Agentic ≠ autonomous — os melhores patterns são determinísticos com LLM calls seletivos
- LangGraph é uma state machine, não um framework de agents — pense assim e tudo simplifica
- Checkpointing agressivo desde o dia 0 — o custo de adicioná-lo depois é alto
- Valide tool outputs com schemas — nunca passe ao LLM algo sem validar
- Tracing + cost metrics por node — sem isso não há iteração real
E agora?
Estamos explorando sub-graphs reutilizáveis — nosso Validation Agent é genérico o suficiente para usar em outros fluxos (não só compliance). LangGraph suporta composabilidade de graphs, e isso abre a porta para um catálogo interno de patterns que podemos remixar.
Se você está fazendo build de AI agents em produção e te surpreende que seu sistema é mais lento/mais caro/mais instável que sua demo — provavelmente é falta de orquestração determinística. Comece por aí.
Construindo algo parecido? Vamos conversar — nos interessa compartilhar arquitetura e patterns.




