# Intent: Automatische Eskalationsleiter für am Iterationslimit gescheiterte Builder-Läufe ## Ziel Heute erhält jede Builder-Karte bei `limit_reached` genau einen automatischen Wiederholungsversuch mit exakt denselben Parametern (Modell, Reasoning-Effort, Iterationslimit) wie der gescheiterte Lauf -- scheitert auch dieser, bleibt die Karte stehen und braucht sofort eine manuelle Entscheidung (`repair` oder, außer bei Operator-Kindern, `Aufteilen angefordert`). Live beobachtet (10.09.2026, `board-usage-endpoint-claude`): ein Lauf, der seine gesamte Iterationslimit-Budget mit Planung statt Codeänderungen verbraucht, scheitert bei identischer Wiederholung mit hoher Wahrscheinlichkeit erneut auf dieselbe Weise, weil sich an den Bedingungen nichts geändert hat. Dieser Intent ersetzt den einen flachen Wiederholungsversuch durch eine kurze, vom Operator vorab festgelegte Eskalationsleiter: bis zu zwei weitere automatische Versuche mit höherem Iterationslimit und/oder einem anderen Modell, bevor die Karte wie heute in eine manuelle Klärung fällt. Die Eskalation ist vollständig deterministisch und wrapper-gesteuert -- kein Modell entscheidet zur Laufzeit selbst über eine größere Ausstattung, das Muster folgt damit demselben Vertrauensgrenzen-Prinzip wie der Rest dieses Systems (Operator schlägt vor, Wrapper validiert und führt aus). ## Umfang ### Schema-Erweiterung der Operator-Entscheidung - Neues optionales Feld `escalation` in `===OPERATOR-DECISION===`: eine Liste von höchstens zwei zusätzlichen Versuchsprofilen, je `{model, reasoning_effort, iteration_limit}`. - Bei `direct_builder`: `escalation` gehört zur Parent-Karte selbst. - Bei `split`: `escalation` ist auf Parent-Ebene zwingend leer; jedes Kind trägt stattdessen sein eigenes `escalation`. - Bei `needs_human`: `escalation` ist zwingend leer. - Fehlt das Feld in einer Antwort ganz (ältere/kompatible Operator-Läufe), wird es vor der Schema-Prüfung auf `[]` defaultet -- eine Antwort ohne `escalation` bleibt also weiterhin gültig und erhält schlicht keine Leiter. - Jede Eskalationsstufe validiert serverseitig gegen `orchestration.MODEL_POOL` (neue, pro Runtime gepflegte Modell-Allowlist, von Hand synchron gehalten mit `board.html`s bestehender `MODEL_OPTIONS`-Dropdown-Liste): das Modell muss ein konkreter, gelisteter Wert für dieselbe Runtime sein wie der erste Versuch, nie `null`/leer und nie frei erfunden. `iteration_limit` bleibt innerhalb der bestehenden Grenzen (5 bis 40). Runtime und Lane ändern sich über die gesamte Leiter nie -- nur Modell, Reasoning-Effort und Iterationslimit dürfen je Stufe variieren (die beiden Builder-Lanes haben heute ohnehin dieselben Modellfähigkeiten, nur getrennte Warteschlangen; eine dritte Profil-Ebene ist daher nicht nötig). - `agents/operator.md` und der in `worker.py`s `OPERATOR_PROMPT_TEMPLATE` injizierte Startauftrag benennen die erlaubten Eskalationsmodelle je Runtime explizit, damit der Operator ausschließlich daraus wählt. ### Speicherung und deterministische Ausführung - `orchestration.apply_direct()` und `orchestration._apply_split_unlocked()` schreiben die validierte Leiter als `escalation_attempts` auf die jeweilige Karte (Parent bei `direct_builder`, jedes Kind bei `split`). - `orchestration.escalation_ladder_for(card)`: liefert `escalation_attempts`, falls vorhanden, sonst einen synthetischen Ein-Schritt-Fallback, der exakt die aktuellen Werte der Karte wiederholt -- identisch mit dem heutigen flachen Verhalten für jede Karte, die nie durch den Operator gelaufen ist (z. B. eine manuell direkt von Geplant nach In Umsetzung gezogene Karte). - `completion_watcher.py`s `process_record()`: bei `status == "limit_reached"` wird nicht mehr starr `auto_retry_attempts < 1` geprüft, sondern gegen `len(escalation_ladder_for(card))`. Ist noch eine Stufe offen, queued der Wrapper den nächsten Builder-Lauf mit genau den Werten dieser Stufe (nicht mit den alten Werten) und erhöht `auto_retry_attempts` um eins. Ist die Leiter erschöpft, verhält sich die Karte exakt wie heute (keine weitere automatische Aktion, sichtbar für einen Menschen). - `orchestration.child_failed()` und `trigger.py`s Eignungsprüfung für `Aufteilen angefordert` verwenden dieselbe dynamische Leiterlänge statt des bisherigen festen Schwellenwerts `>= 1` -- ein Operator-Kind zählt für den Parent-Scheduler (`durch_kinder_blockiert`) erst nach vollständig ausgeschöpfter eigener Leiter als gescheitert, nicht schon nach dem ersten Fehlschlag. ### Sichtbarkeit - `board.html` zeigt bei `auto_retry_attempts > 0` eine kompakte Zeile „Versuch X von Y (Modell, Z Iterationen)" auf der Karte, damit sichtbar ist, dass gerade eskaliert wird, statt dass die Karte scheinbar hängt. ## Explizit ausgeschlossen - Kein Modellwechsel über die Runtime/Lane hinweg (kein Sprung von `leicht` nach `schwer` oder umgekehrt innerhalb einer Leiter). - Keine dritte, eigene Eskalations-Profil-Konfigurationsebene zusätzlich zu den bestehenden Lane-Spalten -- die Modell-Allowlist ist pro Runtime, nicht pro Lane. - Keine Eskalation für `repair`-/Fixer-Läufe oder für Recherche-Läufe -- ausschließlich Builder-Läufe (`in_umsetzung`/`in_umsetzung_leicht`) mit `status == "limit_reached"`. - Kein rekursives Splitten und keine sonstige Änderung an den bestehenden Operator-Kind-Beschränkungen (`board-intent-operator-orchestration.md` bleibt in Kraft). - Keine unbegrenzte oder vom Modell selbst gesteuerte Eskalation -- maximal zwei zusätzliche, vorab vom Operator festgelegte Stufen, ausschließlich vom Wrapper ausgeführt. - Keine Änderung am bestehenden `repair`-Pfad (weiterhin manuell auslösbar, unabhängig vom Leiterstatus, siehe `trigger.py`s `genuine_failure`-Prüfung) oder an `Aufteilen angefordert` für Nicht-Operator-Karten. ## Annahmen - Die beiden Builder-Lanes (`schwer`/`leicht`) haben heute identische Modellfähigkeiten und unterscheiden sich nur durch separate Warteschlangen/Instanzen (`board-builder-weighted-lanes.md`) -- deshalb genügt eine Modell-Allowlist pro Runtime statt pro Lane. - Eine Karte, die nie durch den Operator gelaufen ist, soll ihr heutiges Verhalten (ein flacher automatischer Wiederholungsversuch mit identischen Parametern) unverändert behalten, nicht regressieren auf null automatische Versuche. - `docs/intent/board-builder-limit-reached-fallback.md` (der ursprüngliche einzelne automatische Retry) bleibt als Sonderfall der hier verallgemeinerten Leiter (Leiterlänge 1) inhaltlich gültig, wird aber durch die dynamische Leiterlängen-Logik hier faktisch ersetzt. ## Offene Risiken - Eine vom Operator schlecht gewählte Eskalationsstufe (z. B. ein Modell, das dieselbe Art Fehler macht) verschwendet bis zu zwei zusätzliche volle Builder-Läufe, bevor ein Mensch eingreift -- der bestehende `repair`-Pfad bleibt jederzeit als sofortiger manueller Ausweg nutzbar, unabhängig vom Leiterstatus. - Mehr automatische Versuche bedeuten mehr Rechenzeit/Kosten pro hartnäckig scheiternder Karte, bevor überhaupt ein Mensch involviert wird -- begrenzt auf maximal zwei zusätzliche Stufen, nicht unbegrenzt. - Die Modell-Allowlist (`orchestration.MODEL_POOL`) und `board.html`s `MODEL_OPTIONS` werden von Hand synchron gehalten, kein gemeinsamer Quellort -- ein vergessenes Nachziehen einer Seite kann zu einer Diskrepanz zwischen der Dropdown-Anzeige und tatsächlich erlaubten Eskalationsmodellen führen. ## Erfolgskriterien - Eine gültige Operator-Antwort mit `escalation`-Einträgen wird akzeptiert und auf der/den betroffenen Karte(n) als `escalation_attempts` gespeichert; eine Antwort ohne dieses Feld bleibt weiterhin gültig. - Ein ungültiger Eskalationsschritt (unbekanntes Modell, mehr als zwei Stufen, Iterationslimit außerhalb 5-40, Runtime-/Lane-Wechsel) macht die gesamte Operator-Antwort ungültig, ohne Teilzustand zu erzeugen. - Ein `limit_reached`-Lauf auf einer Karte mit offener Leiterstufe löst automatisch einen neuen Builder-Lauf mit genau den Werten dieser Stufe aus und erhöht `auto_retry_attempts`. - Eine Karte ohne Operator-Leiter verhält sich bei `limit_reached` unverändert wie heute (ein automatischer Wiederholungsversuch mit identischen Parametern). - Ein Operator-Kind zählt für den Parent-Scheduler erst nach vollständig ausgeschöpfter eigener Leiter als gescheitert. - `Aufteilen angefordert` bleibt für Nicht-Operator-Karten erst nach ausgeschöpfter Leiter erlaubt, für Operator-Kinder weiterhin vollständig gesperrt. - Das Board zeigt bei laufender Eskalation sichtbar den aktuellen Versuchsstand.