Ce qui s'est passé quand j'ai lâché plusieurs agents IA dans le même dépôt
Je pensais que lancer des sessions d'agents en parallèle multiplierait la productivité. En réalité, en une semaine, les incidents réels se sont enchaînés : branche détournée, commits posés sur la branche d'un autre, commits orphelins repêchés au reflog, jusqu'à la double implémentation de la même fonctionnalité. Une typologie des accidents que produisent des agents parallèles partageant un seul checkout, et les règles nées de cette expérience.
L'essentiel
Le vrai ennemi des sessions d'agents parallèles n'est pas le niveau du modèle, mais le répertoire de travail partagé. HEAD est une variable globale pour tout le monde : le changement de branche d'une session pose les commits d'une autre au mauvais endroit. Quatre types d'accidents vécus en une semaine et leur récupération, puis trois lignes de défense : isolation par worktree, re-vérification de la branche juste avant commit, et réduction du périmètre du portail de vérification.
Sur cette page
À force de travailler avec des agents IA, l'appétit vient naturellement. Pendant qu'une session corrige un bug, une autre pourrait refactorer, et une troisième enquêter sur un ticket, non ? On peut lancer plusieurs modèles, donc la productivité devrait grimper d'autant.
C'est comme ça que j'ai commencé, moi aussi. Et en une semaine, j'ai compris : le vrai ennemi des agents parallèles n'est pas le niveau du modèle, mais le répertoire de travail qu'ils partagent.
HEAD est une variable globale
La cause tient en une phrase. Quand plusieurs sessions partagent un seul checkout git, l'état "branche courante" devient la variable globale de tout le monde.
Imaginez deux personnes travaillant en même temps sur le même ordinateur : l'absurdité saute aux yeux. Mais en lançant les agents, je n'y ai pas pensé. Une session par onglet de terminal, tout a l'air bien cloisonné. Sauf que le système de fichiers est unique, et HEAD aussi. À l'instant où une session fait git checkout, le sol se dérobe sous toutes les autres.
Les accidents de cette semaine-là avaient des types bien nets.
Le détournement de branche. Pendant que la session A travaillait sur sa branche de sujet, la session B a changé de branche pour son propre travail. A a commité sans le savoir, et le commit s'est posé sur la branche de B. Le sens inverse aussi est arrivé : au moment où A allait commiter, la branche avait basculé sur develop, et seul le hook qui bloque les commits directs sur branche protégée a évité l'accident. Sans le hook, c'était passé.
Le commit orphelin. La branche de sujet de la session A a été supprimée par la session B lors d'un nettoyage. Les commits de A, n'appartenant plus à aucune branche, sont devenus orphelins ; j'ai fouillé le reflog, retrouvé le hash, et récupéré le tout au cherry-pick. Heureusement que j'ai pu les repêcher : reflog expiré ou hash introuvable, et le travail s'évaporait en bloc.
La pollution du staging. Au moment où la session A fabriquait son commit, une suppression de fichiers que la session B avait mise en staging traînait dans la zone de staging. Commité tel quel, le commit de A aurait embarqué la suppression de B. En parcourant le diff, un changement inconnu m'a alerté et je l'ai écarté, mais si l'agent n'avait pas regardé le diff juste avant de commiter, personne n'aurait rien vu.
La double implémentation. Le type le plus décourageant. Deux sessions, chacune ignorant l'existence de l'autre, ont implémenté la même fonctionnalité chacune de son côté. Les deux ont fait du bon travail consciencieux, et l'une a dû partir entière à la poubelle. Le temps gagné par la parallélisation, rendu intégralement.
Même le portail de vérification devient un piège mutuel
Les fichiers et les branches n'étaient pas les seuls en cause. Notre harnais comporte un portail qui, avant la fin d'une session, lance l'analyse statique et les tests sur tout le dépôt pour interdire de conclure un travail en état cassé. En session solo, c'est un excellent dispositif.
En sessions parallèles, c'est devenu un piège où chacun fait trébucher l'autre. La session A n'avait touché qu'à la documentation, mais son portail échouait à cause d'une erreur de compilation dans un fichier que la session B était en train de modifier. A brûlait des tours à prouver que "ce n'est pas mon changement", et les mauvais jours, attendait trente minutes que B ait fini de ranger. Le pire incident : une session qui, pour passer le portail, a corrigé le code sur lequel une autre session travaillait. Le portail était devenu une incitation à toucher au travail des autres.
Le contrôle en soi n'était pas fautif. C'est son périmètre, "tout le dépôt", qui est devenu fautif à l'instant où le dépôt a cessé d'appartenir à une seule personne.
Trois lignes de défense
Après avoir vécu les accidents type par type, j'ai monté trois couches de défense.
- L'isolation par worktree comme valeur par défaut. Donnez à chaque session son répertoire de travail indépendant avec
git worktree, et HEAD cesse d'être une variable partagée. Détournement de branche, commits orphelins, pollution du staging disparaissent à la racine. Trois des quatre types d'accidents ci-dessus s'évanouissent d'un seul coup. Mais ce n'est pas gratuit. Dans un monorepo, chaque worktree exige de refaire l'installation des dépendances et la génération de code, et dans un dépôt qui utilise un outil qui s'entend mal avec les worktrees, comme git-crypt, c'est difficile à imposer. Nous gardons d'ailleurs un dépôt en checkout partagé à cause de cette contrainte, d'où la nécessité des deux autres lignes de défense. - Re-vérifier la branche juste avant de commiter. Au début de son travail, la session consigne "je travaille sur cette branche", et juste avant de commiter, elle confronte cette note au HEAD courant. En cas de différence, on arrête le commit et on commence par comprendre la situation. La règle est d'une simplicité presque ridicule, mais tous les accidents de détournement de branche venaient de "le HEAD au moment du commit n'était pas le HEAD que je croyais". Un humain remarquerait le nom de branche affiché dans son prompt ; un agent, lui, ne vérifie pas si on ne le lui demande pas explicitement.
- Restreindre le portail de vérification à mes changements. Le périmètre contrôlé par le portail de fin de session est réduit : non plus tout le dépôt, mais les fichiers que la session a réellement modifiés. Fini d'être bloqué à la sortie par le WIP d'un autre, finie l'incitation à toucher au code d'un autre pour passer le portail. La santé du dépôt entier, la CI la re-vérifie de toute façon sur l'état commité. Le portail de session n'avait pas besoin de tout regarder.
J'y ai ajouté une règle d'exploitation : avant de lancer une session, parcourir les branches et les PR ouvertes pour repérer les périmètres qui se recoupent. La double implémentation n'est pas un problème que git peut bloquer, c'est un problème de dispatch, et il n'y avait pas d'autre choix que de le prévenir par l'habitude plutôt que par l'outil.
Les problèmes de concurrence n'existaient pas que dans les bases de données
En faisant le bilan, le tableau est familier. Ressource partagée, accès concurrent sans verrou, race conditions, et niveaux d'isolation. Exactement le problème que nous avons appris pendant des décennies avec les bases de données et le code multithread, rejoué sur un répertoire de travail.
Avec un seul agent, ce problème n'existe pas. Quand un humain et un agent alternent, l'humain joue implicitement le rôle de coordinateur. Le problème commence à l'instant où les agents deviennent plusieurs et où l'humain lâche la coordination. À partir de là, le répertoire de travail est une ressource partagée qui exige un contrôle de concurrence, et le faire tourner sans isolation, c'est perdre des données à coup sûr, comme les bases de données l'ont toujours fait.
Je ne dis pas de renoncer aux agents parallèles. Je lance encore plusieurs sessions chaque jour. Une seule chose a changé : je ne les enferme plus dans la même pièce.
Articles similaires
- 💻 Dev
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.
- 💻 Dev
Quand l'IA dit "c'est fait", le dispositif qui refuse de la croire
Les agents de codage IA disent que le travail est terminé alors qu'il ne l'est pas. Ils annoncent des tests réussis sans les avoir lancés, un bug corrigé sans l'avoir reproduit. L'histoire du garde-fou de vérification qu'un développeur solo, qui écrit presque tout son code avec l'IA, a mis en place pour n'accepter le "terminé" que sur preuves, jamais sur parole.
- 💻 Dev
J'ai arrêté de croire le "terminé" de l'agent - le gate verify.sh, prove-it
Un agent de codage IA annonce "tests passés" sans avoir lancé les tests. Ce n'est pas un mensonge, c'est un problème structurel. Ce que j'ai appris en construisant prove-it, un gate open source qui transforme le rapport en contrôle - et les quatre façons dont un hook maison de cinq lignes échoue en silence.