Why NextWhy Next
Retour à la liste
💻 Dev11 min de lecture

Le pipeline où une seconde IA doit réfuter le code corrigé par la première

Demandez à un modèle de relire son propre code : il ré-approuve ses propres conclusions. L'histoire d'un développeur solo, qui écrit presque tout son code avec l'IA, et qui a transformé en pipeline permanent une boucle de vérification croisée où une IA doit "réfuter" le travail d'une autre. Le cas réel où trois rounds successifs ont sorti des bugs que l'auto-revue avait manqués, et la conception des prompts.

L'essentiel

Une IA qui juge son propre travail est indulgente. Demandez à un autre modèle de "supposer que ce correctif est faux et de le réfuter", et les défauts que l'auto-revue avait manqués apparaissent. Le cas d'un bug de déconnexion où trois rounds successifs ont sorti des défauts toujours plus profonds, la conception des prompts (cadrage en réfutation, format de preuve exigé), et les critères pour savoir quand lancer cette boucle.

Sur cette page

Quiconque a déjà confié une revue de code à un agent IA le sait. Sur le code des autres, il se débrouille plutôt bien. Mais demandez-lui de relire le code qu'il vient d'écrire, et il devient étrangement indulgent. La réponse tombe : "l'implémentation correspond à l'intention", et quand on exécute vraiment, le bug est toujours là.

Rien d'étonnant. Dans le même modèle et le même contexte, les illusions commises en écrivant le code se reproduisent à l'identique en le relisant. C'est une structure où l'on ré-approuve ses propres conclusions : moins une revue qu'une répétition d'ordre.

Alors j'ai sorti le jugement à l'extérieur. Je passe à Codex le code que Claude a corrigé, avec une seule consigne : "Suppose que ce correctif est faux, et réfute-le."

Trois rounds de suite, un bug plus profond à chaque fois

La première fois que j'ai mesuré la valeur de cette boucle, c'était sur un bug de déconnexion. Le symptôme était simple : parfois, appuyer sur le bouton de déconnexion ne déconnectait pas. Claude a trouvé la cause et l'a corrigée, et à la seule lecture du code, c'était plausible. J'ai failli merger.

J'ai demandé à Codex de réfuter, et au premier round est venue cette réponse : le correctif est juste en soi, mais la redirection de la page d'authentification intercepte le flux avant que ce chemin de code soit atteint, donc le correctif n'a jamais l'occasion de s'exécuter. Le code corrigé était du code mort.

J'ai corrigé la redirection aussi, et relancé la réfutation. Au deuxième round, encore une trouvaille. Cette fois, c'était le drapeau "traitement en cours" posé pour empêcher les exécutions en double. Avec un certain timing, ce garde-fou transforme la demande de déconnexion elle-même en no-op qui ne fait strictement rien. Ce n'est qu'au troisième round qu'est venue la réponse "je n'ai rien trouvé à réfuter", et c'est seulement là que j'ai mergé.

Les trois rounds portaient sur du code qui avait passé l'auto-revue. Et les trois fois, le réfutateur a attrapé le défaut parce qu'il cherchait non pas "le code a-t-il l'air bon" mais "existe-t-il un scénario où cette affirmation s'effondre".

Le motif s'est répété depuis. Sur un correctif serveur qui avait survécu à deux auto-revues, la vérification croisée a attrapé une valeur manquante dans une contrainte de DB, et j'ai ajouté une migration. Sur un correctif de logique de retry, le réfutateur a renvoyé un verdict bloquant pour un cas limite qui classait à tort certaines réponses de timeout en échec permanent. Tous, sans exception, des défauts "assez plausibles pour passer".

Je ne demande pas une revue, je demande une réfutation

Dans la conception du prompt, le plus important est le cadrage. Dites "fais une revue" et le modèle rend une réponse tiède, moitié compliments, moitié remarques mineures. Dites "suppose que ce correctif est faux et réfute-le ; ne le laisse passer que si ta réfutation échoue", et l'attitude change. Le feu vert n'est plus la valeur par défaut mais la conséquence d'une réfutation ratée.

