Como evitar que múltiplos agentes gastem tokens discutindo entre eles

Um ciclo entre dois agentes, um gerando e outro revisando, não tem limite natural de parada embutido em lugar nenhum. Sem alguém definir explicitamente quando esse ciclo deveria terminar, ele só para quando alguém percebe, de fora, que já rodou tempo (e token) demais.

Max Iterations: o limite mais simples de implementar

A forma mais direta de evitar um ciclo indefinido é limitar o número de voltas permitidas, um contador simples que interrompe o ciclo depois de N tentativas, independente do resultado ter convergido ou não.

const int maxIteracoes = 5;
var iteracao = 0;
var resultado = await agenteGerador.ExecutarAsync(tarefa);

while (iteracao < maxIteracoes)
{
    var avaliacao = await agenteRevisor.AvaliarAsync(resultado);
    if (avaliacao.Aprovado)
        break;

    resultado = await agenteGerador.CorrigirAsync(resultado, avaliacao.Feedback);
    iteracao++;
}

Esse limite é uma rede de segurança, não uma solução de qualidade. Ele garante que o ciclo vai parar em algum ponto, mas não garante que o resultado na iteração final seja bom, só que o sistema vai parar de gastar token tentando melhorá-lo depois daquele ponto.

Termination conditions explícitas, além do limite de tentativas

Um limite de iterações sozinho trata todo ciclo igual, sem considerar se ele está progredindo ou travado. Uma condição de parada mais específica avalia se o critério de aprovação foi atingido (não só se o revisor aprovou, mas se aprovou com base em algo mensurável: passou num teste automatizado, atingiu uma pontuação mínima numa avaliação, satisfez uma regra explícita), permitindo parar mais cedo quando o resultado já está bom o suficiente, sem gastar as iterações restantes só porque o limite ainda não foi atingido.

Detectar repetição: quando o ciclo não está progredindo

Um padrão específico de falha, além de simplesmente não convergir, é o ciclo entrar em loop, o revisor pedindo essencialmente a mesma correção de novo, ou o gerador devolvendo uma resposta muito parecida com uma versão anterior já rejeitada. Detectar isso exige comparar a resposta atual com respostas anteriores dentro do mesmo ciclo, seja por comparação direta de texto, seja por similaridade semântica via embedding, interrompendo o ciclo quando a resposta nova é essencialmente igual a uma já rejeitada antes.

if (respostasAnteriores.Any(r => SimilaridadeCoseno(r, resultado) > 0.95))
{
    // ciclo não está progredindo, parar aqui
    break;
}

Esse tipo de verificação captura um caso que limite de iterações sozinho não captura: um ciclo pode estar bem abaixo do limite máximo e ainda assim já estar preso, repetindo a mesma correção sem avançar.

Orçamento de tokens por execução, não só por chamada

Limitar tentativas controla quantas voltas o ciclo dá, mas não controla diretamente quanto cada volta custa. Um orçamento de tokens definido para a execução inteira (não por chamada individual) permite interromper o ciclo quando o custo acumulado ultrapassa um limite aceitável para aquela tarefa, mesmo que o número de iterações ainda esteja dentro do permitido. Isso importa principalmente quando iterações diferentes não custam o mesmo: uma correção pequena pode consumir poucos tokens, uma reescrita completa pode consumir bem mais, e um limite só de iterações não captura essa diferença de custo real.

O que fazer quando o limite é atingido sem convergência

Nenhum desses mecanismos resolve o problema de fundo se, ao atingir o limite, o sistema simplesmente devolve o último resultado disponível como se fosse aprovado. Atingir o limite de iterações, de repetição ou de orçamento deveria disparar um caminho explícito: devolver a melhor versão obtida com uma marcação clara de que não houve aprovação completa, escalar para revisão humana, ou aplicar um fallback mais simples e seguro, em vez de deixar o resultado da última tentativa passar silenciosamente como se tivesse sido validado.

Parar não é falha do sistema, é parte do desenho dele

Um ciclo de agentes sem mecanismo de parada explícito não é mais robusto por continuar tentando indefinidamente, é só mais caro e mais difícil de prever quando vai realmente terminar. Definir de antemão quando parar, seja por número de tentativas, por critério de convergência, por repetição detectada ou por orçamento de token, faz parte do desenho do sistema tanto quanto definir o que ele deveria fazer quando está funcionando bem.