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:
- Feinere Wiederaufnahme: Fortschrittszeiger und In-Worker-Checkpoints, statt am letzten gespeicherten Knoten neu anzusetzen.
- Live-Sicht während des Laufs: Streaming-Tracing statt Telemetrie erst nach dem Abschluss.
- Alarmierung: Regeln, die ein Signal an eine Reaktion koppeln.
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:
- Was hat der Worker zuletzt getan?
- Wodurch erfahre ich vom Stillstand?
- Was wird beim Neustart erneut ausgeführt?
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
- quality-gates-toolkit
- öffentlich, MIT-Lizenz, v1.7.0.
- agentic-planner-core
- öffentlich, MIT-Lizenz
- agentic-developer-core
- öffentlich, MIT-Lizenz
- issue-to-pr-skill