Em sistemas com agentes, o erro comum não é “falta de log”. É falta de correlação. Um pedaço está no trace do app, outro no log da ferramenta, outro no ticket do suporte, outro no workflow engine. Quando dá certo, ninguém liga. Quando dá errado, a organização entra em arqueologia.
OpenTelemetry (OTel) muda isso porque impõe um formato: spans, traces e contexto propagado. Ele não resolve governança sozinho, mas cria a espinha dorsal para responder perguntas do tipo: qual prompt gerou qual ação? qual ferramenta foi chamada? quantas vezes retry aconteceu? qual foi o custo? quem era o ator? qual era a versão ativa?
O desafio — Rastrear a intenção e o efeito no mesmo “fio”
Em agentic AI, você precisa de um fio único que atravesse: decisão → chamada de ferramenta → resposta → efeito colateral → retry/rollback. Se você não tem isso, “o modelo disse” vira “ninguém sabe”, e “temos logs” vira “não temos prova”.
OTel ajuda porque permite:
- colocar o
trace_idcomo chave de correlação em todos os sistemas - registrar atributos que importam para governança (versão, escopo, coorte, idempotência, motivo)
- medir o denominador oculto: retries, latência, intervenção humana e rotas de exceção
O cenário — A mesma ação aconteceu duas vezes e ninguém sabe por quê
Um agente abre tickets, manda e-mails e aciona automações. Um fornecedor fica instável. O workflow reexecuta um passo. O tool wrapper também reexecuta. Dois tickets aparecem. O time diz que foi “um bug”. O cliente pergunta por que foi duplicado. A liderança pergunta por que o sistema permitiu.
Sem correlação, você só tem fragmentos. Com correlação, você tem o caminho.
Visão AI — Logs locais e IDs soltos por convenção
No caminho “AI”, a observabilidade costuma ser pragmática: logs com algum request id, métricas básicas, e uma tentativa de correlacionar manualmente quando dá problema. Isso é suficiente em serviços simples, mas em agentes vira frágil porque o “request” atravessa componentes, ferramentas e reexecuções.
O código abaixo mostra esse padrão. Ele registra logs com um correlation_id e faz chamadas “normais” de ferramenta. Funciona — até o dia em que a investigação exige reconstruir a sequência completa e perceber que os IDs não viajaram por todos os lugares.
"""AI-style: correlação manual via correlation_id em logs.Pros: simples, sem dependências.Cons: o ID nem sempre propaga; não cria uma árvore de spans; retries viram ruído."""from __future__ import annotationsimport timeimport uuidimport loggingfrom typing import Dict, Anylogging.basicConfig(level=logging.INFO)def tool_send_email(to: str, subject: str, body: str) -> Dict[str, Any]:time.sleep(0.05)return {"status": "sent", "to": to}def tool_create_ticket(title: str, body: str) -> Dict[str, Any]:time.sleep(0.03)return {"status": "created", "ticket_id": str(uuid.uuid4())}def run_agent(user_msg: str) -> Dict[str, Any]:correlation_id = f"corr-{uuid.uuid4()}"logging.info("agent_start correlation_id=%s msg=%s", correlation_id, user_msg)ticket = tool_create_ticket("Incidente: latência", f"{user_msg}\ncorrelation_id={correlation_id}")logging.info("ticket_created correlation_id=%s ticket_id=%s", correlation_id, ticket["ticket_id"])email = tool_send_email("ops@empresa.com", "Alerta", f"Veja o ticket {ticket['ticket_id']}")logging.info("email_sent correlation_id=%s to=%s", correlation_id, email["to"])logging.info("agent_end correlation_id=%s", correlation_id)return {"correlation_id": correlation_id, "ticket": ticket, "email": email}if __name__ == "__main__":print(run_agent("p95 subiu na região A; erros aumentando."))
Na prática, esse padrão falha quando uma ferramenta não grava o correlation_id, quando há múltiplas threads/filas, ou quando retries criam múltiplos “subeventos” sem estrutura. Você fica com muitos logs, mas sem uma árvore que explique causalidade.
Visão WM — OpenTelemetry como espinha dorsal do “fio de evidência”
No caminho “WM”, a ideia é transformar correlação em infraestrutura. OTel define o trace como “fio” e spans como “passos”. Cada tool call vira um span com atributos governáveis: escopo, versão, idempotência, tentativa, motivo. Retentativas deixam de ser ruído e viram contagem. Se existir efeito colateral, você consegue ver quantas vezes tentou e por quê.
O código abaixo mostra um exemplo mínimo com OpenTelemetry em Python: cria um trace para a execução do agente, cria spans para cada ferramenta e anota atributos que mais tarde permitem reconstruir o caminho e produzir evidência portátil.
"""WM-style: OpenTelemetry tracing para agent + tool calls.- Um trace por execução (correlation spine)- Um span por tool call (árvore causal)- Atributos críticos para governança (versão, coorte, idempotência, tentativa)Obs: exemplo mínimo. Em produção, exporte para OTLP (Collector) e conecte a um backend."""from __future__ import annotationsimport timeimport uuidfrom typing import Dict, Anyfrom opentelemetry import tracefrom opentelemetry.trace import Status, StatusCodefrom opentelemetry.sdk.trace import TracerProviderfrom opentelemetry.sdk.trace.export import SimpleSpanProcessor, ConsoleSpanExporter# Setup mínimo (console). Em produção: OTLPSpanExporter -> OTel Collector.provider = TracerProvider()provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))trace.set_tracer_provider(provider)tracer = trace.get_tracer("trajecta.agent")def tool_send_email(to: str, subject: str, body: str) -> Dict[str, Any]:time.sleep(0.05)return {"status": "sent", "to": to}def tool_create_ticket(title: str, body: str) -> Dict[str, Any]:time.sleep(0.03)return {"status": "created", "ticket_id": str(uuid.uuid4())}def run_agent(user_msg: str, cohort: str = "region:A") -> Dict[str, Any]:execution_id = str(uuid.uuid4())model_version = "model-vX"prompt_version = "prompt-v7"with tracer.start_as_current_span("agent.run") as root:root.set_attribute("agent.execution_id", execution_id)root.set_attribute("cohort.key", cohort)root.set_attribute("model.version", model_version)root.set_attribute("prompt.version", prompt_version)root.set_attribute("input.size", len(user_msg))# Tool: create_ticketwith tracer.start_as_current_span("tool.create_ticket") as span:span.set_attribute("tool.name", "create_ticket")span.set_attribute("tool.effect", "side_effect")span.set_attribute("idempotency.key", f"ticket:{execution_id}")span.set_attribute("attempt", 1)try:ticket = tool_create_ticket("Incidente: latência", user_msg)span.set_attribute("ticket.id", ticket["ticket_id"])span.set_status(Status(StatusCode.OK))except Exception as e:span.record_exception(e)span.set_status(Status(StatusCode.ERROR))raise# Tool: send_emailwith tracer.start_as_current_span("tool.send_email") as span:span.set_attribute("tool.name", "send_email")span.set_attribute("tool.effect", "side_effect")span.set_attribute("idempotency.key", f"email:{execution_id}:ops@empresa.com")span.set_attribute("attempt", 1)try:email = tool_send_email("ops@empresa.com", "Alerta", f"Ticket {ticket['ticket_id']}")span.set_attribute("email.to", email["to"])span.set_status(Status(StatusCode.OK))except Exception as e:span.record_exception(e)span.set_status(Status(StatusCode.ERROR))raiseroot.set_attribute("agent.outcome", "complete")return {"execution_id": execution_id, "ticket": ticket, "email": email}if __name__ == "__main__":print(run_agent("p95 subiu na região A; erros aumentando."))
Esse padrão produz uma árvore causal: uma execução do agente contém spans de ferramentas, cada um com atributos que permitem reconstruir o caminho. Isso facilita identificar duplicidade por retry, correlacionar custo e latência por tool, e, principalmente, ligar o “ato” a um conjunto de metadados que futuramente pode virar um receipt. OTel não substitui gates de permissão nem idempotência real, mas ele reduz o custo de provar o que aconteceu e acelera a capacidade de pausar e reverter.
Comparação — O que muda quando trace vira espinha dorsal
No padrão AI, correlação é um acordo social: “vamos passar um ID e torcer para todos registrarem.” Funciona até o ecossistema crescer, até retries aparecerem em lugares diferentes e até o debate virar contestação. A investigação vira arqueologia.
No padrão WM, correlação é uma propriedade do sistema. Trace e spans criam um fio que atravessa componentes. Retentativas deixam de ser ruído e viram dados. Ferramentas deixam de ser caixas pretas e viram passos observáveis. E o custo de produzir evidência cai — o que, no mundo real, é o que decide se você consegue defender, pausar e reverter.