Blog

Roteamento de conversa entre agentes especializados sem perder o contexto

O cliente descreve o problema em quatro mensagens para o agente de suporte, o agente decide que aquilo é cobrança e transfere, e o agente de cobrança abre com "olá, em que posso ajudar?". Do ponto de vista do código nada falhou: a classificação estava certa, a transferência aconteceu, o segundo agente respondeu. Do ponto de vista do cliente, ele acabou de repetir tudo para a mesma empresa pela segunda vez. O erro não está no roteador, está na suposição de que trocar de agente é trocar de prompt. Quando um sistema tem agentes especializados, cada transferência é uma fronteira onde três coisas podem se perder de forma independente: o histórico literal, o estado já apurado e a autoridade sobre o que pode ser feito. Este artigo trata roteamento entre agentes como problema de transferência de estado: por que enviar a conversa inteira falha por um motivo diferente de enviar só um resumo, qual é a estrutura mínima de um pacote de transferência, como impedir que dois agentes fiquem empurrando o cliente um para o outro, e por que o roteador precisa de teste de regressão próprio, separado dos agentes que ele orquestra.

2026-08-16 / IA Aplicada / 15 min

01

Trocar de agente não é trocar de prompt

A arquitetura mais comum de multiagente em atendimento nasce de uma simplificação razoável: como o modelo é o mesmo e a diferença entre os agentes é a instrução, roteia-se trocando o prompt do sistema e mantendo o mesmo histórico de mensagens. Funciona por algumas semanas e depois começa a produzir dois sintomas opostos. Ou o segundo agente ignora o que já foi apurado e recomeça, ou herda tanto contexto do primeiro que continua se comportando como ele, respondendo sobre cobrança com o tom e as regras do suporte técnico.

Os dois sintomas têm a mesma origem. O histórico de mensagens carrega três informações misturadas que precisariam ser tratadas separadamente na transferência. A primeira é o que o cliente disse, que é fato e quase sempre deve seguir adiante. A segunda é o que o agente anterior concluiu, que é interpretação e precisa seguir marcada como tal, com quem concluiu e com que confiança. A terceira é o que o agente anterior prometeu ou executou, que é compromisso e não pode ser silenciosamente descartado, porque o cliente já ouviu. Passar o histórico bruto entrega as três com o mesmo peso. Passar só um resumo geralmente preserva a primeira, degrada a segunda e perde a terceira, que é justamente a que gera reclamação.

O que atravessa a fronteiraNaturezaO que acontece se someComo deve viajar
Falas do clienteFatoO cliente repete tudo e percebe a transferênciaÚltimos turnos literais mais resumo do anterior
Dados já coletadosFato verificadoO agente novo pede o CPF outra vezCampos tipados com origem e momento da coleta
Conclusões do agente anteriorInterpretaçãoPerde-se o diagnóstico e o trabalho recomeçaCampo marcado como hipótese, com autor e confiança
Promessas feitas ao clienteCompromissoO novo agente contradiz o anterior na frente do clienteLista explícita, sempre injetada, nunca resumida
Ações já executadasEfeito colateralA ação é repetida, com estorno ou pedido duplicadoRegistro idempotente consultável pelo agente novo
Autoridade e limitesPermissãoO agente novo herda poder que não deveria terDerivada do agente de destino, nunca herdada

A última linha é a que mais surpreende quem implementa. Se a transferência copia o contexto inteiro, ela tende a copiar junto as ferramentas disponíveis e as permissões concedidas. Um agente de suporte que ganhou autorização para emitir segunda via não deveria transferir essa autorização junto com a conversa para o agente de retenção, que talvez possa conceder desconto. Autoridade é atributo do agente de destino, calculada no momento da entrada, e não algo que viaja no pacote.

02

O pacote de transferência: estrutura mínima que funciona

A solução prática é parar de transferir conversa e passar a transferir um objeto explícito. O pacote tem uma parte literal, uma parte estruturada e uma parte de compromissos, e cada uma tem uma regra diferente de sobrevivência. A parte literal é curta e recente. A parte estruturada é o estado que já custou perguntas ao cliente. A parte de compromissos é pequena, mas nunca pode ser comprimida.

A distinção entre fato e hipótese no pacote não é preciosismo. Sem ela, o agente de destino trata "cliente relatou cobrança em duplicidade" e "provavelmente é uma assinatura antiga não cancelada" com o mesmo grau de certeza, e passa a afirmar ao cliente algo que o agente anterior apenas suspeitou. Esse é o caminho mais curto entre uma arquitetura elegante e uma reclamação formal.

// routing/handoff.js
// Pacote de transferencia entre agentes especializados.
// Regra central: fato, hipotese e compromisso viajam em campos distintos,
// e autoridade nunca viaja, e sempre derivada do agente de destino.

