Cosa è successo quando ho scatenato più agenti AI nello stesso repo
Pensavo che lanciare sessioni di agenti in parallelo avrebbe moltiplicato la produttività. In realtà, nel giro di una settimana, si sono susseguiti incidenti veri: branch dirottati, commit finiti sul branch di qualcun altro, commit orfani recuperati dal reflog, perfino la doppia implementazione della stessa feature. Una tassonomia degli incidenti causati da agenti paralleli che condividono un checkout, e le regole nate da quell'esperienza.
In sintesi
Il vero nemico delle sessioni di agenti paralleli non è la capacità del modello, ma la directory di lavoro condivisa. HEAD è una variabile globale di tutti, quindi il cambio di branch di una sessione deposita i commit di un'altra nel posto sbagliato. Quattro tipi di incidenti vissuti in una settimana con relativi recuperi, e tre linee di difesa: isolamento con worktree, ricontrollo del branch prima del commit, restringimento dello scope del cancello di verifica.
In questa pagina
Lavorando con gli agenti AI la tentazione viene naturale. Mentre una sessione corregge un bug, un'altra può fare refactoring e una terza indagare un issue, no? I modelli si possono lanciare in parallelo, quindi anche la produttività crescerà in proporzione.
Anch'io ho cominciato così. E in una settimana ho capito che il vero nemico degli agenti paralleli non è la capacità del modello, ma la directory di lavoro che condividono.
HEAD è una variabile globale
La causa si riassume in una frase: se più sessioni condividono un solo checkout git, lo stato "branch corrente" diventa una variabile globale di tutti.
Se immagini due persone che lavorano contemporaneamente sullo stesso computer, l'assurdità salta subito all'occhio; eppure, lanciando gli agenti, a questo non avevo pensato. Con una sessione per ogni tab del terminale, sembrano isolate tra loro. Ma il filesystem è uno solo, e anche HEAD è uno solo. Nel momento in cui una sessione fa git checkout, il terreno sotto i piedi di tutte le altre cambia.
Gli incidenti scoppiati in quella settimana avevano tipologie ben nette.
Dirottamento di branch. Mentre la sessione A lavorava su un topic branch, la sessione B ha cambiato branch per fare il proprio lavoro. A ha committato senza saperlo, e il commit è finito sopra il branch di B. C'è stato anche il caso opposto: nel momento in cui A stava per committare, il branch era diventato develop, e solo il hook che blocca i commit diretti sui branch protetti ha evitato il disastro. Senza il hook, sarebbe entrato dritto.
Commit orfani. Il topic branch della sessione A è stato cancellato dalla sessione B durante un lavoro di pulizia. I commit di A sono diventati orfani, non appartenenti a nessun branch; ho frugato nel reflog, trovato l'hash del commit e li ho recuperati con cherry-pick. Per fortuna li ho ripescati: se il reflog fosse scaduto o non avessi trovato nulla, il lavoro sarebbe evaporato in blocco.
Contaminazione della staging area. Nel momento in cui la sessione A creava un commit, nella staging area c'era anche una cancellazione di file messa in staging dalla sessione B. Se avesse committato così com'era, la cancellazione di B si sarebbe mescolata nel commit di A. L'ho filtrata perché scorrendo il diff è comparsa una modifica che non riconoscevo; se l'agente non avesse guardato il diff subito prima del commit, non l'avremmo mai saputo.
Doppia implementazione. Il tipo più avvilente. Due sessioni, ignare l'una dell'altra, hanno implementato ciascuna la stessa feature. Entrambe l'hanno fatta bene e con diligenza, e una delle due è dovuta finire nel cestino per intero. Il tempo risparmiato con la parallelizzazione, restituito tale e quale.
Anche i cancelli di verifica si pestano i piedi a vicenda
Il problema non erano solo file e branch. Nel nostro harness c'è un cancello che, prima della chiusura di una sessione, esegue analisi statica e test sull'intero repo, impedendo di concludere il lavoro in uno stato rotto. In una sessione singola è un dispositivo eccellente.
Con le sessioni parallele è diventato una trappola in cui ci si fa lo sgambetto a vicenda. La sessione A aveva toccato solo la documentazione, ma il suo cancello falliva per un errore di compilazione in un file che la sessione B stava ancora sistemando. A bruciava turni a dimostrare "non è una modifica mia", e nei giorni peggiori aspettava 30 minuti che B mettesse ordine. L'episodio più grave: una sessione, pur di superare il cancello, ha modificato il codice su cui un'altra sessione stava lavorando. Il cancello stesso era diventato un incentivo a mettere le mani nel lavoro altrui.
Non è il controllo in sé a essere sbagliato. È lo scope del controllo, "l'intero repo", a essere diventato sbagliato nel momento in cui il repo ha smesso di appartenere a una persona sola.
Tre linee di difesa
Dopo aver vissuto gli incidenti tipo per tipo, ho eretto tre strati di difesa.
- Isolamento con worktree come default. Se con
git worktreedai a ogni sessione una directory di lavoro indipendente, HEAD smette di essere una variabile condivisa. Dirottamento di branch, commit orfani e contaminazione della staging spariscono alla radice. Tre dei quattro tipi di incidente di cui sopra si eliminano con questa sola mossa. Ma non è gratis. In un monorepo bisogna rifare installazione delle dipendenze e generazione del codice per ogni worktree, e nei repo che usano strumenti poco compatibili con i worktree, come git-crypt, è difficile imporlo. Anche noi, per questo vincolo, in un repo manteniamo il checkout condiviso, ed è per questo che servono le altre due linee di difesa. - Ricontrollare il branch subito prima del commit. Quando la sessione inizia a lavorare registra "io lavoro su questo branch", e subito prima del commit lo confronta con l'HEAD corrente. Se non coincidono, ferma il commit e prima chiarisce la situazione. La regola è banale fino al ridicolo, ma tutti gli incidenti di dirottamento nascevano da "al momento del commit, l'HEAD non era l'HEAD che conoscevo". Una persona se ne accorgerebbe dal nome del branch nel prompt, ma un agente, se non glielo imponi esplicitamente, non controlla.
- Restringere il cancello di verifica alle mie modifiche. L'ambito che il cancello di fine sessione controlla passa dall'intero repo ai soli file che quella sessione ha effettivamente modificato. Sparisce sia il caso in cui la mia chiusura viene bloccata dal WIP altrui, sia l'incentivo a toccare il codice altrui per superare il cancello. La salute dell'intero repo la ricontrolla comunque la CI sullo stato committato. Non c'era bisogno che anche il cancello di sessione guardasse tutto.
A questo ho aggiunto una regola operativa: prima di lanciare una sessione, scorro i branch e le PR aperte per vedere se ci sono scope che si sovrappongono. La doppia implementazione non è un problema che git possa bloccare, è un problema di smistamento, quindi non restava che prevenirla con l'abitudine anziché con gli strumenti.
I problemi di concorrenza non stavano solo nei database
Tirando le somme, il quadro è familiare: risorsa condivisa, accessi concorrenti senza lock, race condition, e livelli di isolamento. Esattamente il problema che abbiamo studiato per decenni su database e codice multithread, riproposto sulla directory di lavoro.
Con un solo agente questo problema non esiste. Anche quando persona e agente si alternano, è la persona a fare implicitamente da coordinatore. Il problema comincia nel momento in cui gli agenti diventano tanti e la persona molla il coordinamento. Da quel punto in poi la directory di lavoro è una risorsa condivisa che richiede controllo di concorrenza, e se la fai girare senza isolamento perdi dati con certezza, come è sempre successo ai database.
Non sto dicendo di rinunciare agli agenti paralleli. Anche adesso lancio più sessioni ogni giorno. È cambiata una cosa sola: non li chiudo più tutti nella stessa stanza.
Articoli correlati
- 💻 Sviluppo
La pipeline in cui una seconda AI prova a confutare il codice corretto dalla prima
Se chiedi allo stesso modello di revisionare il proprio codice, riapprova le conclusioni che ha già tirato. La storia di uno sviluppatore solitario, che scrive quasi tutto il codice insieme all'AI, e di come ha trasformato in pipeline permanente un loop di verifica incrociata in cui a un'AI viene chiesto di 'confutare' il lavoro di un'altra. Il caso reale di un bug che ha rivelato difetti per tre round consecutivi sfuggiti alla self review, e il design dei prompt.
- 💻 Sviluppo
Quando l'AI dice "è fatto", il meccanismo per non crederle
Gli agenti di coding AI dicono di aver finito quando non hanno finito. Dichiarano che i test passano senza averli eseguiti, dicono di aver corretto un bug senza averlo riprodotto. La storia del cancello di verifica con cui uno sviluppatore solitario, che scrive quasi tutto il codice insieme all'AI, ha reso il completamento riconoscibile solo per prove, non per parole.
- 💻 Sviluppo
Ho smesso di credere al "fatto" dell'agente - prove-it, un cancello su verify.sh
Un agente di coding AI riporta "i test passano" senza averli eseguiti. Non è una bugia, è un problema strutturale. Cosa ho imparato costruendo prove-it, un cancello open source che trasforma il resoconto in un controllo - e i quattro modi in cui un hook di cinque righe scritto a mano fallisce in silenzio.