The Operator's ===OPERATOR-DECISION=== schema gains an optional "escalation" field (<=2 steps of model/reasoning_effort/iteration_limit, validated against a new per-runtime orchestration.MODEL_POOL allowlist): per-parent for direct_builder, per-child for split. On limit_reached, completion_watcher.py now walks this ladder (via the shared fanout.escalation_ladder_for()) instead of a single flat identical-params retry, escalating model/iteration_limit before falling back to today's manual repair/Aufteilen-angefordert checkpoints. A card that never went through the Operator keeps its existing flat single-retry behavior unchanged. orchestration.child_failed(), fanout.child_terminal(), and trigger.py's Aufteilen-angefordert guard all switch from the old hardcoded ">= 1" threshold to the card's actual ladder length. board.html surfaces the current attempt count on affected cards. Also includes the board-session-usage-indicator research writeup (Codex app-server JSON-RPC + Claude /api/oauth/usage as the two non-interactive usage sources) and its Operator-produced child intents, plus the operator_klaerung_noetig phase-visibility fix already staged in this checkout (distinct phase for a needs_human/crashed Operator run that never produced children, instead of reusing "durch_kinder_blockiert"). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SyeDivnWm6hgiNd3YKjK7Z
155 lines
8.6 KiB
Markdown
155 lines
8.6 KiB
Markdown
# 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.
|