Quand l'IA code vite, qui contrôle l'IA ?

Un agent de code produit en quelques minutes ce qui demandait une journée. La génération cesse alors d'être le goulot d'étranglement, et la question devient : sur quel contexte travaille-t-il, et qu'a-t-il réellement vérifié ? Voici une chaîne de garde-fous qui refuse l'action plutôt que de la commenter.

Quand l'IA code vite, qui contrôle l'IA ?

Un agent de code écrit en quelques minutes ce qui prenait une journée. Le fait est désormais banal ; sa conséquence l'est moins. Quand la production de code cesse d'être le goulot d'étranglement, le problème ne disparaît pas, il se déplace vers une série de questions dont aucune ne concerne la vitesse d'écriture.

L'agent travaille-t-il sur le bon contexte ? Son diagnostic est-il démontré ou seulement vraisemblable ? Sa correction dégrade-t-elle l'architecture ? Ses tests décrivent-ils ce que le logiciel doit faire, ou ce qu'il fait aujourd'hui ? A-t-il vérifié quoi que ce soit avant d'annoncer que c'était terminé ?

Ces questions ne sont pas nouvelles : un développeur chevronné se les pose depuis toujours, silencieusement. Ce qui change, c'est la cadence. Cinq modifications par jour laissent le temps de se les poser. Cinquante, non.

Nous avons donc essayé de les sortir de la tête du développeur pour les inscrire dans l'outil. Non pas sous forme de consignes, mais sous forme de refus. Cet article décrit ce dispositif, avec les messages de blocage réels qu'il produit.

Le problème n'est pas la qualité du modèle

Il faut écarter d'emblée une lecture facile : ce dispositif n'existe pas parce que les modèles seraient mauvais. Il existe parce qu'ils sont bons.

Un grand modèle de langage excelle à produire une explication cohérente à partir de ce qu'il observe. Montrez-lui trente lignes de code contenant une anomalie, il produira un récit plausible de la cause, bien écrit, techniquement crédible, et souvent juste.

Souvent. Pas toujours. Là est la difficulté : rien, dans la forme de la réponse, ne distingue le cas où elle est démontrée du cas où elle est seulement vraisemblable. Un humain qui doute hésite et le dit. Un modèle qui se trompe produit exactement la même prose assurée qu'un modèle qui a raison.

Ajoutez à cela la vitesse d'exécution, et vous obtenez une chaîne de comportement caractéristique :

contexte partiel → explication plausible → correction immédiate → dette supplémentaire → tests ajustés à la correction → déclaration de réussite

Chaque maillon est individuellement raisonnable. L'ensemble produit du code qui compile, des tests qui passent, et un logiciel qui dérive.

L'hypothèse que nous avons voulu tester est la suivante : une partie de la discipline qu'un ingénieur expérimenté applique par réflexe peut être convertie en contraintes exécutables, placées non pas après l'action de l'agent, mais avant.

Cinq temps

Le dispositif s'articule autour de cinq questions, dans cet ordre :

VOIR → PROUVER → STRUCTURER → SPÉCIFIER → VÉRIFIER

Chacune correspond à un contrôle automatique qui s'interpose entre l'agent et son action. Techniquement, ce sont des hooks : de petits programmes que l'environnement appelle à des moments précis, avant la modification d'un fichier, avant une commande système, à la fin d'une tâche. Le programme inspecte ce que l'agent s'apprête à faire et rend un verdict. S'il renvoie un code d'erreur, l'action n'a pas lieu.

Le point important tient en une phrase : ce ne sont pas des avertissements. Un avertissement se lit et se contourne. Ici, l'écriture n'a pas lieu.

VOIR : ne pas modifier ce qu'on n'a pas regardé

Le premier contrôle interdit de modifier un fichier existant dont la totalité n'a pas été lue pendant la session en cours. Le mécanisme est comptable plutôt qu'intelligent : chaque lecture enregistre les plages de lignes exposées à l'agent, chaque tentative de modification vérifie que leur union couvre 100 % du fichier. En dessous, refus, avec le détail des plages manquantes.

Ce contrôle ne prétend pas mesurer la compréhension, et il serait malhonnête de le laisser croire : lire intégralement un fichier ne garantit pas qu'on l'ait compris. Il vérifie une condition plus modeste et plus objective, le contenu a-t-il été présenté à l'agent qui va le modifier, et c'est ce qui lui permet d'éliminer une catégorie entière de raccourcis : construire un correctif à partir de trois résultats de recherche textuelle et d'une fonction isolée, en raisonnant comme si l'on connaissait l'ensemble.

