Why NextWhy Next
Voltar à lista
💻 Dev10 min de leitura

O pipeline que põe outra IA para refutar o código que uma IA corrigiu

Se você pede ao mesmo modelo para revisar o próprio código, ele aprova de novo a conclusão que ele mesmo tirou. Um desenvolvedor solo, que escreve quase todo o código com IA, conta como transformou em pipeline permanente o loop de verificação cruzada em que uma IA manda a outra 'tentar refutar' o trabalho. O caso real em que três rodadas seguidas pegaram bugs que o self-review deixou passar, e o desenho dos prompts.

Resumo

Uma IA que julga o próprio trabalho é generosa. Quando você manda outro modelo 'assumir que esta correção está errada e refutá-la', aparecem os defeitos que o self-review deixou passar. O caso de um bug de logout que rendeu três rodadas seguidas de defeitos cada vez mais profundos, o desenho do prompt (enquadramento de refutação, formato de evidência) e o critério de quando rodar esse loop.

Nesta página

Quem já mandou um agente de IA fazer revisão de código sabe. Código dos outros, ele pega bem. Mas peça para revisar o código que ele mesmo acabou de escrever e ele fica estranhamente generoso. Volta um "a implementação está de acordo com a intenção" e, quando você roda de verdade, o bug continua lá.

Faz sentido. Dentro do mesmo modelo e do mesmo contexto, o equívoco cometido na hora de escrever o código se repete igualzinho na hora de revisar. É uma estrutura em que ele aprova de novo a própria conclusão, então aquilo é menos uma revisão e mais um carimbo no próprio parecer.

Por isso tirei o julgamento para fora. Passo o código que o Claude corrigiu para o Codex e faço um único pedido. "Assuma que esta correção está errada e tente refutá-la."

Três rodadas seguidas de bugs cada vez mais profundos

A primeira vez que senti o valor desse loop foi com um bug de logout. O sintoma era simples: às vezes, apertar o botão de logout não deslogava. O Claude achou a causa e corrigiu, e lendo só o código parecia plausível. Eu quase fiz o merge.

Mandei o Codex refutar e, na primeira rodada, veio esta resposta. A correção em si estava certa, mas o redirecionamento da página de autenticação interceptava o fluxo antes de aquele caminho de código ser alcançado, então a correção nunca tinha chance de executar. O código corrigido era código morto.

Corrigi também o redirecionamento e pedi refutação de novo. Na segunda rodada, saiu outra. Dessa vez o problema era a flag de "processando" colocada para evitar execução duplicada. Em certos timings, por causa desse guard, a própria requisição de logout virava um no-op que não fazia nada. Só na terceira rodada veio um "não encontrei o que refutar", e só então fiz o merge.

Nas três rodadas, era código que tinha passado no self-review. E nas três rodadas o refutador pegou o problema porque procurava não "o código parece bom?", e sim "existe um cenário em que essa alegação desmorona?".

Depois disso o padrão se repetiu. Houve uma correção de servidor em que, mesmo depois de dois self-reviews, a verificação cruzada pegou um valor que faltava numa constraint do DB, e uma migração foi adicionada. Numa correção de lógica de retry, o refutador devolveu com veredito de bloqueio um caso de borda que classificava errado uma certa resposta de timeout como falha permanente. Todos eles eram do tipo "plausível o bastante para quase passar".

Não peço revisão, peço refutação

No desenho do prompt, o que mais importa é o enquadramento. Se você diz "revise isso", o modelo devolve uma resposta morna, metade elogio, metade apontamento menor. Se você diz "assuma que esta correção está errada e refute-a; só aprove se a refutação falhar", a postura muda. Porque a aprovação deixa de ser o padrão e passa a ser a consequência de uma refutação fracassada.

Somo três exigências a isso.

  1. Forço um formato de evidência. Todo apontamento tem que vir com arquivo e número de linha, mais um cenário de reprodução em que o defeito estoura de verdade. "Esta parte parece arriscada" não é aceito. Se não dá para montar o cenário, a suspeita não é apontamento, é descarte. Na prática, é bem frequente o refutador refutar a própria suspeita e recuar, e esse é o mecanismo central que filtra os falsos positivos.
  2. Recebo o veredito em graus. Aprovado / aprovado após ajustes menores / bloqueado, três níveis bastam. Com graus, o estado "tem apontamentos, mas pode fazer merge" não se mistura com o "não pode fazer merge".
  3. Rodo em contexto independente. O refutador recebe só o diff e o código relacionado. Não recebe a conversa do processo de trabalho nem a narrativa do "por que corrigi assim". No momento em que a narrativa é compartilhada, o refutador se contamina do mesmo equívoco.

Uma dica de operação também. O refutador roda em modo somente leitura. Se você dá permissão de mexer no código, em vez de apontar ele tenta corrigir por conta própria, e aí volta o problema de quem verifica essa correção. O papel fica fixado em julgar, e a correção volta para quem fez o trabalho original.

Quando rodar esse loop

Não rodo esse loop em toda mudança. Chamar o refutador para corrigir um erro de digitação é desperdício. O que roda sem exceção são as mudanças difíceis de desfazer. Migração de DB, exclusão de dados, caminhos de pagamento ou autenticação entram aqui. Mudanças que carregam a alegação de "corrigi" também são alvo. Correção de bug exige o par "o bug se reproduzia antes e sumiu depois da correção", e o refutador é bom em achar as brechas desse par. A última é a mudança cujo código eu não vou ler inteiro. Uma pessoa ler por completo um diff grande gerado por IA desmorona na prática. Se não vou ler, no mínimo ponho outro modelo para ler de forma adversarial.

Não dá para fugir da conversa sobre custo: uma rodada de refutação custa algumas chamadas de modelo. Comparado ao custo de uma pessoa rastrear um bug que chegou à produção, nem era assunto para pensar duas vezes. Se aquele bug de logout tivesse ido até o deploy, eu teria que escavar sozinho as duas camadas, o redirecionamento e o guard da flag, munido apenas do relato "às vezes o logout não funciona".

Por que tem que ser outro modelo

Uma sessão nova do mesmo modelo já ajuda até certo ponto. Só de separar o contexto, o problema de se contaminar pela narrativa desaparece. Mas a conclusão de alguns meses rodando isso é que, quando o modelo em si é outro, pega-se claramente mais coisa.

Cada modelo presta atenção em pontos diferentes. Um é sensível a problemas de timing na gestão de estado, o outro a violações de contrato e valores de borda, e por aí vai. Duas sessões do mesmo modelo compartilham os mesmos pontos cegos; modelos diferentes têm pontos cegos desalinhados. O valor da verificação cruzada vem exatamente desse desalinhamento.

Essa estrutura não deve soar estranha. É a mesma razão pela qual um time de pessoas entrega a revisão de código a alguém que não é o autor. Trabalhar com IA não apaga esse princípio; só o tornou barato o bastante para aplicar o tempo todo. Um julgador que não é o autor, um olhar não contaminado pela narrativa. E uma aprovação que só sai quando a refutação fracassa. O que a organização humana fazia só de vez em quando por ser caro, agora dá para deixar de pé na frente de todo merge.

Artigos relacionados

0 comentários