export function buildHandoff({ conversation, fromAgent, toAgent, reason }) {
  return {
    conversationId: conversation.id,
    from: fromAgent.id,
    to: toAgent.id,
    reason,
    hopCount: (conversation.handoff?.hopCount ?? 0) + 1,
    visited: [...(conversation.handoff?.visited ?? []), fromAgent.id],

    // Parte literal: turnos recentes preservados palavra por palavra.
    // O resto vira resumo, porque o custo cresce com a conversa inteira.
    recentTurns: conversation.turns.slice(-6).map((turn) => ({
      role: turn.role,
      text: turn.text,
      at: turn.at,
    })),
    earlierSummary: conversation.summary ?? null,

    // Parte estruturada: o que ja foi apurado e nao deve ser perguntado
    // de novo. Cada campo carrega origem, para o agente de destino saber
    // se pode afirmar ou se precisa confirmar.
    facts: conversation.facts
      .filter((fact) => fact.verified)
      .map(({ key, value, source, at }) => ({ key, value, source, at })),

    hypotheses: conversation.facts
      .filter((fact) => !fact.verified)
      .map(({ key, value, confidence, author }) => ({
        key,
        value,
        confidence,
        author,
      })),

    // Parte de compromisso: nunca resumida, nunca truncada.
    // O cliente ja ouviu isso e vai cobrar.
    commitments: conversation.commitments,
    executedActions: conversation.actions.map(({ name, idempotencyKey, at }) => ({
      name,
      idempotencyKey,
      at,
    })),
  };
}

// Autoridade e recalculada na entrada, nunca copiada do pacote.
export function grantsFor(toAgent, handoff, policy) {
  return policy.resolve({
    agent: toAgent.id,
    tier: handoff.facts.find((fact) => fact.key === 'customerTier')?.value,
  });
}

Repare no que o pacote não tem: nenhum campo de ferramentas, nenhuma lista de permissões e nenhuma referência ao prompt do agente anterior. O que atravessa é estado da conversa, não configuração do agente. Essa separação é o que permite adicionar um quarto agente meses depois sem revisar o comportamento dos três primeiros.

03

O laço de transferência e a garantia de terminação

Todo sistema com mais de dois agentes especializados acaba produzindo, mais cedo ou mais tarde, uma conversa que circula. Suporte decide que é cobrança, cobrança decide que é técnico, técnico decide que é suporte. Cada decisão isolada é defensável e nenhuma delas tem informação suficiente para perceber o ciclo, porque cada agente enxerga só o próprio turno. O laço não é bug de um agente, é propriedade emergente do conjunto, e por isso precisa ser resolvido fora deles.

A garantia mais simples e mais eficaz é fazer o pacote carregar o próprio histórico de roteamento e transformar terminação em invariante do sistema. Duas regras cobrem quase todos os casos reais: um agente já visitado não pode ser destino de novo na mesma conversa sem que algum fato novo tenha sido coletado desde a visita anterior, e existe um teto absoluto de saltos que, ao ser atingido, encaminha para humano em vez de tentar mais um agente.

Cliente -> [Roteador] -> Suporte
                          |
          fato novo? nao  |  visitado?  sim
                          v
              Suporte -> [Roteador] -> Cobranca
                                |
                  hops = 3, teto = 3
                                v
                        [Fila humana]

Invariantes verificados no roteador, nao nos agentes:
  1. destino != agente atual
  2. destino in visited  =>  exige fato novo desde a ultima visita
  3. hopCount < maxHops  senao  destino = humano

A condição do "fato novo" é o detalhe que separa uma trava útil de uma trava que atrapalha. Sem ela, um retorno legítimo fica bloqueado: o cliente foi ao técnico, o técnico descobriu que a assinatura está suspensa e agora precisa voltar para cobrança com uma informação que antes não existia. Com ela, esse retorno passa e o ciclo improdutivo, no qual ninguém apurou nada entre uma transferência e outra, para na segunda tentativa.

// routing/router.js
// O roteador decide o destino e garante terminacao. Os agentes nao
// conhecem a topologia: eles apenas declaram que nao e com eles.

const MAX_HOPS = 3;

export function resolveTarget({ proposed, handoff, factsAtLastVisit }) {
  if (handoff.hopCount >= MAX_HOPS) {
    return { target: 'human', reason: 'hop_limit' };
  }

  if (proposed === handoff.from) {
    return { target: 'human', reason: 'self_handoff' };
  }

  const alreadyVisited = handoff.visited.includes(proposed);
  if (alreadyVisited) {
    const known = factsAtLastVisit[proposed] ?? [];
    const current = handoff.facts.map((fact) => fact.key);
    const hasNewFact = current.some((key) => !known.includes(key));

    if (!hasNewFact) {
      return { target: 'human', reason: 'loop_without_progress' };
    }
  }

  return { target: proposed, reason: 'routed' };
}