Ce que masque un extrait est bien identifié : une garde située plus haut, un retour anticipé, une surcharge, une convention déjà appliquée vingt lignes plus bas, un comportement qui contredit exactement ce que le fragment laissait supposer. C'est là que naissent la plupart des régressions.

Une précision technique a son importance : la lecture effectuée par un sous-agent ne débloque pas l'agent qui édite. L'information doit se trouver dans le contexte de celui qui écrit, pas dans celui d'un auxiliaire qui lui a résumé le fichier.

PROUVER : la question qui change tout

Le deuxième contrôle est le plus intéressant, et probablement le plus transposable. Il part d'un constat : la lecture ne démontre rien. Un agent peut avoir intégralement lu un fichier et se tromper complètement sur la cause du problème. Le contrôle exige donc qu'une mesure ait été effectuée sur le fichier avant toute modification, dès lors qu'il dépasse une taille significative (80 lignes par défaut).

Voici ce que l'agent reçoit lorsqu'il tente de passer outre :

BLOQUE par hook probe-before-patch (H7)

Fichier : src/vues/focus.ts (82 lignes) Aucune MESURE enregistrée sur ce fichier dans cette session.

Tu l'as peut-être lu intégralement (H6 le vérifie), mais tu n'as rien MESURÉ dessus. Lire ne suffit pas à établir un fait : une hypothèse plausible, lue dans le code, se présente comme établie alors qu'une commande la trancherait.

AVANT DE PATCHER, exécute une mesure QUI PEUT TE CONTREDIRE.

Ne comptent PAS comme mesure : cargo check, npm run build, tsc (ils valident la syntaxe, pas ton hypothèse).

Question à te poser : quelle commande PROUVE ce que je m'apprête à affirmer ? Si la réponse est « aucune », ce n'est pas établi, c'est une spéculation.

Aucun bypass possible.

Deux éléments méritent d'être relevés.

Le premier est la liste des exclusions. Une compilation réussie ne compte pas comme mesure, un typage validé non plus : ces commandes établissent la validité syntaxique du code, pas la véracité du diagnostic. Un correctif peut compiler parfaitement et corriger un problème qui n'existait pas, et le vert de la compilation ressemble alors à une confirmation. Comptent en revanche une recherche ciblée, un historique de version, un différentiel, une annotation ligne à ligne, un test dirigé sur le comportement supposé, un appel réel au service.

Le second est la formulation : une mesure qui peut te contredire. Ce n'est pas une nuance de style. Une vérification qui ne peut pas échouer ne vérifie rien. On retrouve là, appliquée à l'ingénierie logicielle, la logique de la falsification : une hypothèse n'a de valeur que si l'on sait dire ce qui l'invaliderait.

La question centrale tient en huit mots :

« Quelle commande PROUVE ce que je m'apprête à affirmer ? »

Elle mérite d'être posée bien au-delà des agents. Elle sépare, dans n'importe quelle revue technique, ce qui est établi de ce qui est cru.

Le contrôle transforme ainsi la séquence habituelle en :

observation → hypothèse → mesure susceptible de contredire l'hypothèse → modification

STRUCTURER : le cliquet

Le troisième contrôle vise un effet de bord propre aux agents rapides : la résolution locale des problèmes par accumulation. Un agent chargé de corriger un comportement dans un fichier de 400 lignes y ajoutera volontiers douze lignes. La correction est juste. Répétée cinquante fois, elle produit un fichier de mille lignes que plus personne ne veut ouvrir.

Le principe retenu est un cliquet : la dette existante reste modifiable, elle ne peut plus croître.

BLOQUE par hook code-standards (H8) : AGGRAVATION D'UN FICHIER EN DETTE

Fichier : scripts/coverage-dashboard.py État : 416 lignes → 417 lignes (+1) Plafond py : 400 lignes, déjà dépassé de 16 lignes.

Ce fichier fait partie des fichiers en dette du parc. Il reste ÉDITABLE : tu peux le corriger, le réduire, le laisser stable. Ce qui est refusé, c'est qu'il grossisse encore.

Trois régimes coexistent. Un fichier neuf qui dépasse d'emblée son plafond est refusé, au motif que la création est le moment où le découpage coûte le moins cher. Un fichier conforme qui franchirait le plafond par la modification en cours est refusé au moment précis de la bascule. Un fichier déjà en dette reste éditable à taille égale ou décroissante.

L'agent bloqué se voit proposer trois issues : déplacer l'ajout dans un module dédié, compenser en retirant au moins autant de lignes, ou demander un arbitrage humain si le découpage nécessaire déborde le périmètre de la tâche. Une consigne complète le dispositif : « Ne contourne pas en écrivant le même code dans un fichier voisin déjà obèse. »

