- IA
- LLM
- Tests
- Architecture
Contra-validation à trois niveaux, zones de fichiers disjointes, preuve exigée dès le prompt et modèle choisi selon l'étape : la discipline qui m'évite de livrer ce qu'un agent IA a seulement affirmé.
Je développe une bonne partie de mes produits avec des agents IA qui travaillent en parallèle : l'un écrit un module, un autre corrige une série de tests, un troisième audite une route. Le risque n'est pas celui qu'on imagine : un agent écrit rarement du code absurde. Le vrai danger, c'est qu'il rende un rapport plus optimiste que la réalité : « tests verts », « fichier créé », « les échecs étaient déjà là avant ».
La discipline que j'applique s'est construite sur Atelier du Tuning, la marketplace sur laquelle j'ai le plus délégué à des agents. Elle tient dans une phrase que je recopie en tête de mes notes : un rapport d'agent n'est jamais une vérité, c'est une hypothèse à vérifier. J'appelle cette vérification la contra-validation.
La contra-validation, à trois niveaux
Après chaque livraison d'un agent, je refais moi-même trois vérifications, dans cet ordre :
- Les fichiers. Un
ls, puis la lecture des fichiers annoncés comme créés ou modifiés. Existent-ils ? Contiennent-ils ce qui est annoncé ? Les zones que j'avais interdites sont-elles intactes ? - Les gates. Je relance moi-même
tsc --noEmit, ESLint, l'audit des traductions et les tests des modules touchés. Je ne me contente jamais du chiffre écrit dans le rapport. - Le comportement. Il me faut une preuve indépendante : un
curlqui renvoie les codes attendus, 200 et 400, 401 ou 404, une ligne réellement créée en base, une capture d'écran. Un « tsc 0 » prouve que le code compile, pas que la fonctionnalité marche.
Sur les points critiques (argent, sécurité, données), je ne relaie jamais un verdict d'agent sans avoir refait ces trois niveaux. L'audit de sécurité d'Atelier du Tuning avant la mise en production a suivi cette règle : chaque constat a été contra-validé à la main avant d'être corrigé. J'en détaille les failles dans l'article sur les douze failles d'une API NestJS.
Elle marche aussi dans l'autre sens : quand un agent de relecture signale un bloquant, je vérifie la ligne citée avant de l'accepter. Un bloquant qui ne tient pas est retiré et noté comme faux positif.
Exiger la preuve dans le prompt, pas après
Plutôt que de réclamer des preuves une fois le travail rendu, je les exige dès la commande. Chaque prompt d'agent contient « gates avant de rendre, prouve-les avec des chiffres » et « curl de preuve ». La liste des preuves acceptées est courte :
- un
curlsur l'environnement réel, qui couvre le cas nominal et un cas d'erreur ; - une ligne en base aux valeurs cohérentes, par exemple un coût recalculé à la main : tokens d'entrée × prix d'entrée + tokens de sortie × prix de sortie ;
- un test rouge avant la correction et vert après ;
- une capture du rendu.
La règle qui en découle est simple : un rapport sans preuve correspond à un travail non terminé. Il en va de même pour mes propres revues avant push. Les gates y tournent réellement, sans cache, et un gate qui n'a pas été lancé rend le verdict impossible. Je l'écris tel quel, plutôt qu'un « OK » de mémoire.
Des zones de fichiers disjointes et des interdits répétés
Deux agents qui modifient le même fichier en même temps produisent un résultat que ni l'un ni l'autre n'a vérifié. Avant de lancer des agents en parallèle, j'attribue donc la propriété de chaque fichier partagé. L'exemple qui m'a appris la règle est celui des fichiers de traduction (messages/*.json) : un seul agent en est propriétaire à un instant donné, et les autres attendent ou contournent. Les fichiers interdits sont listés explicitement dans le prompt, par exemple : « Ne touche PAS : layout.tsx, Caddyfile, legal/cookies — un autre chantier y travaille ».
Un agent ne voit pas ma conversation. Chaque prompt est donc un dossier autoporteur : l'objectif, les faits établis, la zone de fichiers, le format du rapport, et un bloc d'interdits recopié à l'identique à chaque fois :
- JAMAIS rm / rm -f (même sur des fichiers temporaires — utiliser le scratchpad).
- JAMAIS git commit ni git push (l'orchestrateur committe après contra-validation).
- JAMAIS de commande destructrice DB (prisma migrate reset, DROP, TRUNCATE).
- Pas de process zombie : tout serveur/watcher lancé doit être arrêté ou documenté.
- Rapport final FACTUEL : fichiers exacts, chiffres des gates, écarts avoués — ne pas survendre.Les incidents qui ont écrit ces règles
Chacune de ces règles vient d'un incident réel. Les plus instructifs concernent les tests de bout en bout, qui partagent une même base de données entre agents.
Les Jest zombies. Plusieurs exécutions e2e simultanées sur la même base ont fait casser les beforeAll en cascade : dix tests rouges, et un faux verdict de régression. Le code n'y était pour rien. Mon réflexe depuis : lancer pgrep -f "jest" avant de conclure, tuer les processus orphelins et relancer la suite seule. Si elle passe seule, le problème venait de l'environnement, pas du code. Pour que cela ne se reproduise pas, un verrou par répertoire sérialise les exécutions :
while ! mkdir /tmp/<projet>-e2e.lock 2>/dev/null; do sleep 15; done # acquisition
npx jest --config test/jest-e2e.config.js --runInBand <spec> # un seul jest
rmdir /tmp/<projet>-e2e.lock # TOUJOURS libéré
npx tsx prisma/seed.ts # reseed aprèsCe verrou a lui-même produit deux pièges. Le premier est le verrou orphelin : quand Jest ne se termine pas, à cause de connexions restées ouvertes, la ligne rmdir ne s'exécute jamais. Un verrou présent alors que pgrep ne trouve aucun processus signale donc un orphelin à libérer, suivi d'un nouveau seed. Le second est le code de sortie masqué. Dans cmd; rmdir; seed, le code final est celui du seed, et un échec de Jest disparaît. Je capture donc la valeur explicitement : cmd; EXIT=$?; …; echo "JEST_EXIT=$EXIT".
D'autres pièges concernent la lecture des rapports. Un agent qui affirme que « les échecs sont pré-existants » n'est cru qu'après un git diff de la zone et une exécution isolée. Enfin, quand un agent modifie un contrat d'API, il faut chercher tous les appelants. C'est ce qui s'est passé quand la suppression de compte s'est mise à exiger le mot de passe dans le corps de DELETE /users/me : la fenêtre de confirmation du front devait suivre.
Le bon modèle à la bonne étape
Tous les agents ne tournent pas sur le même modèle. Le principe est le suivant : le modèle cher réfléchit sur peu de tokens, et les modèles bon marché lisent et écrivent beaucoup. Voici la grille que j'utilise (prix API, en dollars par million de tokens, entrée puis sortie) :
- Éclaireur, Haiku 4.5, 1 → 5 : lire beaucoup et rendre peu, sans jugement (grep, logs, inventaire).
- Exécutant, Sonnet 5.5, 2 → 10 : appliquer un plan déjà écrit sur une zone bornée. Plusieurs lots disjoints donnent plusieurs exécutants en parallèle.
- Architecte, Opus 5.5, 5 → 25 : décider comment faire, et relire un diff sensible.
- Oracle, Fable 5.1, 10 → 50 : uniquement en escalade.
Avant tout cela, une question passe en premier : est-ce que ça se scripte ? Créer une branche, lancer des gates, formater ou vérifier qu'un fichier généré est à jour relève d'un script bash, sans modèle. Dans ma revue avant push, les trois premières passes (propreté de la branche, contrôles déterministes sur le diff, gates) ne consomment aucun token. Seul le jugement sur le code est confié à un agent Opus, et ses bloquants sont contra-validés.
L'escalade vers l'oracle suit des critères écrits : argent, authentification, sécurité, données personnelles ou migration irréversible ; demande ambiguë ou qui touche au moins trois modules ; deux plans d'Opus qui divergent ; bug qui a résisté à une vraie tentative. En dehors de ces cas, deux fois le prix d'Opus ne rapporte pas assez. Avant de monter de modèle, je monte l'effort.
Ce que j'en retiens
- Un rapport est une hypothèse. Fichiers, gates et comportement se revérifient soi-même, surtout sur l'argent, la sécurité et les données.
- La preuve se demande dans le prompt. Un curl, une ligne en base ou un test rouge puis vert ; sans preuve, le travail n'est pas terminé.
- Le parallélisme se paie en frontières. Chaque fichier partagé a un seul propriétaire, et les interdits sont répétés dans chaque prompt.
- Le modèle suit l'étape. Un script quand c'est possible, un modèle bon marché pour lire, un modèle cher pour décider.
Vous voulez faire travailler des agents IA sur votre code sans perdre le contrôle de ce qui part en production ? Discutons de votre organisation.