04

Quem decide o destino: o agente, um classificador ou o supervisor

Há três desenhos possíveis para a decisão de roteamento, e a escolha determina o que dá para depurar quando algo dá errado. No primeiro, cada agente decide para quem transferir. No segundo, um classificador dedicado lê a conversa e escolhe. No terceiro, um agente supervisor mantém o controle e delega tarefas aos especialistas sem nunca sair da conversa.

DesenhoCusto por transferênciaOnde falhaQuando compensa
Agente decide o destinoZero, cabe na mesma chamadaCada agente precisa conhecer todos os outros e a topologia envelheceAté três ou quatro especialidades estáveis
Classificador dedicadoUma chamada curta e barata, modelo pequenoDecide sem enxergar o que o agente apurou no turnoMuitas especialidades e roteamento na entrada
Supervisor com delegaçãoAlta, o contexto do supervisor cresce a cada delegaçãoA janela do supervisor vira o gargalo de custo e latênciaTarefas compostas que precisam de síntese final

Na prática, o desenho que mais se sustenta em atendimento é híbrido e explora uma assimetria: a primeira decisão e as seguintes são problemas diferentes. A entrada da conversa é classificação pura, com pouco contexto e alto volume, e um classificador barato resolve bem. As transferências seguintes acontecem depois que um especialista já trabalhou e formou uma opinião, e nesse ponto ele é a fonte mais informada sobre o destino, desde que declare o motivo em vez de apontar o nome do próximo agente.

A distinção entre declarar motivo e apontar destino é o que mantém a topologia editável. Quando o agente devolve "assunto é cobrança de fatura recorrente", o roteador traduz isso para o agente correto usando uma tabela que uma pessoa consegue alterar sem tocar em nenhum prompt. Quando o agente devolve "transferir para agente-cobranca-v2", o nome do destino passa a viver dentro do texto de vários prompts, e renomear ou dividir um agente vira uma migração de conteúdo.

05

O custo escondido: cada transferência reprocessa a conversa

Uma consequência raramente orçada é que transferir agente invalida o prefixo em cache. O agente de destino tem outro prompt de sistema, outro conjunto de ferramentas e recebe um pacote de contexto diferente, o que significa que nada do prefixo anterior é reaproveitado. Uma conversa que sofre três transferências paga o custo de entrada quatro vezes, e a quarta é a mais cara porque carrega o acumulado das anteriores.

Esse é o argumento econômico mais forte a favor do pacote estruturado sobre o histórico bruto. Passar o histórico inteiro faz o custo por transferência crescer com o tamanho da conversa, que é exatamente a variável que cresce quando o atendimento está difícil. Passar campos estruturados mais os últimos turnos faz o custo por transferência ficar aproximadamente constante, independente de a conversa ter dez ou sessenta turnos.

EstratégiaTokens de entrada na terceira transferênciaRisco de perdaCache de prefixo
Histórico bruto completoCresce linearmente com a conversaBaixo para fato, alto para foco do agenteInvalidado a cada troca
Resumo livre gerado por modeloBaixo e estávelAlto: compromisso e dado coletado somem no resumoInvalidado a cada troca
Pacote estruturado mais turnos recentesEstável, cresce só com campos apuradosBaixo, se compromisso nunca for resumidoInvalidado, mas com prefixo menor

Vale registrar a métrica que expõe o problema antes da fatura: custo por conversa resolvida segmentado por número de transferências. Se a conversa de três saltos custa cinco vezes a de zero saltos e resolve na mesma proporção, o roteamento está funcionando. Se custa cinco vezes e resolve menos, cada transferência está degradando o contexto, e o número de saltos virou proxy de fracasso em vez de especialização.

06

Testar o roteador separado dos agentes

O erro de avaliação mais comum nesse tipo de sistema é medir só a resposta final. Quando ela sai errada, não dá para saber se o roteador escolheu o agente errado ou se o agente certo respondeu mal, e o time acaba ajustando o prompt do especialista para compensar um problema de roteamento. Roteador e agente precisam de conjuntos de avaliação separados, porque falham por motivos diferentes e são corrigidos em lugares diferentes.

O conjunto do roteador é barato de montar e de rodar: entrada de conversa e destino esperado, sem executar nenhum agente. O ponto delicado é como rotular. Rotular pelo destino que o sistema escolheu na produção só congela o comportamento atual, inclusive os erros. O rótulo útil vem do desfecho: conversas que foram resolvidas sem nova transferência confirmam o destino, e conversas que sofreram transferência logo em seguida são candidatas a rótulo corrigido, com o segundo destino como resposta certa.

