Why NextWhy Next
Torna all'elenco
💻 SviluppoLettura di 11 min

La pipeline in cui una seconda AI prova a confutare il codice corretto dalla prima

Se chiedi allo stesso modello di revisionare il proprio codice, riapprova le conclusioni che ha già tirato. La storia di uno sviluppatore solitario, che scrive quasi tutto il codice insieme all'AI, e di come ha trasformato in pipeline permanente un loop di verifica incrociata in cui a un'AI viene chiesto di 'confutare' il lavoro di un'altra. Il caso reale di un bug che ha rivelato difetti per tre round consecutivi sfuggiti alla self review, e il design dei prompt.

In sintesi

Un'AI che giudica il proprio lavoro è indulgente. Se chiedi a un altro modello di 'assumere che questa correzione sia sbagliata e confutarla', emergono i difetti che la self review si era persa. Il caso di un singolo bug di logout da cui sono usciti difetti sempre più profondi per tre round consecutivi, il design dei prompt (framing di confutazione, formato delle prove) e i criteri su quando far girare questo loop.

In questa pagina

Chi ha provato a far fare code review a un agente AI lo sa. Sul codice degli altri se la cava piuttosto bene. Ma se gli fai revisionare il codice che ha appena scritto lui stesso, diventa stranamente indulgente. Ti risponde "l'implementazione è coerente con l'intento", e quando poi lo esegui davvero il bug è ancora lì.

È inevitabile. Dentro lo stesso modello e lo stesso contesto, gli abbagli presi mentre si scriveva il codice si ripetono identici mentre lo si revisiona. È una struttura in cui uno riapprova le proprie conclusioni: più che una review, è un ripetere gli ordini a voce alta.

Così ho portato il giudizio fuori. Passo il codice corretto da Claude a Codex, con una sola consegna: "Assumi che questa correzione sia sbagliata, e prova a confutarla."

Per tre round consecutivi sono usciti bug sempre più profondi

La prima volta che ho toccato con mano il valore di questo loop è stato con un bug di logout. Il sintomo era semplice: premendo il pulsante di logout, ogni tanto il logout non avveniva. Claude ha trovato la causa e l'ha corretta, e a leggere il codice sembrava plausibile. Stavo quasi per fare il merge.

Ho chiesto a Codex di confutare, e al primo round è arrivata questa risposta: la correzione in sé è giusta, ma il redirect della pagina di autenticazione intercetta il flusso prima che quel percorso di codice venga raggiunto, quindi la correzione non ha mai occasione di essere eseguita. Il codice corretto era codice morto.

Ho sistemato anche il redirect e ho fatto ripartire la confutazione. Al secondo round ne è uscito un altro. Stavolta il problema era il flag di "elaborazione in corso" messo per evitare esecuzioni duplicate: con certi timing, proprio per via di quella guardia, la richiesta di logout diventava un no-op che non faceva assolutamente nulla. Solo al terzo round è arrivata la risposta "non ho trovato nulla da confutare", e solo allora ho fatto il merge.

Tutti e tre i round riguardavano codice che aveva superato la self review. E in tutti e tre i round il confutatore ha colto il difetto perché non cercava "il codice sembra buono?", ma "esiste uno scenario in cui questa affermazione crolla?".

Da allora il pattern si è ripetuto. Su una modifica lato server, dopo due giri di self review, la verifica incrociata ha beccato un valore mancante in un vincolo del DB e ho dovuto aggiungere una migrazione. Su una correzione della logica di retry, il confutatore ha rimandato indietro con verdetto bloccante un caso limite che classificava erroneamente una certa risposta di timeout come fallimento permanente. Tutte cose che "erano abbastanza plausibili da passare".

Non chiedo una review, chiedo una confutazione

Nella progettazione del prompt la cosa più importante è il framing. Se dici "fammi una review", il modello ti dà una risposta innocua, metà complimenti e metà osservazioni minori. Se dici "assumi che questa correzione sia sbagliata e confutala; promuovila solo se la confutazione fallisce", l'atteggiamento cambia. Perché il via libera non è più il valore di default, ma la conseguenza di una confutazione fallita.

