# Red Queen — A5: Controlled Heartbeat & Resume (V1) Der **Controlled Heartbeat & Resume** verbindet den A4 **Bounded Orchestrator** mit einem **dünnen Scheduling-/Resume-Layer** (Heartbeat), sodass eine längere Mission über **mehrere getrennte, sichere, restart-feste Arbeitszyklen** fortgesetzt werden kann — **ohne unkontrollierten Dauer-LLM-Loop**. Der Heartbeat ist **NICHT der Orchestrator** und **keine neue Mission-Engine**: - **A2** (`missions.db`) bleibt autoritativ für Mission-/WP-State. - **A3** (`safety.db`) bleibt verbindliche Safety-Decision (Circuit, Evaluate, Redact). - **A4** bleibt die Orchestrierung (`run_bounded` = garantiert endender Zyklus). - **A5** ist eine **reine, inaktive Scheduling-/Resume-Schicht**, die pro Tick **GENAU EINEN** bounded A4-Zyklus anstößt, das Ergebnis persistiert und sofort endet. **Kein while-True, kein Daemon, kein self-reschedule.** ``` SCHEDULER (Hermes Cron) → Heartbeat.run_tick() → Run-ID (Idempotenz) → Kill-Switch-Gate → Tick-Lock → Mission-Eligibility (inkl. Circuit + Resume-Pending) → A3 check_safety_state → optional Git-Preflight → Approval-Gate → GENAU EIN A4 run_bounded(max_steps=1) → Ergebnis persistieren → Lock freigeben → EXIT ``` --- ## Integrations (verified from source) - **A2 `MissionStore`** (`a2/rq_mission.py`, `missions.db`): - `mission_list()` [NEW, read-only, additiv — A5-Enumeration] - `mission_read(id)`, `mission_state(id)`, `wp_state(id)` - Transitionen: `mission_transition`, `mission_pause`, `mission_resume`, `mission_complete`, `wp_transition`, `wp_complete`. - **A3 `SafetyStore`** (`a3/rq_safety.py`, `safety_db`): - `check_safety_state(mid, wp)` — fail-closed `STATE_INCONSISTENT` - `evaluate_next_action(mid, wp, is_mutating, persist)`, `circuit_state`, `open_circuit`, `attempts`, `safety_evidence`, `redact_secret`. - **A4 `Orchestrator`** (`a4/rq_orchestrator.py`): - `run_bounded(mission_id, max_steps=1, actor)` — EXACTLY-N bounded steps, garantiertes Ende. **Heartbeat ruft DAS GENAU EINMAL pro Tick.** - **Hermes native Cron**: `no_agent`, `executions.db`, `.tick.lock`, `context_from`, `workdir`, `delivery` — die bevorzugte Scheduling-Plattform (kein zweiter Scheduler). --- ## Eigene Registry (A5-spezifisch, `heartbeat.db`) A5 führt **keine neue Missions-/Safety-DB**, sondern nur eine schlanke Registry für Heartbeat-eigene Steuerung: - `kill_switch` — `AUTONOMOUS_EXECUTION_ENABLED`, **Default OFF**. OFF → `run_tick` liefert `HB_KILL_SWITCH_OFF` vor jeder A4-Mutation. `status`/`health` bleiben read-only. - `lock` — **EIN-Tick-Lock** (idempotent). `acquire_lock` → `ok|locked|recover`. Stale nach `TICK_LOCK_STALE_SECONDS` (600s). - `run_id`-Ledger + `tick_log` — jede Tick-Ausführung wird persistiert (Idempotenz). - `approval_gates`, `resume_pending`, `notification_dedup`, `priority`. **Restart-Festigkeit:** Alle DBs (missions, safety, heartbeat) liegen auf **persistenter Volume**, nicht process-local. Ein frischer Prozess liest den State neu; neue Run-ID, keine Doppel-/Neu-Ausführung, kein verlorener Pending-State. --- ## A5-Result-Codes (§10) ``` HB_NO_ACTIVE_MISSION, HB_MISSION_NOT_ELIGIBLE, HB_SAFETY_BLOCKED, HB_STATE_CONFLICT, HB_NO_RUNNABLE_WORK, HB_BOUNDED_RUN_COMPLETE, HB_MISSION_COMPLETED, HB_APPROVAL_REQUIRED, HB_ESCALATED, HB_CIRCUIT_OPEN, HB_TIMEOUT, HB_INTERNAL_ERROR, HB_LOCKED, HB_KILL_SWITCH_OFF, HB_PAUSED, HB_CANCELLED, HB_GIT_CONFLICT, HB_RECOVERY_REQUIRED ``` `health()`/`status` sind read-only und immer erlaubt. Jeder gescheiterte Gate liefert einen **stabilen HB_*-Code** (fail-closed, keine partielle Mutation). --- ## CLI `python3 a5/rq_heartbeat_cli.py` — deterministisch, kein Loop, kein Root: ``` status Kill-Switch, letzter Tick, Registry-Infos tick [--mission ID] EIN bounded Heartbeat-Zyklus (ohne --execute read-only) enable Kill-Switch ON (bewusst; erlaubt A4-Mutation) disable Kill-Switch OFF (Fail-closed) resume --mission ID PAUSED-Mission explizit als resume-fähig markieren approve --gate ID Approval-Gate bewilligen deny --gate ID Approval-Gate ablehnen priority --mission --prio N ``` `--execute` ist ein **globales** Flag (vor dem Subcommand): ohne ist `tick` read-only, mit injiziert es einen echten `Orchestrator` und erlaubt einen A4-Mutation. --- ## Cron-Integration (Hermes native) ```yaml cron: # One-Shot oder wiederkehrend, no_agent=True, Script aus $HERMES_HOME/scripts script: a5_heartbeat_tick.sh # ruft EINEN bounded Tick; kein Loop ``` One-Shot-Jobs werden nach Ausführung automatisch entfernt → es bleibt **kein unkontrollierter Dauer-Job** zurück (`hermes cron list` = 0). Der Tick läuft mit `no_agent` über die Heartbeat-Library, **ohne** eigenständigen LLM-Loop. --- ## Test Harness `python3 a5/test_a5.py` — isolierte, deterministische Tests (temp-DBs, kein Netz, kein echter A4-Dispatch außer einem bounded real-Orchestrator-Test). Exit-Code 0 = PASS. Deckt: Kill-Switch off/on, **Tick-Lock (overlap → `HB_LOCKED` und Lock-Erhalt)**, Lock-Release nach Owner-Tick, Eligibility (cancelled/paused/resumable), Priorität, **GENAU EIN** bounded A4-Call, No-Runnable, Approval-Block/Grant/Resume, Circuit-Block, Git-Konflikt, unbekannte Mission, read-only health, bounded-real-Orchestrator-Contract. Regressions: `a2/test_a2.py` (61), `a3/test_a3.py` (69), `a4/test_a4.py` (83). --- ## Fail-closed & Approval - `mission_completion_gate(..., final_review_pass=False)` — A5 übergibt standardmäßig `False` → **kein autonomes Final-Review/Auto-Complete in V1**. - Approval-Gates persistieren in der Registry; erst nach `approve` wird die Mission wieder runnable. - `open_circuit` (A3) ist **keyword-only** nach scope_type/scope_id: `open_circuit("MISSION", mid, trigger=..., reason=...)`. --- ## Known Limitations - A5 selbst startet **keine** echte A4-Mutation außerhalb eines ausdrücklichen `--execute`-Ticks. Produktiver Heartbeat/Cron ist erst nach expliziter Freigabe (Kill-Switch) aktiv; Default OFF. - Kein automatisches Rain; kein Self-Improvement-Automation; kein unkontrollierter Dauerlauf.