Mehrere Coding-Agenten, ein Repository: Claims statt Sperren
Parallele Coding-Agenten kollidieren beim Merge, weil keiner weiß, was die anderen bearbeiten. Die Cloud-Agenten von Foxl Code beanspruchen jetzt die Dateien, die sie ändern wollen, und erfahren, welche deiner anderen aktiven Agenten schon einen überlappenden Pfad beansprucht haben. Ein Claim wird in jedem Fall festgehalten und sperrt nichts; bei einem Konflikt wird der Agent angewiesen, dem Inhaber die Änderung zu schicken, zu warten oder dich zu fragen. Echte Ausgabe des ausgelieferten Befehls, die Glob-Regeln, die Datenbankkosten und die Frage, ob sich Agenten wirklich an die Regel halten. Die können wir noch nicht beantworten.

Auf dieser Seite
Lass mehrere Coding-Agenten in einem Repository arbeiten, und sie kollidieren beim Merge, weil keiner weiß, was die anderen gerade bearbeiten. Foxl Code führt Coding-Agenten wie Claude Code und Codex in der Cloud aus, und seit v0.9.3-beta.5 wird jeder Agent angewiesen, die Pfade, die er ändern will, vor dem Bearbeiten mit foxl-agents claim <paths> zu beanspruchen. Hat ein anderer Agent desselben Nutzers, der noch läuft (ein aktiver Agent), schon einen überlappenden Pfad beansprucht, nennt die Antwort diesen Agenten und die Notiz, die er hinterlassen hat. Gesperrt wird nichts. Bei einem Konflikt wird der Agent angewiesen, dem Inhaber die Änderung zu schicken, die er braucht, zu warten, bis der Inhaber den Claim freigibt, oder den Nutzer zu fragen, und seine eigenen Claims verschwinden, wenn sein Lauf endet.
Warum parallele Coding-Agenten kollidieren
Jeder Agent arbeitet in seinem eigenen Checkout auf seinem eigenen Branch, also hindert nichts zwei von ihnen daran, dieselbe Datei zu ändern. Git meldet einen Textkonflikt, wenn der zweite Pull Request gemergt wird, nachdem beide Agenten fertig sind und bezahlt wurden. Einen semantischen Konflikt, bei dem zwei Pull Requests jeweils ihre Checks bestehen, aber den Build brechen, sobald beide gemergt sind, meldet gar nichts, bis jemand beide merged. Claims fangen diese Art nur ab, wenn die beiden Agenten dieselben Pfade anfassen.
Unser eigenes Setup hat uns gezeigt, was hilft. Wir bauen Foxl mit 15 bis 20 Sitzungen von Claude Code, die parallel auf seinem Repository laufen, und in 14 Tagen haben sie einander 4.773 Nachrichten geschickt. Was sie davon abhielt, sich gegenseitig zu überschreiben, war eine gemeinsame Zuständigkeitsliste. Eine Koordinator-Sitzung hält fest, welcher Sitzung welche Dateien gehören (und bei einer Datei, die viele Sitzungen anfassen, welche Teile davon), und eine Sitzung, die eine Änderung in einer Datei braucht, die ihr nicht gehört, schickt die Änderung an die zuständige Sitzung, statt sie selbst zu machen. Vor der Liste bearbeiteten Sitzungen gleichzeitig dieselben Dateien und merkten es erst beim Merge. Die Liste hatte ihren eigenen Preis: Von den 123 Malen, die eine Sitzung in diesen 14 Tagen blockiert war, warteten 33 auf eine Antwort des Koordinators.
Den Nachrichtenteil hatte Foxl Code schon: einen Befehl foxl-agents, mit dem jeder Cloud-Agent die anderen aktiven Agenten des Nutzers auflisten und ihnen eine Nachricht schicken kann. Claims fügen die Zuständigkeitsliste ohne Koordinator hinzu: Jeder Agent sagt, was er anfassen wird, und die Datenbank antwortet in einer einzigen Anfrage.
Warum Claims statt Sperren?
Eine Sperre funktioniert nur, wenn jeder Agent sie beachtet und jeder Agent sie wieder freigibt. Ein Coding-Agent, der abstürzt, sein Budget aufbraucht oder abgebrochen wird, gibt nichts frei, also braucht eine Sperre ein Timeout, und ein Timeout, das für einen echten Lauf lang genug ist (unsere können über sieben Stunden dauern), blockiert alle so lange. Zwei Agenten, die jeweils eine Zeile in der Datei des anderen brauchen, würden ewig aufeinander warten. Und einem Agenten, dessen Sperre abgelehnt wird, bleibt nichts anderes übrig, als aufzuhören.
Claims umgehen jedes dieser Probleme. Ein abgestürzter Inhaber zählt nicht mehr, sobald sein Lauf nicht mehr aktiv ist, es gibt also kein Timeout abzusitzen. Ein Claim, der sich mit einem anderen überschneidet, wird nicht abgelehnt; er wird festgehalten, und die Überschneidung geht an den Agenten zurück, der gefragt hat, mit Task-ID, Titel, beanspruchten Pfaden und Notiz des Inhabers. Und ein Agent, der auf einen Konflikt stößt, wird zuerst angewiesen, dem Inhaber die nötige Änderung zu schicken, sodass zwei Agenten, die gegenseitig ihre Dateien brauchen, etwas anderes zu tun haben als zu warten, auch wenn nichts sie davon abhält, trotzdem beide zu warten. Das sind die Anweisungen, die jeder Agent bekommt, Wort für Wort:
- 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.Diese Zeilen werden an die Anweisungsdatei auf Nutzerebene angehängt, die jede CLI liest: CLAUDE.md für Claude Code und AGENTS.md für Codex, Kiro, Cursor, Hermes und OpenCode. Derselbe Mechanismus muss sechs verschiedene CLIs erreichen, und deshalb ist foxl-agents ein Shell-Befehl und kein MCP-Server. Jede Coding-CLI kann einen Shell-Befehl ausführen, also funktioniert ein Skript in allen sechs, ohne dass man jede einzeln so konfigurieren muss, dass sie einen Server lädt.
Was ein Agent ausführt und was er sieht
Unten steht die echte Ausgabe des ausgelieferten Befehls, ausgeführt von einem Skript statt von einem Agenten, für zwei Demo-Tasks in einem Demo-Repository. Task A ist ein Codex-Task, der Rate Limiting hinzufügt, und Task B ist ein Claude-Code-Task, der dem Users-Endpoint Paginierung hinzufügt. Server, Datenbank und Befehl sind dieselben, die auch die Cloud-Agenten bekommen.

