5.9 KiB
Red Queen — A3: Deterministic Safety Layer (V1)
Der Deterministic Safety Layer sitzt zwischen Mission/WP-State (A2 missions.db)
und dem späteren Orchestrator/Action/Retry/Delegation. Er entscheidet
deterministisch (Zähler, Limits, Tabellen — nicht „LLM-Lust"):
CONTINUE / RETRY / DEBUG / SECOND_OPINION / BLOCK / ESCALATE / CIRCUIT_BREAK
Ziel: Red Queen erhält die Safety-Logik, BEVOR spätere autonome Mission-Loops existieren. Dieser Build enthält KEINE autonome Orchestrierung — kein dispatcher, kein loop, kein heartbeat, kein cron, kein self-improvement. A3 ist eine reine Library / Safety-Capability.
DB-Entscheidung (Maker-Entscheidung, §29)
A3 nutzt eine eigene safety.db (separate SQLite-Datei), nicht die A2
missions.db:
- Rollback-sicher für A2: Die bestehende A2-Datenbank wird überhaupt nicht
angefasst — kein ALTER, kein neues Schema in
missions.db, keine Gefahr für Bestandsdaten (CREATE TABLE IF NOT EXISTSauf den A3-Tabellen insafety.db). - Entkopplung: Safety-State (Circuit, Attempts, Events, Evidence) ist unabhängig vom Mission-State. Ein beschädigter Mission-State kann den Safety-Layer nicht mitreißen und umgekehrt.
- Testbar: A3-DB wird immer über temp-Pfade getestet, nie produktiv berührt.
Die A2-Integration erfolgt sauber: SafetyStore verweist Missions-/WP-IDs
als Fremdschlüssel-Namen (mission_id/wp_id) und ruft A2-Regeln nicht auf; A2-APIs
mission_block/mission_transition bleiben unberührt. Mission-BLOCKED kann in
A4 durch den Orchestrator auf Basis einer BLOCK-Safety-Entscheidung gesetzt werden —
A3 selbst mutiert A2 nicht direkt.
Module
| Datei | Inhalt |
|---|---|
rq_safety.py |
Kernmodul: SafetyStore, SafetyError, Error-Signature, Strategy-Fingerprint, Redaction, Circuit Breaker, Retry Controller, Oscillation, Fail-Closed, Safety Events, Evidence, evaluate_next_action (§21) |
rq_safety_telegram.py |
Telegram-Notification-Interface (§19): format_alert, build_payload, should_notify (Anti-Spam). KEIN Daemon. |
rq_safety_cli.py |
Dünne, deterministische CLI (--json, Exit-Code 2 bei SafetyError), A2-CLI-Muster spiegelnd |
test_a3.py |
Isolierte Testsuite (temp-DB), Exit-Code 0 = PASS |
Kern-API (SafetyStore)
from rq_safety import SafetyStore
store = SafetyStore("path/to/safety.db")
# Attempt Ledger (idempotent via idempotency_key, append-only, redacted)
store.record_attempt("M1", "W1", actor="maker", result="FAIL",
error="boom pid=123", strategy="s1", idempotency_key="k-1")
# Retry / Oscillation / Circuit / Fail-closed
store.evaluate_next_action("M1", "W1", error="boom", strategy_label="s2",
is_mutating=True) # -> DECISION/REASON_CODE/ALLOWED_ACTION
# Circuit Breaker
store.open_circuit("MISSION", "M1", trigger="REG", severity="HIGH")
store.circuit_state("MISSION", "M1")
store.request_circuit_reset("MISSION", "M1", cause="...", recovery_evidence="...")
store.close_circuit("MISSION", "M1", approved_by="human", gate="human_gate", cause="...", recovery_evidence="...")
# Events / Evidence
store.safety_event("OSCILLATION_DETECTED", severity="CRITICAL", mission_id="M1")
store.safety_evidence("test_results", {"failing": 2})
store.evidence()
Entscheidungen (Reason Codes, §22)
| Code | Bedeutung |
|---|---|
RETRY_AVAILABLE |
nächste Aktion erlaubt |
RETRY_LIMIT |
Maker/Checker-Repair MAX 3 erreicht → SECOND_OPINION |
SAME_ERROR_LIMIT |
gleiche Error-Signatur MAX 2 → DEBUG/Strategiewechsel |
FAILED_STRATEGY_REPEAT |
bereits gescheiterte Strategie → keine blinde Wiederholung |
NO_MEASURABLE_PROGRESS |
FAIL ohne messbaren Fortschritt (UNKNOWN ≠ Progress) |
OSCILLATION_ABAB |
A-B-A-B-Muster → Circuit-Breaker-Kandidat |
CIRCUIT_ALREADY_OPEN |
Circuit OPEN → BLOCK (read-only erlaubt) |
STATE_INCONSISTENT |
Fail-Closed: Safety-State korrupt → keine Mutation |
CRITICAL_TRIGGER |
kritischer GLOBAL-Trigger |
HUMAN_GATE_REQUIRED |
Circuit-Reset braucht Human Gate bei HIGH/CRITICAL/GLOBAL |
Limite (A1 SAFETY_CONTRACT, konservativ V1)
- Maker→Checker-Repair: MAX 3
- Gleiche Error-Signatur: MAX 2
- Gleiche bereits gescheiterte Strategie: keine blinde Wiederholung
- Oscillation A-B-A-B: Circuit-Breaker-Kandidat
MAX_ITERATIONS=50: äußerste Runtime-Notbremse, nicht operatives Retry-Limit
Fail-Closed (§16)
Bei unbekanntem/inkonsistentem Safety-State → SAFETY_STATE_ERROR → STOP →
Evidence sichern → KEINE Mutation (read-only Diagnose ggf. erlaubt).
Secret-Safety (§28)
- Alle credential-artigen Werte werden vor Persistenz UND vor Fingerprint/Signatur-
Ableitung redacted (
redact_secret). - Keine Tokens/Passwörter/private Keys/Authorization-Header in
safety.db/Events/Telegram. - Secret Exposure wird nur als
SECRET_EXPOSURE_DETECTED+ LOCATION/TYPE +VALUE=REDACTEDerfasst.
Telegram Interface (§19)
Kein Daemon. Nur Formatter/Payload. Kein Spam — nur signifikante Events
(CIRCUIT_OPENED, CRITICAL, ESCALATION_REQUIRED, HUMAN_DECISION_REQUIRED,
RETRY_LIMIT_REACHED, OSCILLATION_DETECTED, SAFETY_STATE_ERROR). Payload ist
deterministisch und redacted.
from rq_safety_telegram import build_payload
payload = build_payload("CIRCUIT_OPENED", "CRITICAL", reason="...", mission_id="M")
# payload["notify"] == True, payload["text"] fertig formatiert
Tests
python3 test_a3.py # Exit 0 = PASS; isoliert (temp-DB), produktive DBs unberührt
Abgedeckt: Attempt Ledger + Idempotenz, Error-Signature-Normalisierung, Strategy- Fingerprint, Retry-Limits, Progress, Oscillation A-B-A-B, False-Positives, Circuit-Breaker (+Restart-Persistenz + Negativ-Test), Fail-Closed, Events, Evidence, Secret-Safety, Telegram-Interface, Loop-Simulation (A–E), A2-DB-Isolation.