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

8.6 KiB

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.htmls 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.pys 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.pys 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.pys 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.pys 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.htmls 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.