Task B hat src/api/ mit einem abschließenden Schrägstrich beansprucht, der ein Verzeichnis kennzeichnet, und der Server hat das als src/api/** gespeichert. Ohne den Schrägstrich hätte der Befehl /** selbst angehängt: Er läuft im Checkout, dem einzigen Ort, der eine Datei von einem Verzeichnis unterscheiden kann. Der Befehl schreibt außerdem jeden Pfad relativ zur Wurzel des Repositorys um, egal in welchem Arbeitsverzeichnis, und lehnt einen Pfad außerhalb des Checkouts ab, bevor er irgendetwas sendet.
Der Konflikt nennt die beiden Dateien von Task A und ihre Notiz, und Task B entscheidet anhand der Notiz. Hier könnte sie die Middleware in Ruhe lassen und Task A die eine Änderung schicken, die sie dort braucht, oder mit dem eigenen Handler des Endpoints anfangen und wieder nachsehen, sobald Task A freigegeben hat.
Wie erkennt man, ob sich zwei Globs überschneiden?
Zwei Claims überschneiden sich, wenn mindestens ein Pfad auf beide passt. Die Grammatik ist absichtlich klein gehalten:
- ein einfacher Pfad ist ein Glob ohne Platzhalter;
*passt innerhalb eines Pfadsegments;?passt auf genau ein Zeichen;- ein Segment, das genau
**ist, passt auf beliebig viele ganze Segmente; - jedes andere Zeichen ist wörtlich gemeint,
[id].tsxist also ein Dateiname und keine Zeichenklasse.
Das liefert der ausgelieferte Matcher für ein paar Paare:
| Claim A | Claim B | Überschneidung |
|---|---|---|
src/api/** | src/api/users.ts | ja |
src/*.ts | src/api/users.ts | nein, * bleibt in einem Segment |
src/**/*.test.ts | src/api/*.ts | ja, beide passen auf src/api/x.test.ts |
docs/** | src/** | nein |
src/app/[id]/page.tsx | src/app/a/page.tsx | nein, [id] ist wörtlich |
**/*.md | docs/guide.md | ja |
Eine Überschneidung führt nie dazu, dass ein Claim abgelehnt wird, ein paar Formen aber schon:
- ein Glob nur aus Platzhaltern, der
**enthält, etwa**oder**/*, weil er das ganze Repository beanspruchen würde; - ein Pfadsegment
.., ein Backslash oder ein Steuerzeichen; - ein führendes
!, weil es hier keine Negation ist und auch nicht wie eine aussehen soll.
Ein Task hält höchstens 50 Claims mit je höchstens 200 Zeichen, dazu Notizen mit bis zu 280 Zeichen.
Der Vergleich ist gedeckelt, damit eine ungewöhnliche Menge von Claims den Server nicht viel CPU kosten kann. Zwei Claims, deren führende wörtliche Segmente sich unterscheiden (docs/** und src/**), werden sofort ausgeschlossen. Andernfalls geht der Server memoisiert über Paare von Segmenten und, innerhalb eines Segments, über Paare von Zeichen. Alle Vergleiche in einer Anfrage teilen sich ein Budget von 50.000 Schritten, und ein Paar, das das Budget nie erreicht, wird als mögliche Überschneidung gemeldet, statt unter den Tisch zu fallen. So erfährt der Agent auch bei einer Anfrage, der das Budget ausgeht, davon.
Wann ein Claim endet
Ein Claim gehört zu einem Lauf. Wenn der Lauf endet, verwirft der Task jeden Claim, den er angelegt hat, im selben Schritt, der den Lauf als beendet festhält. Zwei Prüfungen beim Lesen decken einen Lauf ab, der diesen Schritt nie erreicht hat: Ein Claim, dessen Inhaber nicht mehr aktiv ist, wird ignoriert, ebenso ein Claim, der älter ist als die längste mögliche Dauer eines Laufs, 7 Stunden 20 Minuten. Claims haben dieselbe Sichtbarkeit wie Nachrichten: Nur die aktiven Agenten eines einzigen Nutzers, die über dieselbe Installation der GitHub App auf demselben Repository arbeiten, sehen gegenseitig ihre Claims.

Was es auf einem kostenlosen Datenbanktarif kostet
Claims liegen in einer Tabelle mit einem Index auf (Nutzer, Repository, Erstellungszeit), und nichts fragt sie per Polling ab. Jeder Lesezugriff fragt nur nach Claims, die in den letzten 7 Stunden 20 Minuten angelegt wurden, und die Erstellungszeit ist die letzte Spalte des Index, also fallen ältere Zeilen aus dem Bereich, den die Datenbank durchsucht. Veraltete Claims, die niemand freigegeben hat, fügen einem Lesezugriff eine Zeile hinzu, egal ob es 1.000 oder 2.000 davon gibt; wir haben beides gemessen.
Wir messen das, weil Foxl auf dem kostenlosen Tarif von Cloudflare läuft, bei dem sich alle D1-Datenbanken eines Kontos 5 Millionen gelesene und 100.000 geschriebene Zeilen pro Tag teilen und das Konto 100.000 Worker-Anfragen pro Tag bekommt. Die Zahlen unten stammen von D1, lokal in workerd ausgeführt, der Runtime von Cloudflare, und sind noch nicht mit den eigenen Metriken des D1 in Produktion abgeglichen:
| Aufruf | gelesene Zeilen | geschriebene Zeilen |
|---|---|---|
| N Pfade beanspruchen | etwa 2 pro aktivem Claim auf dem Repository, plus etwa 1 pro Pfad und ein paar feste Lesezugriffe | 2 pro Pfad |
| Claims auflisten | 1 + 2 pro aktivem Claim | 0 |
| freigeben | etwa 1 pro Pfad | 1 pro freigegebener Zeile |
| ein Lauf endet, der nie etwas beansprucht hat | 0 | 0 |
Bei etwa 50 Cloud-Tasks am Tag über alle Nutzer und je zwei Claim-Aufrufen sind das 100 Aufrufe am Tag. Jeder Aufruf sind zwei Worker-Anfragen, eine bei code.foxl.ai und eine über einen internen Hop zum Server, also etwa 200 Anfragen oder 0,2 % der 100.000 des kostenlosen Tarifs. Selbst wenn jeder dieser Aufrufe so viel kosten würde wie der eines vielbeschäftigten Nutzers im schlimmsten Fall (zehn Agenten mit je fünf Claims auf einem Repository), würde der Tag etwa 11.000 Zeilen lesen (0,2 % der 5 Millionen) und etwa 830 schreiben (0,8 % der 100.000).
Halten sich die Agenten wirklich an die Regel?
Das wissen wir noch nicht, und diese Phase ist dafür gebaut, es herauszufinden. Unsere eigenen lokalen Sitzungen brauchten einen Koordinator, um dieselbe Regel durchzusetzen, also gehen wir nicht davon aus, dass sechs verschiedene CLIs vor dem Bearbeiten beanspruchen und bei einem Konflikt zurückstecken, nur weil es in ihren Anweisungen steht. Jeder Konflikt schreibt einen Audit-Eintrag (jede Serverinstanz überspringt eine Wiederholung für denselben Task und dieselben Inhaber innerhalb von zehn Minuten), und das tut auch jede Nachricht, die ein Agent einem anderen schickt. Ob ein Agent dem Inhaber geschrieben hat, zeigt das Audit-Log, ob er seinen Claim freigegeben hat, zeigt die Claims-Tabelle, und ob er die Datei trotzdem bearbeitet hat, zeigt der Diff des Pull Requests.
Die nächste Phase hängt davon ab, was das zeigt. Wenn Agenten Claims respektieren, soll der Teil von Foxl Code, der eine Anfrage auf mehrere Agenten aufteilt, jedem Teil-Task eine Dateiliste geben und Teil-Tasks, die sich überschneiden, nacheinander statt gleichzeitig ausführen. Stand 23. Oktober 2026 ist die Produktionstabelle leer, weil seit dem Deployment der Funktion kein Cloud-Task gestartet wurde.
Wer etwas Ähnliches baut: Die drei Eigenschaften, die wir behalten würden, sind, dass ein Claim immer festgehalten wird, dass eine Überschneidung mit Inhaber und Notiz zurückkommt und dass ein Claim nur so lange gilt wie sein Lauf. Unser Beitrag zum Start von Foxl Code beschreibt die Agentenschleife selbst, die auf ein Ziel hin abgleicht, statt jedem Schritt zu vertrauen (auf Englisch).
Quellen und weiterführende Lektüre
- Cloudflare D1: Preise und Limits des Free-TarifsDokumentation
- Release Foxl v0.9.3-beta.5Release