O que aconteceu quando soltei vários agentes de IA no mesmo repositório
Achei que subir sessões de agente em paralelo dobraria a produtividade. Na prática, em uma semana estouraram acidentes reais em série: branch sequestrada, commit empilhado na branch alheia, commit órfão resgatado pelo reflog, até implementação dupla da mesma funcionalidade. Uma tipologia dos acidentes que agentes paralelos compartilhando um checkout produzem, e as regras que nasceram deles.
Resumo
O verdadeiro inimigo das sessões de agente em paralelo não é a capacidade do modelo, e sim o diretório de trabalho compartilhado. HEAD é a variável global de todo mundo, então a troca de branch de uma sessão empilha o commit da outra no lugar errado. Os quatro tipos de acidente vividos em uma semana com o relato da recuperação, e as três linhas de defesa: isolamento por worktree, reconferência da branch logo antes do commit e redução do escopo do portão de verificação.
Nesta página
Trabalhando com agentes de IA, a ambição vem naturalmente. Enquanto uma sessão corrige um bug, outra faz a refatoração e uma terceira investiga um issue, por que não? Dá para subir vários modelos, então a produtividade cresceria na mesma proporção.
Eu também comecei assim. E em uma semana descobri: o verdadeiro inimigo dos agentes paralelos não é a capacidade do modelo, e sim o diretório de trabalho que eles compartilham.
HEAD é uma variável global
A causa se resume numa frase. Quando várias sessões compartilham um único checkout do git, o estado "branch atual" vira a variável global de todo mundo.
Se você imagina duas pessoas trabalhando ao mesmo tempo no mesmo computador, o estranho salta aos olhos na hora; ao subir os agentes, esse pensamento não me ocorreu. Com uma sessão em cada aba do terminal, elas parecem isoladas entre si. Mas o sistema de arquivos é um só, e o HEAD também. No instante em que uma sessão faz git checkout, o chão de todas as outras muda.
Os acidentes daquela semana tinham tipos nítidos.
Sequestro de branch. Enquanto a sessão A trabalhava numa branch de tópico, a sessão B trocou de branch para fazer o próprio trabalho. A sessão A commitou sem saber disso, e o commit foi parar em cima da branch da B. Também houve o sentido inverso. No momento em que A ia commitar, a branch tinha mudado para develop, e o acidente só foi evitado porque o hook que bloqueia commit direto em branch protegida barrou. Sem o hook, teria entrado.
Commit órfão. A branch de tópico da sessão A foi apagada pela sessão B durante uma faxina. Os commits da A viraram órfãos que não pertenciam a branch nenhuma, e a recuperação foi vasculhar o reflog, achar o hash do commit e resgatar com cherry-pick. Sorte que deu para salvar; se o reflog tivesse expirado ou o hash não aparecesse, o trabalho teria evaporado inteiro.
Contaminação do staging. No momento em que a sessão A montava um commit, uma exclusão de arquivo que a sessão B tinha deixado em staging estava lá junto, na área de staging. Se tivesse commitado daquele jeito, a exclusão da B entraria misturada no commit da A. Só foi filtrado porque, passando o olho no diff, apareceu uma mudança estranha; se o agente não tivesse olhado o diff logo antes do commit, ninguém saberia.
Implementação dupla. O tipo mais melancólico. Duas sessões, sem saber da existência uma da outra, implementaram cada uma a mesma funcionalidade. As duas fizeram um trabalho caprichado, e uma teve que ser jogada fora inteira. O tempo economizado com a paralelização foi devolvido na íntegra.
Até o portão de verificação vira pisada no pé alheio
O problema não parava em arquivos e branches. Nosso harness tem um portão que, antes de a sessão terminar, roda a análise estática e os testes do repositório inteiro, impedindo encerrar o trabalho num estado quebrado. Em sessão solo, é um dispositivo excelente.
Em sessões paralelas, isso virou uma armadilha em que uma trava o pé da outra. A sessão A só mexeu em documentação, mas o portão da A falha por causa do erro de compilação num arquivo que a B está corrigindo naquele exato momento. A gasta turnos provando que "a mudança não é minha" e, num dia ruim, esperou 30 minutos até a B arrumar a casa. O pior episódio foi uma sessão alterar o código em que outra sessão estava trabalhando, só para passar no portão. O portão virou justamente um incentivo para mexer no trabalho alheio.
A checagem em si não está errada. O escopo da checagem ser "o repositório inteiro" é que ficou errado no instante em que o repositório deixou de pertencer a uma pessoa só.
Três linhas de defesa
Depois de viver os acidentes tipo a tipo, levantei a defesa em três camadas.
- Isolamento por worktree como padrão. Dando a cada sessão um diretório de trabalho independente com
git worktree, o HEAD deixa de ser variável compartilhada. Sequestro de branch, commit órfão e contaminação do staging desaparecem pela raiz. Dos quatro tipos de acidente acima, três somem com essa única jogada. Mas não é de graça. Num monorepo, cada worktree exige refazer a instalação de dependências e a geração de código, e em repositórios que usam ferramentas que combinam mal com worktree, como o git-crypt, é difícil impor. Nós mesmos mantemos checkout compartilhado num dos repositórios por causa dessa restrição, e é por isso que as outras duas linhas de defesa são necessárias. - Reconferir a branch logo antes do commit. Quando a sessão começa o trabalho, registra "eu trabalho nesta branch" e, logo antes do commit, compara com o HEAD atual. Se estiver diferente, para o commit e primeiro entende a situação. A regra é simples a ponto de parecer boba, mas todos os acidentes de sequestro de branch nasceram de "o HEAD no momento do commit não era o HEAD que eu conhecia". Uma pessoa notaria pelo nome da branch exibido no prompt; um agente não confere se não for mandado explicitamente.
- Escopar o portão de verificação às minhas mudanças. O alcance do portão de encerramento da sessão estreita: do repositório inteiro para os arquivos que aquela sessão de fato modificou. Some o meu encerramento barrado pelo WIP alheio, e some o incentivo a mexer no código dos outros para passar no portão. A saúde do repositório inteiro o CI revê de qualquer forma, tomando como base o estado commitado. Não havia necessidade de o portão da sessão olhar o todo também.
Em cima disso, uma regra de operação. Antes de subir uma sessão, passo o olho nas branches e nos PRs abertos para ver se há escopo sobreposto. Implementação dupla não é um problema que o git consiga bloquear, é um problema de distribuição de tarefas, então só restou prevenir com hábito, não com ferramenta.
Concorrência nunca foi um problema só de banco de dados
Organizando tudo, o desenho é familiar. Recurso compartilhado, acesso concorrente sem lock, condição de corrida e nível de isolamento. Exatamente o problema que passamos décadas aprendendo em bancos de dados e código multithread, reencenado em cima do diretório de trabalho.
Trabalhando com um único agente, esse problema não existe. Quando pessoa e agente se revezam, a pessoa faz implicitamente o papel de coordenadora. O problema começa no instante em que os agentes viram vários e a pessoa solta a coordenação. Desse ponto em diante, o diretório de trabalho é um recurso compartilhado que precisa de controle de concorrência, e rodar sem isolamento perde dados com certeza, como acontecia com os bancos de dados.
Não estou dizendo para desistir dos agentes paralelos. Até hoje subo várias sessões todos os dias. Só uma coisa mudou. Agora eu não enfio todos eles na mesma sala.
Artigos relacionados
- 💻 Dev
O pipeline que põe outra IA para refutar o código que uma IA corrigiu
Se você pede ao mesmo modelo para revisar o próprio código, ele aprova de novo a conclusão que ele mesmo tirou. Um desenvolvedor solo, que escreve quase todo o código com IA, conta como transformou em pipeline permanente o loop de verificação cruzada em que uma IA manda a outra 'tentar refutar' o trabalho. O caso real em que três rodadas seguidas pegaram bugs que o self-review deixou passar, e o desenho dos prompts.
- 💻 Dev
Quando a IA diz "está pronto", o mecanismo que não acredita nela
Agentes de IA para programação dizem que terminaram sem ter terminado. Afirmam que o teste passou sem tê-lo rodado, dizem que corrigiram sem ter reproduzido o bug. Um desenvolvedor solo, que escreve quase todo o código junto com a IA, conta sobre o portão de verificação que passou a reconhecer a conclusão só por evidências, não por palavras.
- 💻 Dev
Decidi não acreditar no "pronto" do agente - prove-it, o portão do verify.sh
Agentes de IA para programação reportam "testes passando" sem sequer rodar os testes. Não é mentira, é um problema estrutural. O que aprendi construindo o prove-it, um portão open source que transforma o relato em verificação - e as quatro maneiras como um hook de cinco linhas escrito à mão falha em silêncio.