Como descobrir qual agente errou quando a orquestração entrega um resultado errado

Depurar orquestração de agentes é diferente de montar a orquestração: aqui todo agente aparece como sucesso e mesmo assim o resultado final sai errado, porque a falha mora no espaço ENTRE os agentes, nas fronteiras de comunicação e handoff. O caminho é ler a árvore de spans das convenções GenAI do OpenTelemetry (invoke_agent na raiz, chat por chamada de LLM, execute_tool por ferramenta), propagar um único trace ID ponta a ponta, comparar o que entrou e o que saiu de cada etapa e reexecutar só a etapa suspeita a partir de um checkpoint, sem repetir a cadeia inteira
Fala aí, beleza? Tem poucas coisas mais irritantes que abrir a execução, ver TODO agente marcado como sucesso, e o resultado final estar errado do mesmo jeito
Ninguém falhou, mas a resposta tá torta
Este post não é sobre montar cadeia de agentes, é sobre depurar uma que já existe e já te entregou lixo. A ideia é ir do sintoma ("a saída final tá errada") até o ponto exato da falha: qual etapa, qual fronteira, qual dado se perdeu no meio do caminho
Se liga que quase todo sintoma aqui tem a mesma raiz: você tá olhando log de agente quando deveria estar olhando a passagem entre eles
Sintoma: todo agente aparece como sucesso, mas o resultado final está errado
A causa: falhas em sistemas multiagente costumam estar no espaço entre os agentes, e não dentro da lógica de um agente isolado, como o pessoal da Sentry descreve bem
Cada agente, olhado sozinho, executou direitinho
Recebeu uma entrada, produziu uma saída, fechou sem exceção. Por isso o log individual de ninguém registra erro: a falha está na comunicação e no handoff, não na execução
A solução: parar de perguntar "qual agente quebrou?" e começar a perguntar "o que saiu de um e o que entrou no outro?"
A unidade de investigação vira a FRONTEIRA, não o agente
Como prevenir: tratar handoff como interface, do mesmo jeito que você trataria uma chamada de API entre dois serviços: entrada registrada, saída registrada, e um jeito de comparar as duas depois
Formação Claude Code
Domine Claude Code do absoluto zero até o avançado
- 120 aulas
- 4 projetos
- 9h 45min
Sintoma: você não consegue enxergar a cadeia inteira da orquestração
Você abre o painel e vê pedaços soltos: um trace do orquestrador aqui, outro do subagente ali, e nada que ligue os dois
A causa: o trace reinicia a cada fronteira de agente e a árvore fica picada
A solução: propagar um único trace ID em toda invocação de agente, sem reiniciar quando cruza a fronteira. Com um trace ID só, ponta a ponta, você enxerga quem chamou o quê, em que ordem, com quais entradas e quais saídas
E aí entra a parte que padroniza o desenho da árvore
Como as convenções GenAI do OpenTelemetry organizam a execução:
As convenções semânticas GenAI do OpenTelemetry organizam a execução em uma árvore de spans assim:
- um span de nível superior
invoke_agent, que é a raiz - spans filhos
chat, um pra cada chamada de LLM - spans
execute_tool, um pra cada uso de ferramenta
No span de execução de ferramenta o nome da ferramenta é obrigatório, e é ele que dá nome ao span execute_tool
Isso é MUITO prático na hora de depurar: você bate o olho na árvore e já lê qual ferramenta rodou dentro de qual agente, sem ficar adivinhando por timestamp
Como prevenir: instrumentar antes de precisar, não depois do incidente. Instrumentação feita no meio do fogo só cobre a execução seguinte, e a que quebrou já era
Nota pra não te perder na documentação: as convenções de IA generativa saíram do repositório principal de semantic conventions e passaram a viver em um repositório dedicado de convenções GenAI. A página antiga de spans de agente no site hoje aparece marcada como "Moved", então se você caiu nela por link velho, é só seguir a mudança de casa
Sintoma: a etapa parou no meio e você não sabe por quê
A saída de uma etapa veio curtinha, e a cadeia seguiu em frente com meia informação
A causa: sem atributos padronizados, "saída curta porque o modelo resolveu assim" e "saída truncada porque bateu o limite" parecem exatamente a mesma coisa no seu log
Duas causas completamente diferentes, o mesmo sintoma visual
A solução: ler os atributos padronizados que acompanham esses spans:
| Atributo | O que ele te conta |
|---|---|
gen_ai.request.model |
qual modelo rodou naquela etapa |
gen_ai.usage.input_tokens |
tokens de entrada consumidos |
gen_ai.usage.output_tokens |
tokens de saída gerados |
gen_ai.response.finish_reasons |
por que aquela etapa parou |
Com esses quatro campos a conversa muda de "acho que o modelo alucinou" pra "essa etapa parou por esse motivo, consumindo essa quantidade de contexto"
Como prevenir: exportar esses atributos por padrão em TODA etapa da cadeia, não só nas que você desconfia. O ponto de exportar sempre é justamente não precisar reproduzir o bug pra ter dado
Sintoma: o erro só aparece depois de dois ou três agentes e você não sabe qual estragou o dado
Esse é o clássico: o agente 1 pesquisou certo, o agente 2 resumiu, o agente 3 escreveu, e a informação certa evaporou em algum lugar do meio
A causa: perda de contexto no handoff
Cada vez que um agente resume antes de passar adiante, a informação sofre compressão com perda. E o dilema é real: passar tudo estoura a janela de contexto, resumir perde nuance
Não existe saída elegante, existe escolha consciente
A solução: comparar, dentro do trace, o texto que ENTROU e o texto que SAIU de cada etapa
Você vai lendo passagem por passagem até achar a primeira em que o dado correto já não está mais lá. Essa é a etapa culpada, mesmo que ela tenha fechado com sucesso
Como prevenir: registrar o payload do handoff, não só o veredito do agente. "Agente de pesquisa: ok" não serve pra nada numa investigação, o que serve é o texto que ele efetivamente entregou pro próximo
Sintoma: você isolou a etapa suspeita mas teria que rodar tudo de novo pra testar
Você já sabe onde é o problema, beleza? Só que pra testar a correção você teria que reexecutar a cadeia inteira, do zero, queimando tempo e token em etapas que já estavam certas
A causa: sem persistência de estado, reexecutar significa repetir tudo
A solução: checkpoint
No LangGraph, o checkpointer salva o estado da thread a cada super-step e permite recuperar estados históricos, o que habilita replay, retomada após falha e time travel
O caminho é esse:
- use
get_state_historypra localizar o checkpoint do ponto que te interessa - chame
invokecom a config daquele checkpoint pra reproduzir dali pra frente - se quiser testar uma hipótese ("e se o dado que chegou aqui fosse esse outro?"), use
update_state, que aplica os valores usando os writers do nó indicado, registra no checkpoint que aquele nó produziu a atualização, e a execução segue a partir dos sucessores desse nó
Tome cuidado com uma coisa aqui: no replay, os nós ANTERIORES ao checkpoint não são reexecutados, porque os resultados já estão salvos
Mas os nós POSTERIORES rodam de verdade
Isso inclui chamadas de LLM, requisições de API e interrupts, ou seja, não é leitura de cache e o resultado pode sair diferente do original. Já vi gente concluir que "consertou" quando na real só pegou uma amostragem diferente do mesmo prompt
Quem usa LangSmith tem um atalho pra investigação interativa: dá pra abrir um trace, selecionar a run raiz e clicar em "Run in Studio"
Como prevenir: ligar checkpoint desde o primeiro dia do fluxo. Checkpoint é daquelas coisas que só parecem exagero até o dia em que você precisa reexecutar uma etapa e não pode
Sintoma: você ligou o tracing e nada aparece no backend
Esse aqui dói, porque você faz tudo certo na investigação e o painel te devolve o vazio 😅
A causa e a solução mudam por ferramenta, então vou separar
No OpenAI Agents SDK (Python):
O tracing vem ligado por padrão, sem configuração extra, e registra gerações do LLM, chamadas de ferramenta, handoffs e guardrails
Ou seja: se não tá aparecendo, alguém DESLIGOU. E são três formas de desligar:
- a variável de ambiente
OPENAI_AGENTS_DISABLE_TRACING=1 set_tracing_disabled(True)no códigoRunConfig.tracing_disabled=True, que mata o tracing só de uma execução específica
Tem uma quarta possibilidade que é a pegadinha de verdade: set_trace_processors() SUBSTITUI os processadores padrão. Nesse caso os traces deixam de ir pra OpenAI, a menos que você inclua um processador que faça isso
Se a sua intenção era só mandar os traces também pra outro lugar, o certo é add_trace_processor(), que adiciona um processador extra recebendo traces e spans
Já que você tá mexendo aí, duas coisas que valem a pena:
custom_span()pra marcar trechos próprios, com parâmetros comoname,data,span_id,parentedisabled. O span criado entra automaticamente no trace corrente, aninhado sob o span mais próximo (o controle é via contextvar do Python), então você não precisa costurar hierarquia na mãoRunConfig.trace_include_sensitive_datapra não capturar dado sensível nos traces
No Claude Code:
O Claude Code suporta métricas e eventos de OpenTelemetry, habilitados por variável de ambiente:
export CLAUDE_CODE_ENABLE_TELEMETRY=1
Daí você escolhe o exportador (otlp ou console) pra métricas e logs, e configura o endpoint OTLP, como a documentação de monitoramento mostra (o exemplo com gRPC aponta pra http://localhost:4317)
Pra saber se tá chegando, a documentação indica sinais bem concretos no backend:
- a métrica
claude_code.session.count, emitida no início da sessão - o evento
claude_code.user_prompt, que valida setup só de logs - se nada chegar mesmo assim, rode
claude --debuge procure erros de exportação OTel no log
Armadilha que pega geral: o Claude Code NÃO repassa as variáveis OTEL_* pros subprocessos que ele cria
Ferramenta Bash, hooks, servidores MCP e language servers não herdam a configuração de OpenTelemetry do processo pai
Então se você jurava que tinha cobertura completa e o buraco do trace é justo onde roda um MCP, achou o motivo. E, saindo um pouco do assunto, nem toda tarefa merece esse aparato todo: tem casos em que fazer na mão sai mais rápido que orquestrar e depois depurar a orquestração
O que eu vi rastreando um fluxo multiagente na prática
No vídeo abaixo eu monto do zero um fluxo de orquestração no n8n: um agente principal fazendo o papel de orquestrador e 2 subagentes ligados como ferramentas, um de pesquisa na internet e outro de consulta a uma planilha de vídeos do YouTube
Antes eu fazia multiagente por subfluxo, chamando um workflow separado que executava o agente
Funcionava, mas pra DEPURAR era pior, porque as coisas ficavam separadas em execuções diferentes e você ficava pulando de tela em tela pra remontar a história. O subfluxo ainda tem valor quando o objetivo é reaproveitar o mesmo agente em outros fluxos, mas em alguns casos eu prefiro manter tudo em um fluxo só justamente por isso
A parte que interessa aqui é o rastro
Quando perguntei sobre a versão do n8n, eu acompanhei na tela o caminho completo da execução: orquestrador, agente de pesquisa, ferramenta de busca, e volta pro orquestrador montar a resposta final
A resposta dessa rodada saiu desatualizada
E olha que legal: eu consegui localizar de onde veio o dado errado só observando por qual subagente e por qual ferramenta a execução passou. Não precisei abrir o prompt de todo mundo, o caminho já entregava a fronteira suspeita
Depois repeti o teste com uma pergunta sobre vídeos publicados: caiu no agente certo, acessou a planilha e devolveu os 3 vídeos que eu tinha pedido
Um detalhe importante do meu primeiro teste: mandei um cumprimento simples e o orquestrador não acionou nenhum subagente, respondeu direto. Eu li isso como sinal de que o roteamento estava se comportando
Mas foi sorte, e eu falo isso no vídeo com todas as letras
Conforme o fluxo cresce e ganha mais agentes, o orquestrador tende a se perder e chamar o agente errado. Por isso eu voltei no orquestrador e escrevi uma system message declarando o papel dele e listando os agentes disponíveis, com uma descrição de quando cada um deve ser usado
A descrição de cada subagente serve exatamente pra isso: pro orquestrador e o subagente se entenderem sobre quando aquele agente deve ser chamado
Depois da system message refiz os testes: pergunta de atualidade caiu no agente de pesquisa, pergunta de vídeos caiu no agente da planilha, e conversa solta não acionou agente nenhum
Outra coisa que eu achei massa: quando o orquestrador fica em dúvida, ele não entra em um agente qualquer, ele devolve uma pergunta pro usuário. Isso pra mim é sinal de que o prompt precisa melhorar, não de que o fluxo tá quebrado
E, no fim, eu validei o resultado de uma das respostas contra o que eu mesmo uso nos meus fluxos de produção, em vez de aceitar a saída do agente como verdade
Esse é o hábito que mais evita dor de cabeça: agente respondeu, ótimo, agora confere
(se a sua dúvida for onde rodar isso, eu já comparei n8n na nuvem contra self-hosted em outro post)
Próximo passo: instrumentar antes do próximo incidente
Recapitulando a ordem de ataque quando a orquestração entrega resultado errado e ninguém falhou:
- Trace único. Um trace ID propagado ponta a ponta, sem reiniciar na fronteira, pra você ver a árvore inteira (
invoke_agentna raiz,chatpor chamada de LLM,execute_toolpor ferramenta) - Atributos por etapa.
gen_ai.request.model,gen_ai.usage.input_tokens,gen_ai.usage.output_tokensegen_ai.response.finish_reasonspra saber o que cada etapa consumiu e por que parou - Fronteira, não agente. Comparar o que entrou e o que saiu de cada passagem, porque a informação some no handoff, não dentro do agente
- Checkpoint pra replay. Estado salvo pra reexecutar SÓ a etapa suspeita, lembrando que o que vem depois do checkpoint roda de verdade e pode dar resultado diferente
E o próximo passo concreto é bem chato de tão simples: liga o tracing e o checkpoint no fluxo que você já tem rodando HOJE, provoca uma execução qualquer e reexecuta uma etapa isolada de propósito
Treinar o método com o fluxo funcionando é MUITO mais tranquilo do que descobrir os comandos com o cliente esperando resposta
Quando o erro real chegar, você já vai saber onde clicar 🙂
até o próximo post!
Perguntas frequentes
Qual é a diferença entre o span invoke_agent e os spans chat nas convenções GenAI do OpenTelemetry?
invoke_agent é o span de nível superior, a raiz da árvore daquela execução de agente. Dentro dele ficam os spans chat, um pra cada chamada de LLM, e os spans execute_tool, um pra cada uso de ferramenta. É essa hierarquia que deixa claro, na hora de depurar, o que aconteceu dentro de qual agente.
Dá pra desativar o tracing do OpenAI Agents SDK quando eu não quiser rastrear uma execução?
Dá, e de três formas: variável de ambiente OPENAI_AGENTS_DISABLE_TRACING=1, set_tracing_disabled(True) direto no código, ou RunConfig.tracing_disabled=True pra desligar só numa execução específica. No SDK o tracing já vem ligado por padrão, então essas três opções são justamente pra quando você precisa desligar.
O Claude Code repassa a configuração de OpenTelemetry pra subprocessos como o Bash e servidores MCP?
Não. O Claude Code não repassa as variáveis OTEL_* pros subprocessos que ele cria, então ferramenta Bash, hooks, servidores MCP e language servers não herdam essa configuração do processo pai. Se você depende de rastrear o que roda dentro dessas ferramentas, isso precisa ser instrumentado à parte.
Como eu confirmo que a telemetria do Claude Code está realmente chegando no meu backend?
A documentação indica dois sinais concretos: a métrica claude_code.session.count, emitida no início da sessão, e o evento claude_code.user_prompt, que serve pra validar o setup só com logs. Se nenhum dos dois aparecer, o próximo passo é rodar claude –debug e procurar erro de exportação OTel no log.
Dá pra editar o estado de um agente no LangGraph antes de retomar a execução a partir de um checkpoint?
Dá, com update_state. Ele aplica os valores usando os writers do nó indicado, registra no checkpoint que aquele nó produziu a atualização, e a execução segue a partir dos sucessores desse nó. É o jeito de corrigir um dado errado sem reexecutar tudo do zero.
Por que reexecutar a partir de um checkpoint no LangGraph pode dar um resultado diferente da execução original?
Porque só os nós anteriores ao checkpoint não são reexecutados, já que o resultado deles já está salvo. Os nós posteriores rodam de novo de verdade, incluindo chamada de LLM, requisição de API e interrupt, e nada garante que a resposta do modelo ou da API externa vai ser idêntica à da primeira vez.
Formações
Formação Vibe Coding
Do Prompt ao Produto: Crie Software Real com IA
- 474 aulas
- 20 projetos
- 39h 27min
Blog | Mais populares
Como acompanhar uma orquestração de agentes que roda por horas sem ficar de babá?
Orquestração de agentes rodando por horas? Veja como acompanhar com hooks, transcripts JSONL, agent view e push no celular, sem virar babá do terminal.
Quando vale a pena usar vários agentes no Claude Code em vez de um só?
Vários agentes no Claude Code valem a pena? Veja quando dividir tarefas compensa (e quando só atrasa e encarece), com os 4 canais disponíveis na prática.
Pipeline vs paralelo na orquestração de agentes: quando usar cada padrão?
Orquestração de agentes define sucesso do seu sistema. Pipeline ou paralelo? Entenda latência, custo e throughput na prática com exemplos reais de 2026.
