Warum ich meine Agentenplattform zurückgebaut habe

Sieben Wochen Eigenbetrieb einer Agentenplattform, rund 340 $ an Modellkosten und ein Vorfall, den die Telemetrie erst hinterher erklärte: Warum der zusätzliche autonome Umfang den Eigenbetrieb nicht gerechtfertigt hat und was seither läuft.

Am 27. August 2026 lief meine Agentenplattform so gut wie nie: drei Issues in rund 87 Minuten. Implementierung, Reviews durch LLM-Judges, Korrekturen und Merge, ohne mein Eingreifen. Dann begann das vierte Issue. Es lief länger als erwartet, OpenRouter antwortete mit HTTP 429, und ich stand vor einer Frage, die die Plattform nicht beantworten konnte: weiter warten, Routing ändern, Lauf abbrechen?

Mit KI-assistierter Programmierung arbeite ich privat seit rund zwei Jahren. An der Agentenplattform arbeitete ich sieben aktive Wochen. Die OpenRouter-Rechnung dafür belief sich auf rund 340 $. Meine eigene Zeit und die Infrastruktur waren darin noch nicht enthalten.

Die Rechnung endete nicht bei Tokens und VPS-Miete. Dazu kam die Software, die währenddessen liegen blieb.

Sieben Wochen reichten, um viel Automatisierung zu bauen, aber nicht, um eine Plattform zu bauen, deren Eigenbetrieb sich für meinen Zweck lohnte.

Der Augenblick, in dem die Telemetrie half: zu spät

Ich hatte das Routing auf die drei Anbieter mit der niedrigsten gemessenen Latenz beschränkt. Das hatte die vorherigen Läufe beschleunigt. Über das eingeschränkte Routing kamen wiederholt 429-Antworten (Routing-Einstellungen, HTTP 429 als Rate-Limit). Mit der Beschränkung hatte ich zugleich die Ausweichmöglichkeiten reduziert.

Die 429-Antworten waren sichtbar. Was sie für den aktiven Worker bedeuteten, konnte ich kaum beurteilen.

Dabei besaß die Plattform mehrere Telemetriepfade: eine state.json an Knotengrenzen, laufende Docker-Logs, die ReAct-Trajektorie eines Workers als JSONL-Datei, aber erst nach seinem Abschluss. OpenTelemetry-Spans, aber erst am Laufende. Metriken, aber erst nach erfolgreichem Zyklus. ADR-0016 benennt die Einschränkung ausdrücklich als „Deferred Visibility“: Der umgebaute Export lieferte keine Live-Sicht während des Laufs. Der separate Metrikpfad (ADR-0029) schloss diese Lücke nicht.

Die versteckte Voraussetzung dieser Beobachtbarkeit war ein ausreichend geordneter Abschluss. Ausgerechnet im Fehlerfall war darauf kein Verlass. Um die Zustandsübergänge und Speicherpunkte zu verstehen, ließ ich ein LLM den Code auswerten. Die Analyse war gut. Sie kam nur zu spät für die operative Frage. Beobachtbarkeit, deren Funktionsweise ich erst durch eine Codeanalyse rekonstruieren muss, ist für den Betrieb kaum Beobachtbarkeit.

Und es fehlte nicht nur die Sicht. Es fehlte die Verbindung zwischen den vorhandenen Signalen und einer Handlung:

Signal → erkennbares Ereignis → Schweregrad → Benachrichtigung → Reaktion

Keine Benachrichtigung meldete einen ungewöhnlich lange aktiven Worker. Keine Regel unterschied eine kurze 429-Serie von einem anhaltenden Anbieterproblem. Selbst das Ende des gesamten Laufs durch das Zeitlimit löste keinen Alarm aus. Die Kosten abgeschlossener Reviews waren nachvollziehbar. Judge-Abrechnung und KPI-Reporting pro Review sind dokumentiert (ADR-0058). Der Fortschritt des aktiven Workers war es nicht.

