Vários agentes de programação, um repositório: reivindicações em vez de travas
Agentes de programação em paralelo colidem na hora do merge porque nenhum sabe o que os outros estão editando. Os agentes na nuvem do Foxl Code agora reivindicam os arquivos que vão alterar e ficam sabendo quais dos seus outros agentes ativos já reivindicaram um caminho que se sobrepõe. A reivindicação é registrada de qualquer jeito e não trava nada; num conflito, o agente é instruído a mandar a mudança para o detentor, esperar ou perguntar a você. A saída real do comando lançado, as regras dos globs, o custo no banco de dados e se os agentes seguem mesmo a regra, coisa que ainda não sabemos.

Nesta página
Coloque vários agentes de programação no mesmo repositório e eles colidem na hora do merge, porque nenhum sabe o que os outros estão editando. O Foxl Code roda na nuvem agentes de programação como o Claude Code e o Codex, e desde a v0.9.3-beta.5 cada agente é instruído a reivindicar os caminhos que vai alterar rodando foxl-agents claim <paths> antes de editar. Se outro agente do mesmo usuário que ainda está rodando (um agente ativo) já reivindicou um caminho que se sobrepõe, a resposta diz qual é esse agente e a nota que ele deixou. Nada fica trancado. Num conflito, o agente é instruído a mandar para o detentor a mudança de que precisa, esperar que o detentor libere a reivindicação ou perguntar ao usuário, e as reivindicações dele somem quando a execução termina.
Por que agentes de programação em paralelo colidem
Cada agente trabalha no próprio checkout, na própria branch, então nada impede que dois deles mudem o mesmo arquivo. O Git aponta um conflito textual quando o segundo pull request é mergeado, depois que os dois agentes já terminaram e já foram pagos. Um conflito semântico, em que dois pull requests passam cada um nas suas verificações mas quebram o build quando os dois entram, não é apontado por nada até que alguém faça o merge dos dois. As reivindicações só pegam esse tipo quando os dois agentes mexem nos mesmos caminhos.
A nossa própria configuração nos mostrou o que ajuda. Desenvolvemos o Foxl com 15 a 20 sessões do Claude Code rodando em paralelo no repositório dele, e em 14 dias elas trocaram 4.773 mensagens. O que impedia que uma sobrescrevesse o trabalho da outra era uma lista de responsabilidades compartilhada. Uma sessão coordenadora registra qual sessão é dona de quais arquivos (e, para um arquivo que muitas sessões tocam, de quais partes dele), e uma sessão que precisa de uma mudança num arquivo que não é dela manda a mudança para a dona em vez de fazê-la. Antes da lista, as sessões editavam os mesmos arquivos ao mesmo tempo e só descobriam no merge. A lista tinha o seu próprio custo: das 123 vezes em que uma sessão ficou bloqueada nesses 14 dias, 33 foram esperando a resposta da coordenadora.
O Foxl Code já tinha a parte de mensagens: um comando foxl-agents que cada agente na nuvem pode usar para listar os outros agentes ativos do usuário e mandar mensagens para eles. As reivindicações acrescentam a lista de responsabilidades sem coordenador: cada agente diz o que vai tocar, e o banco de dados responde numa única requisição.
Por que reivindicações em vez de travas?
Uma trava só funciona se todos os agentes a respeitarem e todos a liberarem. Um agente de programação que cai, estoura o orçamento ou é cancelado não libera nada, então uma trava precisa de um tempo limite, e um tempo limite longo o bastante para uma execução de verdade (as nossas podem passar de sete horas) bloqueia todo mundo por esse tempo. Dois agentes que precisam cada um de uma linha do arquivo do outro esperariam um pelo outro para sempre. E um agente cuja trava é recusada não tem mais nada a fazer além de parar.
As reivindicações contornam cada um desses problemas. Um detentor que caiu deixa de contar assim que a execução dele não está mais ativa, então não há tempo limite para esperar passar. Uma reivindicação que se sobrepõe a outra não é recusada; ela é registrada, e a sobreposição volta para o agente que pediu, com o id da tarefa do detentor, o título, os caminhos reivindicados e a nota. E um agente que encontra um conflito é instruído primeiro a mandar para o detentor a mudança de que precisa, então dois agentes que precisam dos arquivos um do outro têm algo para fazer além de esperar, embora nada impeça que os dois esperem mesmo assim. Estas são as instruções que cada agente recebe, palavra por palavra:
- Before you start editing, claim the files and directories you expect to
change, in one command: `foxl-agents claim <path>... --note "<why>"`. A
directory claims everything under it; `*`, `?` and `**` globs work. Claim
what you will change, not the whole repository, and claim again if your
plan grows.
- The answer names any other live agent that already claimed an overlapping
path. On a CONFLICT, do not edit those files: send the holder the exact
change you need with `foxl-agents send <task-id> "<the change>"`, wait until
it releases them (check `foxl-agents claims` at a natural checkpoint, never
in a loop or behind a `sleep`), or tell the user. A claim is information,
not a lock.
- `foxl-agents release <path>...` drops a claim you no longer need. Every
claim you hold is dropped when your run ends.Essas linhas são acrescentadas ao arquivo de instruções no nível do usuário que cada CLI lê: CLAUDE.md para o Claude Code, e AGENTS.md para o Codex, o Kiro, o Cursor, o Hermes e o OpenCode. O mesmo mecanismo precisa chegar a seis CLIs diferentes, e é por isso que o foxl-agents é um comando de shell e não um servidor MCP. Toda CLI de programação consegue rodar um comando de shell, então um único script funciona nas seis, sem configurar cada uma para carregar um servidor.
O que um agente roda e o que ele vê
Abaixo está a saída real do comando que foi lançado, rodado por um script e não por um agente, para duas tarefas de demonstração num repositório de demonstração. A tarefa A é uma tarefa do Codex que adiciona limitação de taxa, e a tarefa B é uma tarefa do Claude Code que adiciona paginação ao endpoint de usuários. O servidor, o banco de dados dele e o comando são os mesmos que os agentes na nuvem recebem.

