Hermes a réécrit son propre harnais ! Il s'est auto-modifié en silence.
Un agent modifie le code de son harnais pour répondre à une demande banale. Deux histoires en une : comment un agent en arrive à se modifier (et comment l'en empêcher), et pourquoi son correctif avait le même angle mort que ce qu'il corrigeait.
Un soir de mai, en début de soirée, j’ai demandé quelque chose de banal à Hermes, l’agent autonome qui tourne en
permanence sur une machine de mon lab. Une phrase envoyée depuis une messagerie : « préviens-moi à
la fin de chaque tâche planifiée, qu’elle réussisse ou qu’elle échoue ». J’ai même précisé le
moyen : réutilise le canal de notification déjà configuré pour communiquer avec moi.
Dix minutes plus tard, c’était fait. Pas comme je l’imaginais.
La demande n’était pas si banale, en fait. Le planificateur de tâches savait annoncer qu’une tâche
avait tourné ; il n’avait aucun moyen de signaler ses propres ratés. L’information que je réclamais
n’existait qu’à un seul endroit : dans la fonction qui exécute les tâches, au cœur du harnais.
Dans son propre code.
Alors Hermes l’a modifié. Cent soixante-huit lignes ajoutées au code de son propre harnais, trois
nouvelles fonctions, deux points d’appel insérés dans le flux d’exécution. Puis il a testé en
envoyant un vrai message, reçu, et considéré le travail terminé. Il n’a pas suivi ma consigne au
passage : au lieu de réutiliser le canal configuré, il a écrit son propre appel.
Il y a deux histoires là-dedans, et elles ne disent pas la même chose :
Un agent qui modifie le programme qui le fait tourner, sans le dire : c’est la première, la plus
spectaculaire. Celle qui fait penser à Skynet, et à Arnold.
Ce que vaut le code qu’il a écrit ce soir-là : c’est la seconde, et c’est la pire.
Un agent qui modifie le programme qui le fait tourner
Ce n’est pas une histoire d’intelligence, c’est une histoire de topologie.
Ce type d’agent s’installe en clonant son code source, et fonctionne directement depuis ce dossier.
Le source est le programme en marche. Modifier un fichier, c’est modifier le comportement, tout
de suite, sans compilation, sans déploiement, sans aucune étape où quelqu’un pourrait regarder.
Donnez à l’agent des outils capables d’écrire sur le disque, et l’auto-modification cesse d’être une
prouesse : c’est parfois le chemin le plus court entre la demande et le résultat.
Deux topologies. La chaîne classique interpose des postes de contrôle entre la source et le programme en marche ; pour l'agent, le dossier cloné est le programme en marche, et il n'y a aucun endroit où regarder.
La bonne question n’est donc pas pourquoi il l’a fait. C’est pourquoi je pensais qu’il ne le ferait
pas.
Un détail de conception a même joué les complices. Le dossier contient un guide destiné aux
assistants de codage des développeurs (le AGENTS.md : les consignes données aux contributeurs
qui font évoluer le produit avec une IA). Sauf que le moteur injecte ce guide dans le contexte de
tout agent qui touche au dossier, sans distinguer l’assistant d’un développeur de l’agent en
production. Hermes a donc reçu, en lisant un simple fichier de son propre arbre, un mode d’emploi
de contributeur qui ne lui était pas adressé. Il l’a suivi ; ces instructions expliquent
précisément comment coder pour faire évoluer Hermes.
Et le sort de ces 168 lignes est la partie que je n’avais pas anticipée. Une modification posée
directement dans l’installation, hors de tout dépôt, meurt en principe à la mise à jour suivante :
écrasée, perdue, travail pour rien. C’est bien ce qui aurait dû arriver. Ce qui l’en a empêchée,
c’est un troisième personnage. Mon assistant qui opère cette machine (une autre IA, pas Hermes)
préparait la montée de version : il a mis de côté les modifications locales, installé la nouvelle
version, puis tout réappliqué par-dessus. Sans lire le diff. Les 168 lignes sauvages ont traversé la
mise à jour dans ce wagon, transportées par un opérateur qui prenait, lui aussi, le chemin le plus
court.
Deux IA, deux erreurs différentes, le même pli : l’une écrit dans le code qu’elle exécute, l’autre
réapplique ce qu’elle n’a pas lu.
Comment on s’en protège
L’éditeur du produit le dit lui-même dans son fichier de sécurité : la seule frontière face à un
modèle qui déraille est le système d’exploitation. Ni les listes d’outils autorisés, ni les portes
d’approbation. Et il applique sa doctrine dans son image conteneurisée : arbre possédé par un compte
privilégié, agent qui n’écrit pas chez lui. Une installation locale n’en bénéficie pas. La mienne
n’en bénéficiait pas.
Le correctif avait le même angle mort que ce qu’il corrigeait
Le soir même, deux lignes s’inscrivent coup sur coup dans le journal. Une tâche planifiée échoue
pour de bon, une erreur de connexion au modèle. Puis, juste derrière, une erreur sur la notification
censée me signaler cet échec : le fameux branchement écrit à la place du canal configuré ne
fonctionnait pas.
Le mécanisme que j’avais demandé pour être prévenu des échecs a échoué à me prévenir d’un échec, le
jour de sa mise en place. Et il a consigné cette double défaillance dans un fichier que personne ne
lisait.
Ce silence, rien ne l’a fermé de l’intérieur. C’est une mise à jour de routine, au petit matin,
qui a comparé le dossier installé à ce qui avait été téléchargé et trouvé 168 lignes en trop. Sans
elle, il durait encore. L’enquête forensique que j’ai menée a ensuite désigné l’auteur par preuve
directe : les noms des fonctions inventées n’existaient nulle part ailleurs que dans une session
archivée, avec la séquence complète.
La chronologie de l'incident, non à l'échelle. Le silence est fermé par le hasard d'une mise à jour de routine ; la preuve n'arrive que le surlendemain.
Regardez la forme de l’incident, pas ses détails. Le planificateur ne savait pas signaler ses
propres ratés : c’est la raison d’être de toute cette histoire. Hermes a construit le mécanisme qui
manquait. Et ce mécanisme ne savait pas signaler les siens. La même impasse, reproduite à
l’identique par le correctif censé la fermer.
La même forme, deux fois. Le planificateur ne pouvait pas signaler son propre échec ; le mécanisme ajouté pour y remédier n'a pas pu signaler le sien.
Le code ne cachait rien, c’est ce qui me frappe encore. Il écrivait honnêtement son propre échec, à
chaque fois, consciencieusement. Dans un journal que personne ne lit. Un journal que personne ne lit
n’est pas une alerte, c’est une archive.
Un agent qui se modifie et qui le dit, c’est gênant, et ça se confine : la première histoire a ses
parades. Un agent qui se modifie, se trompe, et dont l’erreur ne remonte à personne, c’est le vrai
problème. Le mien a fait ce qu’on lui demandait : crier quand quelque chose casse. Il a crié dans
une pièce vide, et noté dans son cahier qu’il avait crié.