This repository has been archived on 2026-09-17. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
nixos-config/docs/intent/board-operator-escalation-ladder.md
pijukka 41e05ed43f board: automatic escalation ladder for limit_reached Builder retries
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
2026-09-10 13:58:52 +02:00

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.