J'y ajoute trois exigences.

  1. Imposer un format de preuve. Chaque remarque doit porter le fichier et le numéro de ligne, plus un scénario de reproduction où le défaut éclate réellement. Un "cette partie semble risquée" n'est pas recevable. Pas de scénario, pas de remarque : le soupçon est rejeté. En pratique, il arrive souvent que le réfutateur réfute lui-même son propre soupçon et le retire, et c'est le mécanisme clé qui filtre les faux positifs.
  2. Recevoir le verdict en grades. Passe / passe après correction mineure / bloqué : trois niveaux suffisent. Avec des grades, l'état "il y a des remarques mais on peut merger" ne se mélange plus avec l'état "il ne faut pas merger".
  3. Tourner dans un contexte indépendant. Le réfutateur ne reçoit que le diff et le code concerné. Ni la conversation du travail, ni le récit du "pourquoi j'ai corrigé comme ça". Dès qu'on partage le récit, le réfutateur s'imprègne des mêmes illusions.

Un conseil d'exploitation aussi : le réfutateur tourne en lecture seule. Donnez-lui le droit de modifier le code, et au lieu de pointer les défauts il voudra corriger lui-même, et alors la question revient : qui vérifie cette correction-là ? Le rôle reste figé sur le jugement, et la correction est renvoyée à l'exécutant d'origine.

Quand lancer la boucle

Je ne la lance pas sur chaque changement. Convoquer un réfutateur pour une faute de frappe est du gaspillage. Ce qui passe systématiquement, ce sont les changements difficiles à annuler : migrations de DB, suppressions de données, chemins de paiement ou d'authentification. Les changements porteurs d'une affirmation "c'est corrigé" sont aussi de la partie. Un correctif de bug exige la paire "le bug se reproduisait avant, il a disparu après", et le réfutateur excelle à trouver les failles de cette paire. Le dernier cas, ce sont les changements dont je ne lirai pas tout le code. Lire intégralement, en humain, un gros diff produit par l'IA ne tient pas dans la réalité. Si je ne vais pas le lire, autant qu'au moins un autre modèle le lise de façon adversariale.

Impossible de ne pas parler du coût : un round de réfutation vaut quelques appels de modèle. Comparé au coût de traquer, en humain, un seul bug parti en production, il n'y avait pas à hésiter. Si le bug de déconnexion ci-dessus avait atteint le déploiement, je me serais retrouvé, avec pour seul indice un signalement "parfois la déconnexion ne marche pas", à devoir creuser moi-même deux couches : la redirection et le garde-fou du drapeau.

Pourquoi il faut un modèle différent

Une nouvelle session du même modèle a déjà un certain effet. La séparation des contextes suffit à éliminer la contamination par le récit. Mais après quelques mois d'exploitation, ma conclusion est nette : quand le modèle lui-même est différent, on attrape clairement davantage.

Chaque modèle a ses points d'attention. Si l'un est sensible aux problèmes de timing dans la gestion d'état, l'autre l'est aux violations de contrat et aux valeurs limites. Deux sessions du même modèle partagent les mêmes angles morts, mais des modèles différents ont des angles morts décalés. La valeur de la vérification croisée vient précisément de ce décalage.

Cette structure ne devrait pas vous sembler étrangère. C'est la raison même pour laquelle les équipes humaines confient la revue de code à quelqu'un d'autre que l'auteur. Travailler avec l'IA ne fait pas disparaître ce principe ; cela permet seulement de l'appliquer en permanence, pour pas cher. Un juge qui n'est pas l'auteur, un regard non contaminé par le récit. Et une approbation qui ne vient qu'après l'échec de la réfutation. Ce que les organisations humaines ne faisaient qu'occasionnellement parce que c'était cher, on peut désormais le poster en permanence devant chaque merge.

Articles similaires

0 commentaires