“J’ai un shell et je suis créatif.”
— Claude, expliquant pourquoi il a créé un script de 47 lignes sous forme de chaîne et l’a transmis à
python -c
Cette phrase est réelle. Elle vient de mon agent IA — pas avec ces mots précis, mais exactement avec ces actions. Il devait exécuter un processus dans un pipeline ETL. La commande correcte était dans le Makefile. Mais quelque chose a échoué. Et plutôt que de demander conseil, il a fait ce que ferait n’importe quel développeur avec un accès root et zéro supervision : il a improvisé.
Incroyable.
La confabulation que personne ne voit
J’ai déjà écrit sur le sujet des hallucinations de code : un LLM qui invente un champ JSON, construit un DTO autour, génère des tests… et au final, on se retrouve avec 90 tests verts validant de la fiction. Ce problème est grave, mais au moins, il est statique. Le code inventé reste là, attendant que quelqu’un le révise.
Il existe un autre type de confabulation beaucoup plus dangereux : la confabulation opérationnelle. Lorsque l’agent n’invente pas du code, mais bien des chemins d’exécution.
Le schéma est toujours le même :
Chemin correct échoue → Agent cherche un raccourci → Raccourci "fonctionne" → Dommages invisibles
Voici deux exemples réels tirés d’un pipeline ETL qui agrège des données dispersées de plusieurs sources web.
Cas 1 : Le script en chaîne. Le pipeline comporte une commande make scrape-source qui lance un watchdog chargé d’exécuter des workers. Le watchdog surveille, relance les workers tombés et ferme les connexions orphelines. Un jour, l’agent devait lancer un processus de scraping. Le make a échoué en raison d’un problème de dépendances. Que s’est-il passé ? Il a créé un script Python inline, 47 lignes sous forme de string, qu’il a passé à python -c "...". Sans gestion des erreurs. Sans watchdog. Sans nettoyage. Cela a fonctionné… Jusqu’à ce qu’un worker reste bloqué et ne soit pas relancé. Résultat : des données partielles, des connexions ouvertes, et moi dans l’ignorance totale pendant trois jours.
Cas 2 : Le worker solitaire. Autre session, même pipeline. L’agent a exécuté voyeur worker directement, contournant le watchdog. Le worker a commencé son scraping, a rencontré un timeout réseau, puis s’est mis dans une boucle de retry infini, consommant des ressources. Sans watchdog, personne ne l’a arrêté. Sans journalisation centralisée, personne ne l’a remarqué. Le serveur a été dédié pendant trois heures à réessayer de scraper une page renvoyant un 503.
Dans les deux cas, l’agent a pris une décision localement raisonnable. “Le make échoue, mais je sais comment faire la même chose à la main.” Sauf qu’il ne savait pas exactement. Il maîtrisait 60 %. Les 40 % restants étaient les invariants système qui n’apparaissent jamais dans un README.
Pourquoi interdire ne fonctionne pas
Ma première réaction a été celle de tout le monde : écrire des règles.
| |
Devine comment un LLM lit ça :
| Ce que tu écris | Ce qu’il interprète |
|---|---|
| “NE JAMAIS faire X” | “X est interdit, sauf si je pense que c’est nécessaire” |
| “TOUJOURS utiliser Y” | “Y est préférable, mais si ça échoue, j’improvise” |
| “Faire Z est dangereux” | “Je ferai attention en faisant Z” |
Je l’avais déjà expliqué dans un post précédent : les instructions vagues décrivent des attitudes, alors qu’un LLM a besoin de contraintes impossibles. “Ne cours pas près de la piscine” ne fonctionne pas. Ce qui marche, c’est qu’il n’y ait pas de piscine ou que le sol soit en velcro.
Le LLM croit toujours que son cas est l’exception. Son entraînement l’a optimisé pour… compléter des tâches, démontrer sa compétence, et éviter les obstacles. Quand le chemin correct échoue, ces incitations s’alignent vers une seule direction : “je peux le résoudre moi-même”. Et il le fait. Incorrectement.
La philosophie : impossible, pas interdit
Il existe une idée en ingénierie de sécurité qui fonctionne depuis des décennies : rendre l’incorrect impossible plutôt que prohibé.
Vous ne mettez pas un panneau “ne mettez pas de diesel” sur une voiture à essence. Vous faites en sorte que la buse ne s’insère pas. Vous n’écrivez pas une note sur une prise disant “cet appareil fonctionne à 110V, ne le branchez pas à 220V”. Vous faites que la prise soit différente.
En clair : le système doit empêcher physiquement de faire l’incorrect, et non compter sur la lecture d’un manuel.
Appliquée à un agent IA opérant un pipeline ETL, cette idée se traduit par trois couches de défense.
Couche 1 : Le code se protège lui-même
Si un worker nécessite un watchdog pour fonctionner correctement, le worker contrôle lui-même :
| |
Peu importe combien l’agent est créatif. Il peut écrire python -c "from pipeline import Worker; Worker().run()", le worker retournera une erreur. Aucun contournement possible. Le code se protège.
De même, pour les phases du pipeline. Si la phase 3 (consolidation) a besoin que la phase 1 (scraping) soit terminée, elle vérifie avant de commencer :
| |
Ce n’est pas un test. Ce n’est pas une règle dans un fichier de configuration. C’est un code qui s’exécute à chaque fois et qui ne dépend pas de la lecture préalable d’un README.
[Le reste du contenu traduit…]