Lo que pasó cuando solté varios agentes de IA en un mismo repo
Pensé que lanzar sesiones de agente en paralelo multiplicaría la productividad. En la realidad, en una sola semana estallaron accidentes en cadena: secuestro de ramas, commits montados sobre la rama de otro, commits huérfanos rescatados con el reflog y hasta la doble implementación de la misma funcionalidad. Una tipología de los accidentes que crean los agentes paralelos que comparten un checkout, y las reglas que levanté tras sufrirlos.
Lo esencial
El verdadero enemigo de las sesiones de agente en paralelo no es la capacidad del modelo, sino el directorio de trabajo compartido. HEAD es la variable global de todos: el cambio de rama de una sesión monta el commit de otra en el sitio equivocado. Los cuatro tipos de accidente vividos en una semana con sus recuperaciones, y las tres líneas de defensa: aislamiento con worktree, reverificar la rama justo antes del commit y reducir el alcance de la puerta de verificación.
En esta página
Trabajando con agentes de IA, la ambición llega sola. Mientras una sesión arregla un bug, otra puede ir refactorizando, y una tercera investigando un issue, ¿no? Si puedo lanzar varios modelos, la productividad crecerá en la misma proporción.
Yo también empecé así. Y en una semana lo aprendí: el verdadero enemigo de los agentes paralelos no es la capacidad del modelo, sino el directorio de trabajo que comparten.
HEAD es una variable global
La causa se resume en una frase. Cuando varias sesiones comparten un único checkout de git, el estado "rama actual" se convierte en la variable global de todos.
Si imaginas a dos personas trabajando a la vez en el mismo ordenador, lo raro se ve enseguida; pero al lanzar los agentes no lo pensé. Con una sesión en cada pestaña del terminal, parecen aisladas entre sí. Pero el sistema de archivos es uno, y HEAD también es uno. En el momento en que una sesión hace git checkout, el suelo se mueve bajo los pies de todas las demás.
Los accidentes que estallaron aquella semana tenían tipos bien nítidos.
Secuestro de rama. Mientras la sesión A trabajaba en su rama de tema, la sesión B cambió de rama para hacer lo suyo. A hizo commit sin enterarse, y el commit quedó montado sobre la rama de B. También hubo caso en la dirección contraria: en el momento en que A iba a hacer commit, la rama había cambiado a develop, y solo el hook que bloquea los commits directos a ramas protegidas evitó el accidente. Sin ese hook, habría entrado tal cual.
Commits huérfanos. La rama de tema de la sesión A la borró la sesión B durante una limpieza. Los commits de A quedaron huérfanos, sin pertenecer a ninguna rama, y hubo que rebuscar en el reflog, encontrar el hash del commit y recuperarlo con cherry-pick. Menos mal que se rescató: si el reflog hubiera expirado o no lo hubiera encontrado, el trabajo entero se habría evaporado.
Contaminación del staging. En el momento en que la sesión A creaba su commit, el borrado de archivos que la sesión B había dejado en staging estaba subido en el área de staging junto a lo demás. De haber hecho commit tal cual, el borrado de B se habría colado en el commit de A. Se filtró porque, repasando el diff, apareció un cambio desconocido; si el agente no hubiera mirado el diff justo antes del commit, nadie se habría enterado.
Doble implementación. El tipo más descorazonador. Dos sesiones, sin saber de la existencia de la otra, implementaron cada una la misma funcionalidad. Las dos la hicieron bien y con diligencia, y una hubo que tirarla entera. El tiempo ahorrado con la paralelización, devuelto tal cual.
Hasta en la puerta de verificación se pisan entre ellas
El problema no eran solo los archivos y las ramas. Nuestro harness tiene una puerta que, antes de que una sesión termine, ejecuta el análisis estático y los tests de todo el repo para que no se pueda cerrar el trabajo en estado roto. En una sesión en solitario es un mecanismo excelente.
Con sesiones en paralelo se convirtió en una trampa en la que unas les ponen la zancadilla a otras. La sesión A solo había tocado documentación, pero su puerta fallaba por el error de compilación de un archivo que la sesión B estaba arreglando en ese momento. A gastaba turnos en demostrar que "este cambio no es mío" y, en el peor día, esperó 30 minutos a que B terminara de ordenar lo suyo. El peor incidente fue cuando una sesión, para pasar la puerta, modificó directamente el código en el que otra sesión estaba trabajando. La puerta se convirtió justamente en el incentivo para meter mano en el trabajo ajeno.
La comprobación en sí no está mal. Lo que se volvió un error fue que su alcance fuera "el repo entero" en el momento en que el repo dejó de ser de una sola persona.
Tres líneas de defensa
Después de sufrir los accidentes tipo por tipo, levanté una defensa de tres capas.
- Aislamiento con worktree como valor por defecto. Si con
git worktreele das a cada sesión un directorio de trabajo independiente, HEAD deja de ser una variable compartida. El secuestro de ramas, los commits huérfanos y la contaminación del staging desaparecen de raíz. Tres de los cuatro tipos de accidente de arriba se esfuman con esta sola jugada. Pero no es gratis. En un monorepo hay que repetir la instalación de dependencias y la generación de código en cada worktree, y en repos que usan herramientas que se llevan mal con los worktree, como git-crypt, es difícil imponerlo. Nosotros mismos mantenemos un repo con checkout compartido por esta restricción, y por eso hacen falta las otras dos líneas de defensa. - Reverificar la rama justo antes del commit. Cuando la sesión empieza a trabajar, deja registrado "yo trabajo en esta rama", y justo antes del commit lo contrasta con el HEAD actual. Si difieren, detiene el commit y primero averigua la situación. La regla es tan simple que casi da vergüenza, pero todos los accidentes de secuestro de rama nacieron de lo mismo: "el HEAD del momento del commit no era el HEAD que yo conocía". Una persona lo notaría porque el nombre de la rama aparece en el prompt, pero un agente no lo comprueba si no se lo mandas explícitamente.
- Acotar la puerta de verificación a mis cambios. El alcance que inspecciona la puerta de cierre de sesión se estrecha: no el repo entero, sino los archivos que esa sesión modificó de verdad. Desaparece tanto el que mi cierre quede bloqueado por el WIP ajeno como el incentivo de tocar el código de otro para pasar la puerta. La salud del repo completo la vuelve a mirar de todos modos la CI sobre el estado ya commiteado. No hacía falta que la puerta de sesión mirara también el conjunto.
A eso le sumé una regla operativa. Antes de lanzar una sesión, repaso las ramas y PR abiertos para ver si hay solapamiento de alcance. La doble implementación no es un problema que git pueda bloquear, sino un problema de despacho de tareas, así que no quedaba otra que atajarlo con hábito y no con herramientas.
Los problemas de concurrencia no eran solo cosa de las bases de datos
Puesto en orden, el cuadro resulta familiar. Recurso compartido, acceso concurrente sin locks, condiciones de carrera y niveles de aislamiento. El mismo problema que llevamos décadas aprendiendo con las bases de datos y el código multihilo, reproducido sobre el directorio de trabajo.
Trabajando con un solo agente, este problema no existe. Cuando persona y agente se turnan, la persona hace implícitamente de coordinadora. El problema empieza en el momento en que los agentes son varios y la persona suelta la coordinación. A partir de ahí, el directorio de trabajo es un recurso compartido que necesita control de concurrencia y, si lo haces girar sin aislamiento, pierdes datos sin falta, igual que les pasaba a las bases de datos.
No estoy diciendo que haya que renunciar a los agentes paralelos. Sigo lanzando varias sesiones cada día. Lo que cambió es una sola cosa: ya no las meto a todas en la misma habitación.
Artículos relacionados
- 💻 Desarrollo
El pipeline donde otra IA intenta refutar el código que arregló la primera
Si le pides al mismo modelo que revise su propio código, vuelve a aprobar su propia conclusión. La historia de un desarrollador en solitario, que escribe casi todo su código con IA, y de cómo convirtió en pipeline permanente un bucle de verificación cruzada en el que le pide a otra IA que "intente rebatir" el trabajo de la primera. El caso real en el que tres rondas seguidas sacaron bugs que la autorevisión había dejado pasar, y el diseño de los prompts.
- 💻 Desarrollo
Cuando la IA dice "ya está", el dispositivo que no se lo cree
Los agentes de codificación con IA dicen que han terminado sin haber terminado. Aseguran que los tests pasan sin haberlos ejecutado, y que han arreglado el bug sin haberlo reproducido. La historia de un desarrollador en solitario, que escribe casi todo su código con IA, sobre la puerta de verificación que la obligó a aceptar el "terminado" solo con pruebas, no con palabras.
- 💻 Desarrollo
Decidí dejar de creerme el "listo" de mi agente - prove-it, un gate con verify.sh
Los agentes de codificación con IA reportan "tests en verde" sin haberlos ejecutado. No es una mentira, es un problema estructural. Lo que aprendí construyendo prove-it, un gate open source que convierte el reporte en una comprobación - y las cuatro formas en que un hook casero de cinco líneas falla en silencio.