A tarefa B reivindicou src/api/ com uma barra no final, que indica um diretório, e o servidor guardou isso como src/api/**. Sem a barra, o próprio comando teria acrescentado /**: ele roda dentro do checkout, o único lugar que consegue distinguir um arquivo de um diretório. O comando também reescreve cada caminho em relação à raiz do repositório, seja qual for o diretório de trabalho, e recusa um caminho fora do checkout antes de enviar qualquer coisa.
O conflito cita os dois arquivos da tarefa A e a nota dela, e a tarefa B decide a partir da nota. Aqui, ela poderia deixar o middleware em paz e mandar para a tarefa A a única mudança de que precisa ali, ou começar pelo handler próprio do endpoint e voltar a conferir quando a tarefa A tiver liberado.
Como saber se dois globs se sobrepõem?
Duas reivindicações se sobrepõem quando pelo menos um caminho casa com as duas. A gramática é pequena de propósito:
- um caminho simples é um glob sem curingas;
*casa dentro de um único segmento do caminho;?casa exatamente um caractere;- um segmento que é exatamente
**casa qualquer número de segmentos inteiros; - qualquer outro caractere é literal, então
[id].tsxé um nome de arquivo, não uma classe de caracteres.
Veja o que o comparador lançado devolve para alguns pares:
| reivindicação A | reivindicação B | sobreposição |
|---|---|---|
src/api/** | src/api/users.ts | sim |
src/*.ts | src/api/users.ts | não, o * fica num segmento só |
src/**/*.test.ts | src/api/*.ts | sim, os dois casam com src/api/x.test.ts |
docs/** | src/** | não |
src/app/[id]/page.tsx | src/app/a/page.tsx | não, [id] é literal |
**/*.md | docs/guide.md | sim |
Uma sobreposição nunca faz uma reivindicação ser recusada, mas algumas formas fazem:
- um glob feito só de curingas que inclua
**, como**ou**/*, porque ele reivindicaria o repositório inteiro; - um segmento de caminho
.., uma barra invertida ou um caractere de controle; - um
!no início, porque aqui ele não é uma negação e não deve parecer uma.
Uma tarefa tem no máximo 50 reivindicações, cada uma com até 200 caracteres, e notas de até 280 caracteres.
A comparação tem um limite, para que um conjunto incomum de reivindicações não custe muita CPU ao servidor. Duas reivindicações cujos segmentos literais iniciais são diferentes (docs/** e src/**) são descartadas na hora. Fora isso, o servidor faz um percurso memoizado sobre pares de segmentos e, dentro de um segmento, sobre pares de caracteres. Todas as comparações de uma requisição dividem um orçamento de 50.000 passos, e um par que o orçamento nunca alcança é informado como possível sobreposição em vez de ser descartado, então uma requisição que esgota o orçamento ainda avisa o agente.
Quando uma reivindicação termina
Uma reivindicação pertence a uma execução. Quando a execução termina, a tarefa descarta todas as reivindicações que fez, no mesmo passo que registra a execução como concluída. Duas verificações na hora da leitura cobrem uma execução que nunca chegou a esse passo: uma reivindicação cujo detentor não está mais ativo é ignorada, e o mesmo vale para uma reivindicação mais antiga que o máximo que uma execução pode durar, 7 horas e 20 minutos. As reivindicações têm a mesma visibilidade que as mensagens: só os agentes ativos de um mesmo usuário, trabalhando pela mesma instalação do GitHub App no mesmo repositório, veem as reivindicações uns dos outros.

Quanto custa num plano gratuito de banco de dados
As reivindicações ficam numa única tabela com um único índice em (usuário, repositório, horário de criação), e nada faz polling nela. Cada leitura pede só as reivindicações criadas nas últimas 7 horas e 20 minutos, e o horário de criação é a última coluna do índice, então as linhas mais antigas ficam fora do intervalo que o banco percorre. Reivindicações vencidas que ninguém liberou acrescentam uma linha a uma leitura, sejam 1.000 ou 2.000; medimos os dois casos.
Medimos isso porque o Foxl roda no plano gratuito da Cloudflare, em que todos os bancos D1 de uma conta dividem 5 milhões de linhas lidas e 100.000 linhas gravadas por dia, e a conta tem 100.000 requisições de Worker por dia. Os números abaixo vêm do D1 rodando localmente no workerd, o runtime da Cloudflare, e ainda não foram confirmados pelas métricas do próprio D1 em produção:
| chamada | linhas lidas | linhas gravadas |
|---|---|---|
| reivindicar N caminhos | cerca de 2 por reivindicação ativa no repositório, mais cerca de 1 por caminho e algumas leituras fixas | 2 por caminho |
| listar reivindicações | 1 + 2 por reivindicação ativa | 0 |
| liberar | cerca de 1 por caminho | 1 por linha liberada |
| termina uma execução que nunca reivindicou nada | 0 | 0 |
Com cerca de 50 tarefas na nuvem por dia somando todos os usuários, e duas chamadas de reivindicação em cada uma, são 100 chamadas por dia. Cada chamada são duas requisições de Worker, uma em code.foxl.ai e outra por um salto interno até o servidor, ou seja, cerca de 200 requisições, 0,2% das 100.000 do plano gratuito. Mesmo que cada uma dessas chamadas custasse o que custa a de um usuário muito ativo no pior caso (dez agentes com cinco reivindicações cada num mesmo repositório), o dia leria cerca de 11.000 linhas (0,2% dos 5 milhões) e gravaria cerca de 830 (0,8% das 100.000).
Os agentes seguem mesmo a regra?
Ainda não sabemos, e esta fase foi feita para descobrir. As nossas próprias sessões locais precisaram de uma coordenadora para fazer valer a mesma regra, então não estamos supondo que seis CLIs diferentes vão reivindicar antes de editar e recuar num conflito só porque as instruções delas mandam. Cada conflito grava um registro de auditoria (cada instância do servidor pula uma repetição para a mesma tarefa e os mesmos detentores dentro de dez minutos), e o mesmo acontece com cada mensagem que um agente manda para outro. Se um agente mandou mensagem para o detentor aparece no log de auditoria, se ele liberou a reivindicação aparece na tabela de reivindicações, e se ele editou o arquivo mesmo assim aparece no diff do pull request.
A próxima fase depende do que isso mostrar. Se os agentes respeitarem as reivindicações, o plano é que a parte do Foxl Code que divide um pedido entre vários agentes dê a cada subtarefa uma lista de arquivos e rode as subtarefas que se sobrepõem uma depois da outra, em vez de ao mesmo tempo. Até 23 de outubro de 2026, a tabela de produção está vazia, porque nenhuma tarefa na nuvem começou desde que o recurso foi implantado.
Para quem está construindo algo parecido, as três propriedades que manteríamos são: uma reivindicação é sempre registrada, uma sobreposição volta com um detentor e uma nota, e uma reivindicação dura só enquanto dura a sua execução. O nosso post do lançamento do Foxl Code trata do próprio loop do agente, que converge para um objetivo em vez de confiar em cada passo (em inglês).