board-agent's own README documents docs/intent/ and docs/research-intent/ as tracked corpus directories, and docs/review/<slug>.md as the reviewer's persisted report per card - but the pipeline only writes these files to the checkout, it never commits them itself. This had built up a backlog of intent/review docs for cards already marked deployed or archiviert in the live board state, which were sitting uncommitted in the working tree. Adds that backlog as-is: the originating intents, their reviewer reports, the research-intent/research pair for the board UX evaluation, and one standalone process-retrospective report (board-review-pipeline-context- overhead.md, same genre as the two already-tracked audit reports in docs/review/). No code changes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T7UVrKsttMP9a6FiGFna9b
7.5 KiB
Intent: Builder-Deploy ohne Self-Termination und Doppelverarbeitung
Ziel
Ein vom Board ausgelöster Builder darf beim automatischen Deployment auf
blunix weder seinen eigenen Prozess noch das Intent Board in einem
halbaktivierten Zustand zurücklassen. Nach Commit und Push muss das
Deployment außerhalb des Lebenszyklus von agent-codex-builder.service
ausgeführt werden und bei Unterbrechungen idempotent fortsetzbar sein.
Beobachteter Produktionsfehler
Beim Lauf für
board-keep-cards-visible-during-builder-stash trat am 07.09.2026
folgender Ablauf auf:
- Um 12:49:29 startete der Board-Job.
- Der Builder erzeugte und pushte Commit
62c9ce2. - Um 12:50:56 startete
deploy_blunix()synchronnixos-rebuild switchinnerhalb vonagent-codex-builder.service. - Während der Aktivierung stoppte systemd um 12:51:31
board-agent-trigger.serviceund anschließend den Builder-Prozess mitSIGTERMbeziehungsweise Status 143. - Der SIGTERM-Handler legte denselben Job wieder in die Queue.
- Der zweite Modelllauf erzeugte und pushte den zusätzlichen Commit
90ef0c8und startete erneut ein Deployment. - Derselbe Self-Termination-Ablauf wiederholte sich um 12:53:27.
- Erst ein dritter Lauf endete ohne weiteres Deployment, weil keine zusätzlichen Änderungen mehr entstanden waren.
board-agent-trigger.servicebliebinactive (dead), obwohl er deklarativ aktiviert und mitRestart=alwayskonfiguriert ist.
Das war kein Python-Crash des Board-Servers. Der Prozess wurde im Rahmen
einer vom Builder selbst gestarteten, dann unterbrochenen NixOS-Aktivierung
geordnet mit SIGTERM beendet.
Belegte Ursache
builder.py:deploy_blunix()führtsudo nixos-rebuild switchsynchron als Kindprozess des Builders aus.agent-codex-builder.serviceist die Unit, aus der dieser Rebuild gestartet wird. Sobald die neue Konfiguration diese Unit während der Aktivierung stoppt, werden Builder und dessen Rebuild-Prozess gemeinsam beendet.builder.py:_handle_sigterm()requeued jeden aktiven Job unabhängig davon, ob Modelllauf, Commit und Push bereits erfolgreich abgeschlossen sind.apply_commit()führt Commit, Push und Deployment als einen nicht persistent phasierten Ablauf aus. Nach dem Requeue ist nicht erkennbar, welche Schritte bereits dauerhaft erfolgt sind.- Der Retry startet deshalb erneut das Modell und kann einen zweiten Commit für denselben Intent erzeugen.
- Das unterbrochene
nixos-rebuild switchhatte den Board-Dienst bereits gestoppt, erreichte dessen anschließenden Start aber nicht mehr.
Umfang / Auftrag an den Builder
Deployment entkoppeln
nixos-rebuild switchnicht mehr synchron im Prozessbaum vonagent-codex-builder.serviceausführen.- Einen separaten, eng begrenzten Deployment-Runner mit eigenem
systemd-Lebenszyklus definieren. Er erhält einen persistierten Auftrag
mit Commit-Hash, Host und Flake-Pfad und führt ausschließlich den
freigegebenen
blunix-Switch aus. - Der Deployment-Runner muss eine Aktivierung seiner eigenen deklarativen Umgebung überleben. Seine Unit darf während des Switch nicht als Teil des Builder-Neustarts beendet werden.
- Builder und Board dürfen ein erfolgreich gestartetes Deployment nicht mit einem erfolgreich abgeschlossenen Deployment verwechseln.
Persistente Zustandsmaschine
- Den Ablauf pro stabiler Board-Job-ID atomar mindestens in folgende
Zustände aufteilen:
building -> committed -> deploying -> deployedbeziehungsweisebuild_failedoderdeploy_failed. - Commit-Hash und Push-Ergebnis unmittelbar nach ihrem Erfolg persistent speichern, bevor ein Deployment gestartet wird.
- Bei Neustart anhand dieses Zustands fortsetzen: Ein bereits committed Job darf weder erneut das Modell ausführen noch einen weiteren Commit erzeugen.
- Requeue im SIGTERM-Handler nur zulassen, solange nachweislich noch kein
Commit für diesen Job gespeichert wurde. Nach
committeddarf höchstens der separate Deployment-Schritt erneut beziehungsweise kontrolliert fortgesetzt werden. - Einen Idempotency-Key aus Board-Job-ID und Intent-Slug verwenden und doppelte Queue-/Completion-Einträge sicher deduplizieren.
Fehler- und Retry-Verhalten
- Zwischen Build-, Push- und Deployment-Fehlern unterscheiden und den Status dem Board explizit melden.
- Ein gepushter Commit bei fehlgeschlagener oder partieller Aktivierung
darf nicht als
deployedgelten. - Deployment-Retries begrenzen und mit Backoff versehen. Wiederholte
Self-Termination, SIGTERM oder Aktivierungsfehler müssen in einem
sichtbaren
deploy_failed-Zustand enden statt den Modelllauf erneut zu starten. - Der Completion-Watcher muss doppelte Records sowie bereits verarbeitete Commit-Hashes ignorieren.
- Nach einem fehlgeschlagenen Deployment muss das Board den Commit-Hash, den Deployment-Status und einen manuellen Recovery-Hinweis anzeigen.
Betriebsstabilität
- Sicherstellen, dass
board-agent-trigger.servicenach erfolgreicher Aktivierung läuft. - Wird eine Aktivierung unterbrochen, muss der Deployment-Runner den tatsächlichen Unit-Zustand prüfen und einen klaren Fehler melden. Kein stilles Zurücklassen eines deklarativ aktivierten, aber faktisch gestoppten Boards.
- Die Lösung darf keine allgemein nutzbare Root-Shell oder zusätzliche Modellberechtigung schaffen. Das Modell erzeugt weiterhin nur die Änderung; der eng definierte Wrapper führt den Deployment-Auftrag aus.
Tests
- Unit-Test: SIGTERM vor dem Commit requeued genau einmal.
- Unit-Test: SIGTERM nach gespeichertem Commit startet das Modell nicht erneut und erzeugt keinen zweiten Commit.
- Unit-Test: Doppelte Queue- und Completion-Einträge werden anhand der Job-ID dedupliziert.
- Unit-Test: Ein gepushter Commit mit fehlgeschlagenem Switch endet in
deploy_failed, nicht indeployedoder einem neuen Builder-Lauf. - Integrationstest mit Fake-Deployment-Runner: Builder übergibt den Auftrag und kann beendet werden, ohne den Runner zu terminieren.
- Integrationstest: Der Runner meldet nach erfolgreicher Aktivierung den Zustand des Board-Dienstes und den aktivierten Systempfad zurück.
- Bestehende Board-, Builder-, Rollen- und Nix-Evaluierungstests müssen grün bleiben.
Ausgeschlossen
- Kein erneuter Modelllauf als allgemeine Recovery-Strategie nach bereits erfolgreichem Commit.
- Kein Zurücksetzen oder Umschreiben der bereits gepushten Commits
62c9ce2und90ef0c8. - Keine Abschaffung der bestehenden Sandbox- und Rollenbegrenzungen.
- Kein automatisches Deployment auf andere Hosts als
blunix. - Keine imperative Aktivierung allein durch das Vorhandensein dieses Intent-Dokuments.
Erfolgskriterien
- Ein Board-Job erzeugt auch bei einem Dienstneustart höchstens einen fachlichen Commit.
- Der Builder wird nicht durch seinen eigenen Deployment-Kindprozess in eine Requeue-Schleife gebracht.
- Ein unterbrochener Switch hinterlässt keinen fälschlich erfolgreichen Board-Status.
- Nach erfolgreichem Switch antwortet das Board wieder; nach einem Fehler zeigt es einen eindeutigen, persistenten Recovery-Status.
- Commit-/Push- und Deployment-Ergebnisse sind getrennt nachvollziehbar.
- Der aktuelle Produktionsfehler lässt sich mit einem Test reproduzieren, der vor der Änderung fehlschlägt und danach besteht.
Aktueller Recovery-Hinweis
Zum Zeitpunkt der Diagnose ist board-agent-trigger.service auf blunix
seit 12:51:31 inactive (dead). Eine manuelle Wiederinbetriebnahme oder
ein vollständiger erneuter nixos-rebuild switch ist eine separate
betriebliche Aktion und nicht durch diesen Intent freigegeben.