Erst später war vollständig klar, was den Lauf beendet hatte. Ich hatte die Anbieterbeschränkung zwischenzeitlich zurückgenommen und OpenRouter das Routing überlassen. Dann griff das maximale Zeitbudget des Execute-Knotens. Ich hatte es zuvor von 30 auf 45 Minuten erhöht, auch das reichte nicht. Bevor das Issue fertig war, war das Zeitlimit überschritten und der Execute-Knoten wurde neu gestartet. Ob der Worker Fortschritte erzielte oder sich in einer Schleife verfangen hatte, war nicht transparent einzusehen.

Was die Plattform sonst noch tragen musste

Die Beobachtbarkeitslücke war keine Ausnahme. Sie war eine Zeile auf einem längeren Zettel. Die konkreten Lösungen hängen vom Framework ab. LangChain, LangGraph, eigener Code. Die Betriebsfragen verschwinden dadurch nicht. Für viele hatte ich bereits Antworten gebaut. Beim vierten Issue zeigte sich, welche fehlten.

flowchart TB
    subgraph plan["Planung, Teil der Plattform"]
        human["Autor: Interviews und Grillsession mit dem Planner"]
        planner["agentic-planner-core: erstellt mehrere Dutzend Issues"]
        human <--> planner
    end
    subgraph start["Start, manuell pro Lauf"]
        lauf["Autor startet einen Lauf mit mehreren Issues"]
    end
    subgraph own["Eigenbetrieb auf eigenem VPS, autonom bis zum Merge"]
        orch["agentic-developer-core: Orchestrator, Wiederaufnahme, Retries, Zeitbudget"]
        worker["Worker auf demselben VPS"]
        ext["Externe Dienste: OpenRouter mit Anbieterbegrenzung und GitHub"]
        gates["Verifikation: deterministische Gates und vier LLM Judges"]
        merge["Merge durch die Plattform"]
        orch <--> worker
        orch <--> ext
        worker -->|"Pull Request"| gates
        gates -->|"Befunde, Korrekturschleifen im Laufen"| worker
        gates -->|"grün"| merge
    end
    planner --> lauf
    lauf --> orch

Wiederaufnahme. Nach einem Neustart setzte die Plattform am letzten gespeicherten Knoten an, statt das Issue von vorn zu beginnen. Das war nützlich und leider gröber, als ich zunächst angenommen hatte. Der interne Plan eines Workers bestand nicht aus einzeln gespeicherten Ausführungsschritten. Wenn er zehn Schritte plante und beim vierten unterbrochen wurde, startete der Execute-Knoten erneut. Das Arbeitsverzeichnis enthielt noch die Änderungen aus den ersten Schritten, und das Modell konnte sie prüfen und darauf aufbauen. Ob der neue Versuch sinnvoll anschloss, hing davon ab, wie das Modell die vorhandenen Änderungen interpretierte. Die Plattform setzte den Zustand des Repositories fort, nicht den Gedankengang des Agenten.

Fehlerbehandlung. Geschichtete Retries mit Modell-Fallback, Schleifenerkennung (3 identische Tool-Calls: Umleiten, 5: Abbrechen) und ein Rekursionslimit als Abbruchsicherung boten sinnvolle Kontrollen. Sie lösten jedoch die falschen Probleme: Ein Worker kann lange blockieren, ohne Aufrufe zu wiederholen, und gehäufte Anbieterfehler lösen nicht zwangsläufig das Rekursionslimit aus.

Sicherheit und Isolation. Environment-Allowlist statt Denylist, SSRF-Validierung für URL-Fetches, Prozess-Supervision und eine eigene Ausführungs-Sandbox auf getrenntem VPS. Ein Server, den ich betreiben, patchen und bezahlen musste.

Verifikation. Deterministische Gates, vier LLM-Judges über den PR-Diff, Feedback-Loop bis zum grünen Merge. Die Details dazu stehen mit dem Rest des Zettels im Anhang.

Der Zettel hat drei Punkte ohne ADR-Nummer. Sie existieren nur als Pläne:

Jede dieser Lösungen hätte die nächste vernünftige Plattformfunktion begründet. Alle drei blieben geplant und wurden nie gebaut.

