Was passierte, als ich mehrere KI-Agenten auf ein Repo losließ
Ich dachte, parallele Agenten-Sessions würden die Produktivität vervielfachen. Tatsächlich gab es innerhalb einer Woche eine Serie echter Unfälle: gekaperte Branches, Commits auf fremden Branches, verwaiste Commits, die erst der reflog rettete, bis hin zur doppelten Implementierung desselben Features. Eine Typologie der Unfälle, die parallele Agenten mit einem geteilten Checkout produzieren, und die Regeln, die daraus entstanden sind.
Das Wichtigste
Der wahre Feind paralleler Agenten-Sessions ist nicht die Fähigkeit des Modells, sondern das geteilte Arbeitsverzeichnis. HEAD ist eine globale Variable für alle: Ein Branch-Wechsel in einer Session setzt die Commits einer anderen an die falsche Stelle. Vier Unfalltypen aus einer Woche samt Bergung, und drei Verteidigungslinien: Worktree-Isolation, Branch-Check unmittelbar vor dem Commit und ein enger gefasster Scope für das Verifizierungs-Gate.
Auf dieser Seite
Wer mit KI-Agenten arbeitet, wird ganz von selbst gierig. Während eine Session einen Bug fixt, könnte doch eine andere refactoren und eine dritte ein Issue untersuchen. Modelle lassen sich schließlich mehrfach starten, also müsste die Produktivität entsprechend mitwachsen.
So habe ich auch angefangen. Und nach einer Woche wusste ich: Der wahre Feind paralleler Agenten ist nicht die Fähigkeit des Modells, sondern das Arbeitsverzeichnis, das sie sich teilen.
HEAD ist eine globale Variable
Die Ursache passt in einen Satz: Teilen sich mehrere Sessions einen git-Checkout, wird der Zustand "aktueller Branch" zur globalen Variable für alle.
Stellt man sich zwei Menschen vor, die gleichzeitig am selben Rechner arbeiten, sieht man sofort, dass etwas nicht stimmt. Beim Starten der Agenten kam mir dieser Gedanke nicht. In jedem Terminal-Tab läuft eine eigene Session, das sieht nach Isolation aus. Aber es gibt nur ein Dateisystem, und es gibt nur einen HEAD. In dem Moment, in dem eine Session git checkout ausführt, verschiebt sich der Boden unter allen anderen Sessions.
Die Unfälle jener Woche hatten klar erkennbare Typen.
Branch-Kaperung. Während Session A auf einem Topic-Branch arbeitete, wechselte Session B für ihre eigene Arbeit den Branch. A committete, ohne davon zu wissen, und der Commit landete auf Bs Branch. Die Gegenrichtung gab es auch: In dem Moment, in dem A committen wollte, stand der Branch auf develop, und nur weil der Hook, der direkte Commits auf geschützte Branches blockiert, eingriff, blieb der Unfall aus. Ohne den Hook wäre es glatt durchgegangen.
Verwaiste Commits. Den Topic-Branch von Session A löschte Session B mitten in einer Aufräumaktion. As Commits gehörten plötzlich zu keinem Branch mehr; ich durchsuchte den reflog, fand die Commit-Hashes und holte sie per cherry-pick zurück. Glück gehabt, denn wäre der reflog abgelaufen oder hätte ich sie nicht gefunden, wäre die Arbeit komplett verdampft.
Staging-Verschmutzung. In dem Moment, in dem Session A einen Commit baute, lag eine von Session B gestagte Dateilöschung mit in der Staging-Area. Wäre einfach committet worden, hätte sich Bs Löschung in As Commit gemischt. Beim Überfliegen des diffs fiel eine fremde Änderung auf und wurde aussortiert; hätte der Agent den diff unmittelbar vor dem Commit nicht angesehen, wäre es niemandem aufgefallen.
Doppelte Implementierung. Der frustrierendste Typ. Zwei Sessions implementierten dasselbe Feature, jede für sich, ohne von der Existenz der anderen zu wissen. Beide machten ihre Sache gewissenhaft und gut, und eine Version musste komplett weggeworfen werden. Die durch Parallelisierung gesparte Zeit wurde eins zu eins zurückgezahlt.
Sogar bei den Verifizierungs-Gates treten sie sich gegenseitig auf die Füße
Nicht nur Dateien und Branches waren das Problem. Unsere Harness hat ein Gate, das vor dem Ende einer Session die statische Analyse und die Tests des gesamten Repos laufen lässt, damit keine Arbeit in kaputtem Zustand abgeschlossen werden kann. In einer Solo-Session ein großartiger Mechanismus.
In parallelen Sessions wurde daraus eine Falle, in der sich alle gegenseitig ausbremsen. Session A hatte nur Doku angefasst, aber As Gate scheiterte am Kompilierfehler in einer Datei, an der Session B gerade mitten in der Arbeit war. A verbrannte Turns damit zu beweisen "das ist nicht meine Änderung", und an schlimmen Tagen wartete A 30 Minuten, bis B aufgeräumt hatte. Der schlimmste Vorfall: Eine Session änderte kurzerhand Code, an dem eine andere Session gerade arbeitete, nur um das eigene Gate zu bestehen. Das Gate wurde ausgerechnet zum Anreiz, fremde Arbeit anzufassen.
Die Prüfung selbst war nicht falsch. Falsch wurde ihr Scope "das ganze Repo" in dem Moment, in dem das Repo niemandem mehr allein gehörte.
Drei Verteidigungslinien
Nachdem ich die Unfälle Typ für Typ durchlebt hatte, habe ich drei Verteidigungslinien gezogen.
- Worktree-Isolation als Default. Gibt man mit
git worktreejeder Session ein eigenes Arbeitsverzeichnis, ist HEAD keine geteilte Variable mehr. Branch-Kaperung, verwaiste Commits und Staging-Verschmutzung verschwinden an der Wurzel. Drei der vier Unfalltypen oben erledigt dieser eine Zug. Umsonst ist er allerdings nicht. In einem Monorepo müssen pro Worktree die Abhängigkeiten installiert und die Codegenerierung erneut ausgeführt werden, und in Repos mit Tools, die sich mit worktrees schlecht vertragen, etwa git-crypt, lässt er sich schwer erzwingen. Auch bei uns behält ein Repo wegen genau dieser Einschränkung den geteilten Checkout, und deshalb braucht es die beiden anderen Verteidigungslinien. - Unmittelbar vor dem Commit den Branch erneut prüfen. Wenn eine Session mit der Arbeit beginnt, hält sie fest "ich arbeite auf diesem Branch" und gleicht das unmittelbar vor dem Commit mit dem aktuellen HEAD ab. Weicht es ab, wird der Commit gestoppt und erst die Lage geklärt. Die Regel ist fast schon banal simpel, aber jeder Branch-Kaperungs-Unfall entstand aus "der HEAD im Moment des Commits war nicht der HEAD, den ich kannte". Ein Mensch würde es am Branch-Namen im Prompt bemerken; ein Agent prüft nicht, wenn man es ihm nicht explizit aufträgt.
- Das Verifizierungs-Gate auf die eigenen Änderungen scopen. Der Prüfbereich des Session-Endgates wird vom gesamten Repo auf die Dateien verengt, die die Session tatsächlich geändert hat. Damit verschwindet beides: dass fremdes WIP mein Session-Ende blockiert, und der Anreiz, fremden Code anzufassen, um das Gate zu bestehen. Die Gesundheit des gesamten Repos prüft ohnehin die CI erneut, auf Basis des committeten Standes. Das Session-Gate musste nie das Ganze sehen.
Dazu kam eine Betriebsregel. Bevor ich eine Session starte, überfliege ich die gerade offenen Branches und PRs und schaue, ob sich irgendwo der Scope überschneidet. Die doppelte Implementierung ist kein Problem, das git verhindern kann, sondern ein Problem der Aufgabenverteilung; dagegen hilft kein Werkzeug, nur eine Gewohnheit.
Nebenläufigkeitsprobleme gab es also nicht nur in Datenbanken
Im Rückblick ist das Bild vertraut: eine geteilte Ressource, gleichzeitiger Zugriff ohne Lock, Race Conditions und Isolationsstufen. Genau das Problem, das wir über Jahrzehnte an Datenbanken und Multithreading-Code gelernt haben, hat sich auf dem Arbeitsverzeichnis wiederholt.
Mit einem einzelnen Agenten existiert dieses Problem nicht. Auch wenn Mensch und Agent sich abwechseln, übernimmt der Mensch implizit die Rolle des Koordinators. Das Problem beginnt in dem Moment, in dem es mehrere Agenten werden und der Mensch die Koordination aus der Hand gibt. Ab diesem Zeitpunkt ist das Arbeitsverzeichnis eine geteilte Ressource, die Nebenläufigkeitskontrolle braucht, und wer sie ohne Isolation betreibt, verliert unweigerlich Daten, so wie es Datenbanken taten.
Das ist kein Plädoyer, parallele Agenten aufzugeben. Ich starte auch heute jeden Tag mehrere Sessions. Geändert hat sich nur eines: Ich sperre sie nicht mehr in denselben Raum.
Ähnliche Beiträge
- 💻 Entwicklung
Die Pipeline, in der eine zweite KI den Fix der ersten widerlegen muss
Lässt man dasselbe Modell den eigenen Code reviewen, segnet es die eigene Schlussfolgerung noch einmal ab. Die Geschichte eines Solo-Entwicklers, der fast seinen gesamten Code mit KI schreibt und die Cross-Check-Schleife, in der eine KI die Arbeit der anderen widerlegen soll, zu einer festen Pipeline gemacht hat. Mit dem realen Fall, in dem drei Runden in Folge Bugs auftauchten, die das Self-Review übersehen hatte, und dem Prompt-Design dahinter.
- 💻 Entwicklung
Wenn die KI "fertig" sagt: der Mechanismus, der es ihr nicht glaubt
KI-Coding-Agenten melden Arbeit als erledigt, die sie nicht erledigt haben. Sie behaupten, Tests seien durchgelaufen, ohne sie gestartet zu haben, und melden einen Fix, ohne den Bug reproduziert zu haben. Die Geschichte eines Solo-Entwicklers, der fast seinen gesamten Code mit KI schreibt und ein Verifizierungs-Gate gebaut hat, das "fertig" nur noch als Beweis gelten lässt, nicht als Behauptung.
- 💻 Entwicklung
Dem "fertig" meines Agenten glaube ich nicht mehr - prove-it, das verify.sh-Gate
KI-Coding-Agenten melden "Tests bestanden", ohne die Tests je gestartet zu haben. Das ist keine Lüge, sondern ein Strukturproblem. Was ich beim Bau von prove-it gelernt habe, einem Open-Source-Gate, das Berichte in Prüfungen verwandelt - und die vier Arten, auf die ein selbst geschriebener Fünf-Zeilen-Hook leise scheitert.