La règle n'est donc pas « toute dette doit disparaître », qui serait irréaliste sur un parc existant. Elle est :

La dette existante est tolérée. Son aggravation ne l'est pas.

Un détail d'implémentation en dit long sur l'esprit du dispositif. Les plafonds ne sont pas des valeurs d'école : ils sont calés sur la médiane mesurée du parc réel, à raison d'environ trois fois cette médiane. Sur notre code, elle est de 139 lignes pour les fichiers PHP, 107 pour les gabarits, 119 pour les composants d'interface. Les plafonds qui en découlent laissent respirer le travail ordinaire et ne mordent que sur l'obésité.

Et lorsqu'un seuil s'avère mal placé, il se discute. Un écran d'orchestration de notre interface est resté au-dessus du plafond après un chantier de découpage, sans qu'un découpage supplémentaire soit justifiable : son état est partagé entre effets, gestionnaires et un assemblage d'une douzaine de composants. Le plafond de sa catégorie a été relevé sur arbitrage humain, après mesure du coût de la décision : cinquante-quatre fichiers passaient mécaniquement de « en dette » à « conforme » sans qu'une ligne ait été corrigée. Ce coût a été inscrit en commentaire dans le fichier de configuration, avec sa date et son motif.

C'est peut-être le point le plus important ici : un garde-fou n'a d'intérêt que si l'on connaît le prix de chaque exception qu'on lui accorde.

SPÉCIFIER : ne pas graver le défaut dans la pierre

Le quatrième contrôle traite d'une erreur plus subtile, et nettement plus coûteuse à long terme : le test vert qui transforme un défaut en spécification.

Le scénario est banal. Une fonction devrait remonter trois niveaux hiérarchiques mais s'arrête au deuxième. L'agent écrit un test qui attend deux niveaux, puisque c'est ce que le code fait. Le test passe, la suite est verte.

Le défaut vient de changer de nature. Il n'est plus un bug : il est devenu le comportement attendu, documenté et vérifié par la suite de tests. Six mois plus tard, un développeur corrige la fonction. Le test casse. La suite lui annonce qu'il vient d'introduire une régression. Dans le meilleur des cas il perd une heure ; dans le pire, il réintroduit le bug pour retrouver le vert.

Le contrôle repère les formulations caractéristiques de ce glissement dans les intitulés de tests et les commentaires : « comportement actuel », « limite mesurée », « bug connu », « pas encore implémenté », « fige le comportement ». Il ne prétend pas analyser sémantiquement les assertions, il détecte les marqueurs par lesquels un test avoue décrire l'existant.

Le message de blocage énonce la règle :

Ces formulations décrivent ce que le code FAIT. Un test doit exprimer ce que le code DOIT faire.

Si le comportement attendu n'est pas implémenté, le test doit être ROUGE. Le rouge EST l'information : il dit qu'il reste du travail, et il s'éteint tout seul quand le code est corrigé.

Un test VERT qui fige un défaut le transforme en SPÉCIFICATION : il sort du radar, et le prochain qui corrige le bug croit casser quelque chose.

Et il donne la forme attendue :

MAUVAIS : it('LIMITE MESUREE : s arrête au 2e niveau (cf. #935)') avec expect(ids).toEqual(['b', 'c']). Vert, grave le défaut.

JUSTE : it('#935 - remonte au 3e niveau et au-delà') avec expect(ids).toEqual(['a', 'b', 'c']). Rouge, dit ce qui manque.

Trois cas sont distingués. Comportement vérifiable comme correct : test normal, vert. Comportement attendu non implémenté : test rouge, nommé avec le numéro de l'anomalie. Comportement douteux, non tranchable techniquement : n'écrire aucun test qui le valide, et demander un arbitrage humain, parce qu'il s'agit d'une décision produit et non d'une décision d'ingénierie.

Le préfixe #<numéro> n'est pas décoratif. Il permet de distinguer, dans un tableau de bord, un rouge intentionnel qui matérialise du travail identifié d'un rouge accidentel qui signale une régression. Sans cette distinction, un rouge permanent finit par être ignoré, et le tableau de bord perd sa fonction.

Une dernière consigne verrouille l'ensemble :

« Ne "répare" JAMAIS un rouge intentionnel en abaissant l'attente : c'est le code qui doit monter. »

La symétrie avec le contrôle précédent est nette, et c'est elle qui donne sa cohérence au dispositif :

PROUVER protège la vérité du diagnostic. SPÉCIFIER protège la vérité de la spécification.