Weniger Plattform, nicht billigere Plattform

Alle drei Punkte zusammen hätten dauerhafte Plattformarbeit bedeutet: Ereignisse definieren, Grenzwerte kalibrieren, Benachrichtigungen betreiben, Abbrüche testen, Wiederanläufe absichern. Währenddessen wartete die Software, die ich mit der Plattform eigentlich bauen wollte.

Der Vorfall zeigte mir, was der nächste Ausbau kosten würde: weitere Plattformarbeit. Nicht mehr der sinnvollste Einsatz meiner Zeit.

Die Entscheidung, die ich danach getroffen habe, beschreibe ich am genauesten so: Ich habe nicht dieselbe Plattform billiger nachgebaut. Früher: Implementierung, Reviews, Korrekturen und Merge ohne mein Eingreifen. Heute: Session starten, das Label agent-ready setzen und selbst mergen. Ich habe den autonomen Umfang verkleinert und die Merge-Entscheidung wieder selbst übernommen. Der Unterschied liegt nicht in der Kostenzeile, sondern in der Art der Verantwortung.

Was geblieben ist, ist auch kein betriebsfreier Zustand. Ein CLI-Harness orchestriert ebenfalls. GitHub Actions und der Modellanbieter übernehmen weiterhin Betrieb. Ich lasse pro Session nur ein Issue laufen und prüfe nach rund 60 Minuten, ob es fertig ist oder Steuerung braucht. In etwa zwei von zehn Issues muss ich die Session abbrechen und starte sie mit dem letzten Arbeitsschritt neu. Nicht perfekt, aber der Aufwand bleibt klein. Der ehrliche Vergleich lautet nicht „Betrieb gegen kein Betrieb“, sondern „weniger Eigenbetrieb gegen mehr Eigenbetrieb“.

Was heute läuft

flowchart TB
    subgraph plan["Planung, einmalig pro Themenschwerpunkt"]
        human["Autor: Interviews und Grillsession mit dem Planner"]
        planner["agentic-planner-core: erstellt mehrere Dutzend Issues"]
        human <--> planner
    end
    subgraph pick["Auswahl, manuell pro Issue"]
        release["Autor setzt das Label agent-ready"]
    end
    subgraph run["Ausführung und Verifikation, autonom bis zum grünen Pull Request"]
        dcode["dcode CLI Harness auf Wegwerf VPS, eine Session pro Issue, Policyschicht"]
        ext["Externe Dienste: OpenRouter mit GLM 5.3 Flash, Tavily und GitHub"]
        gates["quality-gates-toolkit: deterministische Gates und LLM Judges"]
        merge["Autor: Merge"]
        dcode <--> ext
        dcode -->|"Pull Request"| gates
        gates -->|"Befunde, im Schnitt zwei Korrekturschleifen"| dcode
        gates -->|"grün"| merge
    end
    planner --> release
    release --> dcode

Ein CLI-Harness (dcode) nutzt GLM 5.3 Flash über OpenRouter, gestartet pro Session mit --yolo, einmalige Bestätigung, danach führt der Agent freigegebene Aktionen ohne Rückfragen aus. Die Policy-Schicht beschränkt die vorgesehenen Netzwerk- und Dateisystemwerkzeuge: Recherche über Tavily, Datenabrufe über Read-only-Fetches, der Dateisystem-Scope eines Laufs ist das Projekt-Checkout. Eine harte Isolation von Shell-Kommandos und Unterprozessen bietet sie nicht. Mit dem Betrieb auf einer dedizierten VPS als Wegwerf-Infrastruktur habe ich nicht nur den Eigenbetrieb reduziert, sondern verzichte auch bewusst auf eine separate Ausführungs-Sandbox. Die Issues bereitet agentic-planner-core vor. Die Verifikation übernimmt mein quality-gates-toolkit, als einzeln einsetzbarer Verifikationspunkt, ohne den Eigenbetrieb der anderen Komponenten.