A questo aggiungo tre condizioni.

  1. Impongo un formato di prova. Ogni rilievo deve portare file e numero di riga, più uno scenario di riproduzione in cui quel difetto esplode davvero. "Questa parte sembra rischiosa" non lo accetto. Se non riesce a costruire lo scenario, quel sospetto non è un rilievo: è un'archiviazione. In pratica capita spesso che il confutatore confuti da sé i propri sospetti e li ritiri, ed è proprio questo il meccanismo chiave che filtra i falsi positivi.
  2. Il verdetto arriva a gradi. Passa / passa dopo correzioni minori / bloccante: tre livelli bastano. Con i gradi non si mescolano lo stato "ci sono rilievi ma si può fare il merge" e lo stato "il merge non si deve fare".
  3. Lo faccio girare in un contesto indipendente. Al confutatore do solo il diff e il codice correlato. Non gli do la conversazione di lavoro né la narrazione del "perché ho corretto così". Nel momento in cui condividi la narrazione, anche il confutatore si contagia con gli stessi abbagli.

Un consiglio operativo: il confutatore gira in sola lettura. Se gli dai il permesso di modificare il codice, invece di segnalare prova a correggere lui stesso, e allora si ripresenta il problema di chi verifica quella correzione. Il ruolo resta fissato sul giudizio, e le correzioni tornano all'esecutore originale.

Quando farlo girare

Non faccio girare questo loop su ogni modifica. Convocare il confutatore per la correzione di un refuso è uno spreco. Lo faccio girare senza eccezioni sulle modifiche difficili da annullare: migrazioni del DB, cancellazioni di dati, percorsi di pagamento o autenticazione. Sono candidate anche le modifiche che portano con sé l'affermazione "l'ho corretto". Una correzione di bug richiede la coppia "il bug si riproduceva prima ed è sparito dopo", e il confutatore è bravissimo a trovare i buchi in questa coppia. L'ultimo caso sono le modifiche di cui non leggerò tutto il codice. Che una persona legga per intero un grande diff prodotto dall'AI, realisticamente, non regge. Se non lo leggerò io, che almeno lo legga in modo ostile un altro modello.

Del costo bisogna pur parlare: un round di confutazione vale qualche chiamata al modello. Confrontato con il costo di una persona che insegue un bug finito in produzione, non c'era nemmeno da pensarci. Se il bug di logout di prima fosse arrivato al deploy, mi sarei ritrovato con la sola segnalazione "ogni tanto il logout non funziona" a dover scavare da solo attraverso due strati, il redirect e la guardia del flag.

Perché deve essere un modello diverso

Anche una nuova sessione dello stesso modello dà un certo effetto. Il solo separare i contesti elimina il problema del contagio della narrazione. Ma dopo qualche mese di utilizzo la conclusione è che quando il modello stesso è diverso si becca decisamente di più.

Ogni modello presta attenzione a punti diversi. Se uno è sensibile ai problemi di timing nella gestione dello stato, l'altro lo è alle violazioni di contratto e ai valori limite. Due sessioni dello stesso modello condividono gli stessi punti ciechi, ma modelli diversi hanno punti ciechi sfalsati. Il valore della verifica incrociata nasce esattamente da quello sfalsamento.

Questa struttura non dovrebbe suonare nuova. È lo stesso motivo per cui i team umani affidano la code review a qualcuno che non è l'autore. Lavorare con l'AI non cancella quel principio: semplicemente lo rende applicabile in modo economico e permanente. Un giudice che non è l'autore, occhi non contagiati dalla narrazione. E un'approvazione che arriva solo quando la confutazione fallisce. Quello che le organizzazioni umane facevano solo ogni tanto perché costava, ora lo si può piazzare davanti a ogni merge, sempre.

Articoli correlati

0 commenti