Dans les deux cas, il s'agit d'empêcher qu'une convenance immédiate, une explication qui tombe bien, un test qui passe, s'installe comme un fait.

VÉRIFIER : terminé n'est pas vérifié

Le dernier contrôle intervient au moment où l'agent s'apprête à conclure. Son principe est simple :

Avoir écrit une correction n'est pas avoir démontré qu'elle fonctionne.

Il confronte la déclaration de fin de tâche aux vérifications réellement exécutées pendant la session, en consultant un journal des actions, et distingue explicitement les tests partiels des tests complets : un test ciblé démontre que le comportement testé fonctionne, il ne dit rien du reste du logiciel.

Ce contrôle-ci ne bloque pas. C'est délibéré. Un travail partiel doit pouvoir être annoncé comme tel : mieux vaut une session qui se termine en disant « les tests n'ont pas tourné » qu'une session qui ne peut pas se terminer. L'exigence porte sur l'honnêteté du compte rendu, pas sur la complétude du travail.

L'enchaînement compte plus que les maillons

Pris isolément, aucun de ces contrôles n'est original. Lire avant de modifier, mesurer avant de diagnostiquer, ne pas laisser grossir un fichier obèse, écrire des tests qui expriment l'intention, vérifier avant d'annoncer : ce sont des banalités d'ingénierie, on les trouve dans n'importe quel manuel.

Ce qui change, c'est leur statut. Une bonne pratique se rappelle, s'oublie, se contourne quand on est pressé. Une contrainte exécutable placée avant l'action ne se contourne pas. Et l'enchaînement forme une chaîne dont chaque maillon protège le suivant :

VOIR : Est-ce que je travaille réellement sur le code concerné, en entier ?

PROUVER : Mon diagnostic repose-t-il sur une observation qui aurait pu me contredire ?

STRUCTURER : Ma correction résout-elle le problème sans dégrader l'architecture ?

SPÉCIFIER : Mes tests décrivent-ils ce que le système doit faire, ou institutionnalisent-ils ce qu'il fait de travers ?

VÉRIFIER : Ai-je démontré que le résultat annoncé est le résultat obtenu ?

La chaîne défectueuse décrite en ouverture se trouve remplacée, maillon par maillon :

contexte → preuve → correction structurée → spécification → vérification

Ce que ce dispositif ne fait pas

Il faut être précis sur la portée, faute de quoi la démarche perd sa crédibilité.

Ces contrôles ne rendent pas un agent infaillible. Ils ne garantissent pas qu'il comprend le code qu'il a lu, qu'il choisit la mesure pertinente, qu'il découpe l'architecture au bon endroit, qu'il identifie correctement l'intention derrière une demande, ni qu'il n'introduira plus de défauts. Aucun mécanisme automatique ne peut promettre cela.

Ce qu'il fait est plus modeste et plus vérifiable : il élimine mécaniquement des classes entières d'erreurs. Modifier sur la base d'un extrait. Confondre une hypothèse plausible avec un fait mesuré. Résoudre localement en aggravant globalement. Transformer un bug en spécification. Déclarer terminé sans preuve.

Ce sont précisément les erreurs que la vitesse rend fréquentes, et que la relecture humaine détecte mal, parce qu'elles se présentent sous une forme irréprochable : du code propre, des tests verts, une explication cohérente.

La question qui vient

Pendant les premières années de l'IA générative appliquée au développement, l'attention s'est portée presque entièrement sur la capacité des modèles à écrire du code. C'était l'axe de progrès visible, et il a été spectaculaire.

À mesure que cette capacité augmente, la contrainte se déplace. Quand plusieurs agents travaillent vite et en parallèle, la question n'est plus « comment produire davantage de code », mais :

Comment augmenter la production sans augmenter dans les mêmes proportions l'entropie du logiciel et la charge de supervision humaine ?

Si cette lecture est juste, la valeur pourrait progressivement se déplacer des modèles vers les systèmes qui les encadrent : gestion du contexte, permissions, invariants vérifiables, exigence de preuve, contraintes architecturales, intention des tests, vérification avant conclusion.

Ce n'est plus tout à fait du prompting. C'est une gouvernance exécutable des agents de développement, avec ses règles, ses arbitrages datés et son coût assumé.

La question qui structurera les prochaines années n'est peut-être plus :

Quel modèle écrit le meilleur code ?

Mais :

Dans quel système d'ingénierie autorisons-nous ce modèle à modifier notre logiciel ?

3 minutes de lecture
Partager :

Articles similaires

Découvrez d'autres actualités qui pourraient vous intéresser