Why NextWhy Next
Torna all'elenco
💻 SviluppoLettura di 12 min

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.

  1. Isolamento con worktree come default. Se con git worktree dai 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.
  2. 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.
  3. 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

0 commenti