flowchart LR
    pr["Pull Request"] --> gates["Deterministische Gates: Ruff, mypy, pytest, Coverage, Semgrep, pip-audit"]
    gates -->|"alles grün"| judges["Vier LLM Judges über den PR Diff"]
    judges -->|"PASS"| merge["Merge freigegeben"]
    judges -->|"FAIL"| fix["Korrektur und neuer Push"]
    fix --> pr

Der Prompt für einen kompletten Durchlauf ist ein Satz: „Arbeite das nächste agent-ready Issue ab.“ Die vollständige Skill-Definition, SKILL.md, beide Phasen-Referenzen und das Auswahlskript habe ich als Gist veröffentlicht.

Ein vollständiger Durchlauf dieser Kette ist öffentlich dokumentiert: In agentic-planner-core lief Issue #81 über PR #82 bis zum Merge. Vier Judge-Reviews, alle PASS, Token- und Kostenangaben pro Judge in der Review sichtbar.

Was der Betrieb heute kostet, ist eine Größenordnung, kein Einsparungsnachweis: Über die drei Wochen bis zum 13. September belief sich die OpenRouter-Rechnung auf rund 30 $ (ausschließlich für die Nutzung des neuen Setups). Ein Durchlauf vom Issue bis zum merge-bereiten PR kostet typischerweise zwischen 0,25 und 0,75 $. Bezugsgröße ist der merge-bereite PR: Alles, was ein Issue auf dem Weg dorthin verbraucht, etwa Judges, im Schnitt zwei Korrekturschleifen sowie gescheiterte Durchläufe, rechne ich dem PR zu.

Der Test

Vor dem nächsten längeren unbeaufsichtigten Lauf würde ich zwei Fehlerfälle testen: einen festhängenden Worker und einen abgebrochenen Lauf. Der Test muss ohne nachträgliche Codeanalyse drei Fragen beantworten:

Solange diese Antworten fehlen, erhöhe ich die autonome Laufzeit nicht. Der nächste sinnvolle Nachweis ist kein viertes erfolgreiches Issue, sondern eine Unterbrechung, die ich rechtzeitig erkenne und kontrolliert beheben kann.

Anhang: Der volle Betriebszettel

Die im Haupttext beschriebenen Betriebsprobleme, meine Entscheidungen dazu und die öffentlichen Quellen im Überblick:

Betriebsproblem Entscheidung (ADR) Quelle
Wiederaufnahme nach Unterbrechung Resume am letzten gespeicherten Knoten; Modell prüft vorhandene Änderungen 0013
Fehlerklassen Geschichtete Retries mit Modell-Fallback; Schleifenerkennung (drei identische Aufrufe: lenken, fünf: beenden); separates Rekursionsbudget 0021, 0046, 0045
Subprozess- und Netzwerkisolation Environment-Allowlist, SSRF-Validierung, Prozess-Supervision, Sandbox auf getrenntem VPS 0043, 0028, 0015, 0056
PR-Verifikation Deterministische Gates, vier LLM-Judges, versteckter Verdict-Block, Feedback-Loop bis zum grünen Merge 0020, 0014, 0019, 0036
Workspace-Zustand bei Abbruch Snapshot + Rekursionsbudget 0053
Schreibpfade absichern Read-before-edit, Line-Range, Pfadvalidierung 0006, 0033, 0012, 0035
Leere/abgeschnittene Antworten Max-Tokens-Strenge, Finish-Reason-Logging, Budget-Caps 0040, 0049, 0051
Robuste Timeouts Harte Durchsetzung gegen SDK-Retries planner 0021
Hartes Rollback beim letzten Versuch Retry-Hybrid mit Rollback 0034
Websuche als Werkzeugpfad Qualitätskontrollen, Framework-first 0026, 0041, 0042
Prompt-Injection-Abwehr Zero-Trust-Validierung (Planungsschiene) planner 0020
Diff-Coverage Eigenes deterministisches Gate 0052
Judge-Kosten sichtbar machen Usage-Accounting, KPI-Reporting 0058

Referenzierte Repositories