// routing/eval-router.js
// Avalia so a decisao de roteamento, sem executar os agentes.
// Metrica principal: acerto no primeiro destino. Metrica de guarda:
// taxa de transferencia imediata, que revela roteamento ruim mesmo
// quando a resposta final acabou saindo aceitavel.

export async function evaluateRouter(cases, route) {
  const result = { total: cases.length, correct: 0, byIntent: {} };

  for (const testCase of cases) {
    const decision = await route(testCase.conversation);
    const hit = decision.target === testCase.expectedTarget;

    const bucket = (result.byIntent[testCase.intent] ??= { total: 0, correct: 0 });
    bucket.total += 1;
    if (hit) {
      bucket.correct += 1;
      result.correct += 1;
    }
  }

  result.accuracy = result.correct / result.total;

  // Acerto agregado esconde especialidade rara com desempenho ruim,
  // e especialidade rara costuma ser a de maior impacto por conversa.
  result.worstIntent = Object.entries(result.byIntent)
    .map(([intent, bucket]) => ({ intent, accuracy: bucket.correct / bucket.total }))
    .sort((a, b) => a.accuracy - b.accuracy)[0];

  return result;
}

Há ainda um teste que não é de acerto e sim de integridade do pacote, e que costuma pegar mais defeito que o eval de destino. Ele pega conversas reais, monta o pacote de transferência e verifica se todo dado que o cliente forneceu antes da fronteira continua recuperável depois dela. É um teste determinístico, roda sem chamar o modelo e falha exatamente no caso que gera a pior experiência: o cliente que informa o número do pedido no terceiro turno e ouve o pedido de novo no sétimo.

  1. Rode o eval de roteamento em todo merge que toque a tabela de motivos, o classificador ou a descrição das especialidades.
  2. Rode o teste de integridade do pacote em todo merge que toque a construção do handoff ou o esquema de fatos.
  3. Rode o eval dos agentes especialistas com o pacote de entrada fixado, para separar regressão do agente de regressão do roteador.
  4. Monitore em produção a taxa de transferência imediata e o número médio de saltos por conversa resolvida.
  5. Reveja o teto de saltos quando a fila humana receber muito encaminhamento por limite, porque isso indica topologia mal desenhada e não cliente difícil.

FAQ

Perguntas frequentes

Vale a pena ter agentes especializados ou é melhor um agente único com todas as ferramentas?

Depende de quantas ferramentas e de quanta regra conflitante existe. Até algo em torno de dez ou quinze ferramentas com políticas compatíveis, um agente único costuma vencer, porque elimina a fronteira de transferência e todo o custo associado. A especialização compensa quando as políticas conflitam de fato, por exemplo quando um domínio exige confirmação explícita antes de qualquer ação e outro exige agilidade sem confirmação, ou quando a autoridade precisa ser diferente por domínio. Nesse caso, o agente único tende a produzir um prompt cheio de exceções que degrada em todos os cenários ao mesmo tempo.

Como saber se uma transferência foi boa ou ruim depois que aconteceu?

Um sinal simples e forte é a transferência imediata: se o agente de destino transfere de novo nos dois primeiros turnos, o roteamento anterior provavelmente errou. Um segundo sinal é a repetição de pergunta, detectável comparando os campos coletados antes e depois da fronteira, que aponta perda de contexto em vez de erro de destino. O terceiro é a resolução por número de saltos, que precisa cair pouco a cada salto. Se ela despenca do primeiro para o segundo salto, o problema está no pacote e não na escolha do agente.

O pacote de transferência deve incluir o resumo gerado por modelo ou só campos estruturados?

Os dois, com papéis diferentes. Os campos estruturados carregam o que não pode se perder: dados coletados, compromissos assumidos e ações já executadas, e eles nunca devem passar por sumarização. O resumo cobre a parte narrativa antiga da conversa, que dá contexto de tom e histórico sem precisar de fidelidade literal. A regra prática é que nenhum dado que o cliente forneceu deve existir apenas dentro do resumo, porque sumarização é lossy por definição e a perda cai justamente sobre detalhe específico, que é o formato de quase todo dado útil.

A fronteira entre agentes é infraestrutura, não detalhe de prompt

Roteamento entre agentes especializados falha em um lugar bem definido: na fronteira em que o estado da conversa precisa atravessar de um agente para outro. Separar fato de hipótese e de compromisso dentro de um pacote explícito, recalcular autoridade no destino em vez de herdá-la, garantir terminação no roteador com teto de saltos e exigência de fato novo, deixar o agente declarar motivo em vez de apontar destino, e avaliar roteador e especialistas em conjuntos separados transforma um sistema que empurra o cliente entre filas num sistema que realmente especializa. Posso mapear as fronteiras do seu fluxo atual, definir o pacote de transferência a partir do que os seus agentes já coletam e montar o eval de roteamento com o rótulo derivado do desfecho real das conversas.