Como passar contexto entre agentes de IA sem perder informação no caminho

Passar contexto entre agentes quebra quando cada etapa devolve texto livre e a próxima reinterpreta tudo de novo. No Claude Agent SDK, só a mensagem final do subagente volta pro pai, e o único canal de ida é a string de prompt da ferramenta Agent, então o handoff precisa ser contrato, não conversa. Ativando saída estruturada com outputFormat e um JSON Schema, o SDK valida, refaz o prompt em caso de divergência e entrega o dado em message.structured_output. O mesmo padrão aparece no OpenAI Agents SDK (input_filter, output_type) e no LangGraph (estado tipado com reducer por chave)
Fala aí, beleza? Cadeia de agentes é o telefone sem fio mais caro que você já montou 😀
O primeiro agente pesquisa, escreve três parágrafos bonitos e entrega
O segundo lê aquilo, interpreta do jeito dele, resume e passa adiante
O terceiro recebe um resumo de um resumo e decide alguma coisa em cima disso…
No fim, ninguém sabe direito onde o dado sumiu
E aqui vai a parte que quase todo mundo erra no diagnóstico: o problema normalmente NÃO é o modelo, é o canal do handoff. Enquanto cada etapa devolver prosa, cada etapa seguinte vira uma nova chance de reinterpretar. Neste post a gente fecha esse canal: transforma a saída de cada agente num contrato validado, no Claude Agent SDK, no OpenAI Agents SDK, no LangGraph e direto na API
Bora ver na prática?
Por que texto livre degrada a cadeia de agentes
Texto livre não tem contrato
Se o agente 1 pode escrever qualquer coisa, o agente 2 precisa ADIVINHAR o que era importante ali dentro. Isso não é bug de prompt, é falta de formato
E tem um agravante medido: a pesquisa Context Rot, da Chroma, avaliou 18 modelos de ponta e concluiu que o desempenho degrada de forma relevante conforme o input cresce, inclusive em tarefas simples de recuperação e replicação de texto. Ou seja, a suposição de que o modelo processa o contexto de forma uniforme não se sustenta
Pior: a degradação aumenta conforme cai a similaridade entre a informação buscada e a pergunta, que é justamente o cenário do mundo real, onde correspondência exata é rara
Se você já sentiu isso ao revisar textos longos sem perder o contexto, é a mesma física: quanto mais material bruto empilhado, mais coisa escapa
O que acontece quando os agentes conversam demais:
A própria Anthropic relatou o efeito no sistema de pesquisa multi-agente dela: versões iniciais dos agentes chegavam a gerar 50 subagentes para consultas simples e se distraíam com atualizações excessivas entre eles. A principal alavanca de correção foi o prompt de cada agente
Formação Vibe Coding
Do Prompt ao Produto: Crie Software Real com IA
- 474 aulas
- 20 projetos
- 39h 27min
Se liga que a lição é dupla
Mais troca não é mais informação, é mais ruído
E o formato do que trafega é decisão de projeto, não detalhe
A régua que dá o tom:
Nesse mesmo sistema, cada subagente pode gastar dezenas de milhares de tokens explorando, mas devolve apenas um resumo destilado, com frequência de 1.000 a 2.000 tokens
Guarda esse número, porque ele é o modelo mental do post inteiro: exploração é barata dentro do subagente, o que atravessa a fronteira é caro
O que você precisa antes de montar o handoff
Antes de escrever qualquer linha, o mais importante é entender ONDE o subagente vive e o que ele enxerga de verdade
Onde os subagentes vivem:
No Claude Agent SDK, subagentes são definidos programaticamente pela opção agents dentro das options do query(), usando o tipo AgentDefinition (com os campos description, prompt, tools, model, skills e mcpServers)
No Claude Code, subagentes são arquivos Markdown com frontmatter YAML guardados em .claude/agents/.md (nível de projeto) ou ~/.claude/agents/.md (nível de usuário)
E tem uma regra de desempate que já me pouparia tempo se eu soubesse antes: agente definido programaticamente tem PRECEDÊNCIA sobre agente carregado do sistema de arquivos com o mesmo nome. Se você jurava que editou o .md e nada mudou, é provavelmente isso
O que o subagente enxerga (e o que não enxerga):
O subagente recebe:
- o system prompt próprio dele
- a string de prompt da ferramenta Agent
- o
CLAUDE.mddo projeto (viasettingSources) - as definições de ferramentas herdadas
E o que ele NÃO recebe: o histórico da conversa do agente pai e o system prompt do pai
Ou seja, o único canal do pai para o subagente é a string de prompt da ferramenta Agent
Do outro lado, na volta, cada subagente roda com contexto próprio e só a mensagem final dele volta pro pai. Os resultados intermediários de ferramenta ficam lá dentro, morrem com o subagente
Então o desenho é este: uma porta estreita na ida, uma porta estreita na volta. Todo o resto é você achando que o contexto viajou junto
Passo a passo: montando um handoff que não perde informação
- Mapear a fronteira antes de codar. Pega papel (ou um comentário no topo do arquivo mesmo) e escreve duas colunas: o que PRECISA atravessar e o que pode ficar dentro do subagente. Como só a mensagem final volta, tudo que não estiver nela simplesmente não existe pro resto da cadeia. O erro comum deste passo: assumir que o pai vai "ver" o que o subagente leu com o Read ou o Grep, quando aquilo nunca sai de lá
- Escrever o prompt da ferramenta Agent como um briefing autossuficiente. Como ele é o canal único, o prompt precisa carregar o objetivo, os dados de entrada já destilados e o formato esperado de saída. Nada de "continue o que a gente estava fazendo", porque não existe "estávamos" pro subagente. O erro comum: escrever o prompt como se fosse mais uma mensagem numa conversa em andamento
const result = query({
prompt: "Analise os achados da etapa de coleta e classifique cada item",
options: {
agents: {
classificador: {
description: "Classifica achados de coleta em categorias",
prompt: "Você classifica achados. Nunca invente item novo.",
tools: ["Read", "Grep"],
model: "sonnet"
}
}
}
})
- Definir o schema da etapa ANTES do código. O contrato vem primeiro: quais campos a etapa devolve, quais são obrigatórios, o que é lista e o que é string. Se você conhece a ideia de tipar a resposta de uma API antes de consumir ela, é exatamente o mesmo raciocínio aplicado ao agente. O erro comum: schema largo demais, cheio de campo opcional, que na prática volta a ser texto livre com chaves em volta
const schema = {
type: "object",
additionalProperties: false,
required: ["itens", "resumo"],
properties: {
resumo: { type: "string" },
itens: {
type: "array",
items: {
type: "object",
additionalProperties: false,
required: ["titulo", "categoria", "fonte"],
properties: {
titulo: { type: "string" },
categoria: { type: "string" },
fonte: { type: "string" }
}
}
}
}
}
- Ativar a saída estruturada e ler o campo certo. No Claude Agent SDK, você passa o JSON Schema pela opção
outputFormat(TypeScript) ououtput_format(Python) noquery(), comtype: json_schema. O SDK valida o resultado contra o schema e refaz o prompt em caso de divergência, e o dado validado chega no campostructured_outputda mensagem de resultado. O erro comum: continuar fazendo parse do texto da resposta na mão, ignorando ostructured_outputque já veio validado
> Atenção pra não confundir CAMADA aqui, porque o nome se repete e confunde mesmo: esse outputFormat / output_format é a OPÇÃO do Claude Agent SDK, no query(), e é assim que se liga saída estruturada por lá. Não é o mesmo que o campo output_format da API da Claude, que virou output_config.format (esse eu explico mais pra frente, na seção da API). Mesmo nome, lugares diferentes
const result = query({
prompt: "Classifique os achados abaixo",
options: {
outputFormat: {
type: "json_schema",
schema
}
}
})
for await (const message of result) {
if (message.type === "result") {
const dados = message.structured_output
// dados já validado contra o schema
}
}
- Tratar a falha do contrato. Se o agente não conseguir produzir saída válida pro schema, o SDK devolve um resultado de erro com o subtype
error_max_structured_output_retries. Isso é um sinal bom, não uma tragédia: significa que a cadeia parou em vez de seguir com lixo. Trata esse caso explicitamente, seja simplificando o schema, seja deixando o prompt do agente mais direto sobre o formato. O erro comum: não checar o subtype e deixar a próxima etapa rodar em cima de um resultado que nunca chegou
- Encadear destilando, não despejando. A saída validada de uma etapa vira campo do prompt da próxima, e só os campos que a próxima precisa. Aqui é onde a régua da destilação entra: o subagente pode ter queimado dezenas de milhares de tokens explorando, mas o que atravessa é o resumo. É a mesma lógica de quem usa contexto longo com RAG em vez de jogar a base inteira na janela, ou seja, busca o que interessa e leva só isso adiante. O erro comum: serializar o objeto inteiro e colar no prompt seguinte "por segurança", que é justamente o caminho de volta pro input gigante que degrada
const briefing = dados.itens
.filter(i => i.categoria === "critico")
.map(i => `- ${i.titulo} (${i.fonte})`)
.join("\n")
const proximaEtapa = query({
prompt: `Escreva o plano de ação para os itens críticos abaixo:\n${briefing}`,
options: { outputFormat: { type: "json_schema", schema: schemaDoPlano } }
})
O mesmo handoff em outros frameworks (OpenAI Agents SDK e LangGraph)
O padrão é o mesmo em todo lugar: definir o que atravessa e validar o formato. O que muda é a mecânica, e ela muda MUITO
| Framework | O que atravessa por padrão | Onde você controla |
|---|---|---|
| Claude Agent SDK | Só a mensagem final do subagente volta pro pai; na ida, só a string de prompt da ferramenta Agent | outputFormat / output_format com JSON Schema |
| OpenAI Agents SDK | Todo o histórico da conversa vai pro agente que recebe o handoff | input_filter (ou filtro global no RunConfig) e output_type |
| LangGraph | Estado compartilhado tipado entre os nós | Reducer por chave no estado |
- No OpenAI Agents SDK, comece assumindo o oposto do Claude Agent SDK. Por padrão, o agente que recebe o handoff enxerga TODO o histórico da conversa. Não é pouca coisa: você sai de "falta contexto" pra "contexto demais", que é o cenário que o Context Rot descreve. O erro comum: migrar mentalmente de um SDK pro outro achando que o default é o mesmo
- Use o
input_filtercomo o ponto de corte. Ele é uma função que recebe umHandoffInputDatae devolve um novoHandoffInputData, controlando o que segue pro próximo agente. Se o corte é uma política geral da sua aplicação, dá pra aplicar um filtro global no nível doRunConfig. O erro comum: filtrar em um handoff só e deixar os outros passando o histórico inteiro
def filtrar(data: HandoffInputData) -> HandoffInputData:
# devolve um novo HandoffInputData só com o que a próxima etapa precisa
...
- Ligue a saída estruturada com
output_type. Passaroutput_typeao agente faz o modelo usar saída estruturada em vez de texto puro, que é exatamente o contrato que a gente quer no fim de cada etapa. O erro comum: filtrar a entrada com carinho e deixar a saída em prosa, ou seja, resolver metade do handoff
agente = Agent(
name="classificador",
output_type=ResultadoDaEtapa,
)
- No LangGraph, pense em estado, não em mensagem. Os nós de um
StateGraphse comunicam lendo e escrevendo em um estado compartilhado tipado, que pode ser umTypedDictou um modelo Pydantic. Cada nó recebe o estado atual e devolve um dicionário de atualizações. O erro comum: tentar reproduzir "conversa entre agentes" quando o modelo do framework já é um contrato tipado, e você só precisa preencher os campos certos
from typing import TypedDict
class Estado(TypedDict):
achados: list
resumo: str
def no_de_coleta(estado: Estado) -> dict:
return {"achados": [...]}
- Cuide do reducer de cada chave. No LangGraph, cada chave do estado pode ter um reducer que define como combinar valor antigo e valor novo, e o reducer padrão é SUBSTITUIÇÃO. Esse é o clássico "perdi informação no caminho" que não é culpa do modelo: dois nós escrevem na mesma chave e o segundo apaga o primeiro. O erro comum: deixar no padrão uma chave que devia acumular, tipo uma lista de achados de várias etapas
Contrato do lado da API: JSON outputs e strict tool use
Se a sua orquestração é feita na mão, direto na API da Claude, o contrato existe do mesmo jeito
E olha que aqui é OUTRA camada: não é mais opção de SDK, é campo da requisição
Structured Outputs na API está em disponibilidade geral e não exige mais o cabeçalho beta. O parâmetro passou de output_format para output_config.format, e o formato antigo segue funcionando por um período de transição
- Escolha o modo certo pro que você quer garantir. São dois modos complementares: JSON outputs, via
output_config.format, para a resposta do modelo, e strict tool use, comstrict: true, que garante validação de schema no nome e nos inputs da ferramenta. O erro comum: validar a resposta final e deixar a chamada de ferramenta solta, que é justamente onde a cadeia costuma quebrar
- Ajuste o schema pro subconjunto aceito no modo strict. Com
strict: true, o schema precisa seguir o subconjunto de JSON Schema aceito:additionalPropertiesemfalseem TODO objeto e todas as propriedades listadas emrequired. O erro comum: reaproveitar um schema que você já tinha, cheio de campo opcional e objeto aberto, e levar erro de validação sem entender o motivo
{
"type": "object",
"additionalProperties": false,
"required": ["acao", "alvo"],
"properties": {
"acao": { "type": "string" },
"alvo": { "type": "string" }
}
}
Tome cuidado com o hábito de deixar campo opcional "por flexibilidade": no modo strict, todas as propriedades vão em required, então o desenho do contrato tem que ser pensado, não copiado
Quando vale (e quando não vale) quebrar em vários agentes
Agora a parte que ninguém gosta de ouvir: nem toda tarefa merece uma cadeia de agentes
Na avaliação interna da Anthropic, a arquitetura multi-agente com Claude Opus 4 como líder e subagentes Claude Sonnet 4 superou o Claude Opus 4 sozinho em 90,2%, consumindo cerca de 15 vezes mais tokens que uma interação de chat
Se liga nos DOIS números juntos, porque um sozinho engana
Onde a conta fecha:
- tarefa paralelizável, com várias frentes de exploração independentes
- tarefa cara em busca, onde vale queimar token explorando pra achar pouca coisa relevante
- etapas com responsabilidades bem diferentes, que se beneficiam de system prompts próprios
Onde a conta não fecha:
- tarefa curta e linear, que um agente só resolve numa passada
- fluxo onde cada etapa depende do resultado imediato da anterior, sem nada pra paralelizar
- cadeia montada "porque é multi-agente", que é como acabar com 50 subagentes numa consulta simples
E o que torna a conta viável quando ela fecha é o formato de retorno: subagente explorando com dezenas de milhares de tokens e devolvendo de 1.000 a 2.000 destilados
Se cada subagente seu devolve tudo que leu, você não montou uma arquitetura, montou um funil entupido 😛
Conclusão
Handoff é contrato, não conversa
No Claude Agent SDK a fronteira já é estreita por construção (só a mensagem final volta, e o prompt da ferramenta Agent é o único canal de ida), no OpenAI Agents SDK ela é larga por padrão e você fecha com input_filter, e no LangGraph ela é um estado tipado onde o reducer decide quem acumula e quem substitui
Muda a mecânica, não muda a ideia: definir o que atravessa e validar o formato
E lembra da camada: no SDK o contrato se liga na opção do query(), na API ele se liga no campo da requisição
O próximo passo é bem concreto
Pega a etapa mais frágil da sua cadeia hoje, aquela que você já sabe qual é
Escreve o schema dela, troca o retorno em prosa por saída estruturada validada e observa se o erro some
Se aparecer error_max_structured_output_retries, ótimo: a cadeia parou de mentir pra você
té o próximo post! =)
Perguntas frequentes
Como o OpenAI Agents SDK passa contexto entre agentes num handoff?
Por padrão, o agente que recebe o handoff enxerga todo o histórico da conversa, o que já resolve boa parte do telefone sem fio. Quando isso é demais (ou informação sensível demais), dá pra usar um input_filter, uma função que recebe um HandoffInputData e devolve um novo HandoffInputData controlando o que segue adiante. Também existe filtro global no nível do RunConfig, pra aplicar a regra em todos os handoffs de uma vez.
Dá pra forçar saída estruturada no OpenAI Agents SDK como se faz no Claude Agent SDK?
Dá sim: passando output_type ao agente, o modelo usa saída estruturada em vez de texto puro. É o equivalente direto à opção outputFormat (TypeScript) ou output_format (Python) do query() no Claude Agent SDK, só que resolvido no nível do agente em vez do query().
Como funciona a troca de contexto entre nós no LangGraph?
No LangGraph os nós de um StateGraph não trocam mensagem entre si: eles leem e escrevem num estado compartilhado tipado, como um TypedDict ou um modelo Pydantic. Cada nó recebe o estado atual e devolve um dicionário de atualizações, e cada chave pode ter um reducer definindo como combinar valor antigo e novo (o padrão é simplesmente substituir).
Qual a diferença entre output_config.format e strict: true na API da Claude?
São dois modos complementares de Structured Outputs, hoje em disponibilidade geral e sem exigir cabeçalho beta. O output_config.format entrega o JSON output em si, e é o parâmetro atual da API (ele sucedeu o campo output_format da própria API, que ainda funciona por um período de transição). Cuidado pra não confundir camadas: esse output_format em transição é o campo da API, não a opção outputFormat / output_format do query() no Claude Agent SDK, que continua sendo a forma de ativar saída estruturada por lá. Já o strict: true garante validação de schema em nome e inputs de ferramenta, e exige additionalProperties: false em todo objeto com todas as propriedades listadas em required.
O que acontece se o agente não conseguir gerar uma saída que bate com o schema?
No Claude Agent SDK, o próprio SDK valida o resultado contra o schema e refaz o prompt automaticamente quando há divergência. Se mesmo assim não rolar, ele devolve um resultado de erro com o subtype error_max_structured_output_retries, então esse é o sinal de que o schema tá largo demais ou pedindo algo que o agente não tem como responder.
Vale a pena usar arquitetura multi-agente pra qualquer tarefa?
Não necessariamente. Na avaliação interna da Anthropic, o time líder com Opus 4 coordenando subagentes Sonnet 4 superou o Opus 4 sozinho em 90,2%, mas consumindo cerca de 15 vezes mais tokens que uma interação de chat simples. Pra tarefa que um único agente resolve bem, multiplicar agente é só multiplicar custo (e risco de ruído no handoff).
Formações
Formação SAAS com IA
Tire usas ideias do papel criando softwares com IA, integre pagamentos e lance seu projeto!
- 291 aulas
- 18 projetos
- 24h 17min
Blog | Mais populares
As diferenças de var, let e const
Como fazer redirecionamento com PHP
Neste artigo você vai aprender a como fazer redirecionamento com PHP, utilizaremos abordagens fáceis de entender e de aplicar Fala programador(a), beleza? Bora aprender mais […]
Checklist de segurança n8n VPS pública: guia essencial para proteger sua instalação
Checklist de segurança n8n VPS pública: guia essencial para proteger sua instalação A popularidade da automação de processos com o n8n está em alta, principalmente […]
