Verbindlicher Vertrag für die Engine-Modi von Homebase. Ergänzt KONZEPT.md („Engine Seams") um die Regel, während KONZEPT.md den Mechanismus beschreibt. Bei Widerspruch gilt dieses Dokument.
Stand: 2026-08-14 · gültig ab 0.1.0-alpha.21 (Ziel-DB des Task-Seams: alpha.22)
Homebase ist ein Stack-MCP mit genau zwei Betriebsmodi. Der Modus wird pro
Namensraum aufgelöst ([engines.<name>].mode schlägt [engines].mode).
canonical — Homebase konsumiert die kanonischen Module des Stacks
(.MEMORY-Verbund, clutch und weitere) und delegiert an deren echte Engine und
deren echte Datenbank. Homebase hält in diesem Modus keinen eigenen Speicher
für den betroffenen Namensraum.
bundled — Homebase ist eine abgeschlossene, portable Distribution mit
explizit eigener DB. Ein nackter npx/pip install ohne jede weitere
Software funktioniert vollständig. Dieser Modus ist kein Notbehelf, sondern ein
gleichwertiger, vollwertiger Auslieferungsmodus.
mode = "canonical"+ kanonisches Ziel nicht erreichbar ⇒ Tool-Fehler mit klarer Meldung. NIEMALS stiller Wechsel aufbundled.
Begründung (Baukasten-Regel, KONZEPT.md §8): Ein stiller Wechsel legt einen
zweiten, unverbundenen Speicher neben den kanonischen Store. Der Aufruf
meldet Erfolg, die Daten landen aber woanders — genau das, was der
canonical-Modus verhindern soll. Ein leiser Erfolg am falschen Ort ist
schädlicher als ein lauter Fehlschlag.
bundled bleibt jederzeit verfügbar — aber nur, wenn er gewählt wurde, nie
als unangekündigter Ersatz.
Die Regel gilt auf Aufrufebene, nicht beim Start:
| Zeitpunkt | Verhalten |
|---|---|
| Serverstart | Server startet immer. Modul lädt, Tools werden gelistet. Ein ERROR nennt den unerreichbaren Namensraum. |
| Tool-Aufruf | Betroffene Tool-Familie wirft CanonicalEngineUnavailable (MCP-isError). |
So bleibt die Zusage aus KONZEPT.md („der Server startet in jedem Fall") erhalten, ohne den stillen Fallback zu behalten. Zusätzlich wird die bundled-DB in diesem Zustand gar nicht erst angelegt — es entsteht keine Schattendatei.
Die Fehlermeldung nennt immer drei Dinge: betroffene Tool-Familie, gesuchtes
Ziel (Pfad und geprüfte Fundorte) und die zwei Auswege (Pfad korrigieren oder
bundled explizit wählen).
| Namensraum | Kanonisches Ziel | Seam | Verhalten bei canonical + unerreichbar |
|---|---|---|---|
hb_garden_* |
GARDENER | implementiert | Fail-closed (alle 4 Tools) |
hb_state_task_* |
TASKPLAN (taskplan.client.TaskClient über die stabile Fassade rinnsal.tasks.client), Tabelle rinnsal_tasks in ~/.taskplan/taskplan.db |
implementiert | Fail-closed (3 Tools) |
hb_mem_* |
USMC | implementiert | Fail-closed (alle 5 Tools) |
hb_kb_* |
KnowledgeDigest | offen | Kein Seam — bleibt bundled, siehe unten |
hb_route_* |
clutch | offen | Kein Seam — bleibt bundled, siehe unten |
hb_policy_* (read-only: resolve/list) |
policy-registry (policy_registry.PolicyRegistry) |
implementiert, canonical-only | Fail-closed IMMER (kein bundled-Modus existiert für diesen Namensraum) |
hb_ticket_* (read-only: list/show) |
_control-center/_TICKETS-Baum (Presence-Check über ticket-master, Lesezugriff direkt auf die Lifecycle-Ordner) |
implementiert, canonical-only | Fail-closed IMMER (kein bundled-Modus) |
hb_lock_* (read-only: list/check) |
lock-master (lock_scan/lock_status) |
implementiert, canonical-only | Fail-closed IMMER (kein bundled-Modus) |
hb_state_mem_*, hb_state_dispatch |
— | kein Ziel | Immer bundled, per Definition |
hb_swarm_*, hb_api_*, hb_test_*, hb_auto_*, hb_conn_*, hb_plug_* |
— | bundled by design | Immer bundled |
Gating je Tool-Familie, nicht je Modul. state trägt zwei Familien: nur
hb_state_task_* ist gegated; hb_state_mem_* und hb_state_dispatch hatten
nie ein kanonisches Gegenstück und bleiben in jedem Modus nutzbar.
hb_policy_*/hb_ticket_*/hb_lock_* sind eine DRITTE Kategorie, weder
"implementiert" (garden/state/mem) noch "offen/bundled" (kb/route)
[T-20260825-196589547]. Diese drei Namensräume haben kein bundled-Modus
zum Herunterfallen — es gibt keine sinnvolle "kleinere, aber ehrliche"
lokale Kopie einer lebenden Policy-/Ticket-/Lock-Wahrheit (anders als bei
kb/route, wo eine kleinere Ersatzimplementierung immerhin ein eigenständig
nützliches Werkzeug ist). Eine leere/veraltete homebase-lokale Kopie sähe wie
eine echte Antwort über den aktuellen Governance-/Koordinationsstand aus,
wäre es aber nicht — das ist gefährlicher als ein klar fehlgeschlagener
Aufruf. _engine_mode wird von diesen drei Modulen daher gar nicht gelesen
(dieselbe "keine Umschaltung existiert"-Form wie bei kb/route, nur
umgekehrt: kb/route ignorieren canonical und bleiben bundled; diese drei
ignorieren bundled und bleiben canonical-only). v1-Scope ist bewusst
read-only: kein hb_policy_register/register_rule (Registry-Schreibpfad
existiert seit D2-R2 Stufe 1, ist hier aber ohne belegten Bedarf nicht
exponiert), kein hb_ticket_move/create, kein hb_lock_create — Locks
werden geprüft, nicht gesetzt.
Aus der Lücken-Analyse (T-20260825-196589547) waren sechs weitere Module Kandidaten; drei davon wurden bewusst NICHT geseamt:
- roshambo — überschneidet sich mit lock-master (
control.locks) und ist laut Systemaudit bereits als choice-Bundle-Alternative zu lock-master legitimiert. Ein zweiter, parallelerhb_*-Namensraum für dieselbe Rolle würde die Auswahl, welche Locking-Implementierung gilt, aus dem choice-Bundle-Mechanismus heraus- und stillschweigend in Homebase hineinverlagern. - session-checkpoint —
visibility: private,development-Reifegrad; eigene ADRs (ADR-001/002) erklären bewusste Isolation ("ein State-Owner, nie in die DB einer anderen App schreiben", "keine Abhängigkeit der Bequemlichkeit halber"). Ein Homebase-Seam wäre ein von Homebase aufgezwungener externer Anschluss, den das Modul selbst nicht vorsieht. Falls gewünscht: session-checkpoints eigener Maintainer müsste zuerst einen Seam-Adapter deklarieren (Symmetrie zum policy-registry/decision-clicker- Präzedenzfall in dessenARCHITECTURE.md). - grounding-seed — Bootstrap-/Onboarding-Primitive
(
bootstrap.standalone/self-knowledge/migration), kein wiederholt abgefragter Laufzeitbestand. Passt nicht zum Stack-MCP-Zweck (Tools, die ein Agent während der Arbeit wiederholt aufruft), sondern zu einer einmaligen Setup-Phase davor.
source-resolver/system-explorer waren ebenfalls Kandidaten, wurden aber
nicht als neue hb_*-Namensräume behandelt, sondern als Umbau von Homebases
eigener interner Pfadauflösung — siehe Abschnitt 2 der Analyse und die
_try_source_resolver_bare_import()-Integration in engines.py für
garden/mem.
Ziel-DB von hb_state_task_*. Der Tabellenname rinnsal_tasks blieb bei der
Extraktion von TASKPLAN aus Rinnsal (2026-07-11) absichtlich stehen; die
Datenbank ist seither ~/.taskplan/taskplan.db. Auflösung, spezifischstes
zuerst: [state].task_db_path → $TASKPLAN_DB → $SCANNER_TASKS_DB (Legacy,
benannte die stillgelegte _tasks-Scanner-Queue unter ~/.rinnsal/) →
~/.taskplan/taskplan.db. $TASKPLAN_DB steht vor dem Legacy-Namen, weil es
die Auflösungs-Eingabe der kanonischen Engine selbst ist — wer damit die Task-DB
verlegt, darf nicht dazu führen, dass ausgerechnet Homebase in einen Speicher
schreibt, den kein anderer taskplan-Konsument liest.
Fail-closed gilt auch für eine unerreichbare Ziel-DB. Bis 0.1.0-alpha.21 war
nur die Engine gegated: Ließ sie sich importieren, zeigte der Zielpfad aber ins
Leere, kam ein nacktes sqlite3.OperationalError: unable to open database file
zurück — ohne Tool-Familie, ohne Ziel, ohne Ausweg. Seit 0.1.0-alpha.22 wirft die
Familie in diesem Fall CanonicalEngineUnavailable mit denselben drei Angaben.
Das Verzeichnis wird bewusst nicht angelegt: eine frische leere
taskplan.db wäre genau der zweite, unverbundene Speicher, den dieser Vertrag
verhindert.
hb_mem_merge / hb_mem_consolidate sind bundled-only Fähigkeiten (USMC
kennt keine Bulk-Hygiene) und melden unter erreichbarem canonical
not_supported. Ist canonical dagegen unerreichbar, sind auch sie
fail-closed: sonst würden sie Zeilen in der falschen Datenbank löschen.
kb und route: Hier gibt es keinen stillen Fallback, weil es keine
Umschaltung gibt — die Module lesen _engine_mode gar nicht. Ein
mode = "canonical" für sie wird also stillschweigend ignoriert, sichtbar
nur im Startlog (engine_summary(): bundled-only (canonical requested, no seam implemented yet)). Das ist ein offener Punkt, kein Fail-closed-Fall; solange
kein Seam existiert, wird hier bewusst kein Verhalten erfunden.
Bis 0.1.0-alpha.20 lieferten hb_garden_*, hb_state_task_* und hb_mem_* bei
unerreichbarem kanonischem Ziel still ein Ergebnis aus der bundled-DB
("engine": "bundled", Status ok/stored). Ab 0.1.0-alpha.21 werfen sie
stattdessen einen Fehler.
Wen das trifft: [engines].mode wirkt global auf garden, state und
mem. Wer global canonical gesetzt hat, aber nur einen Teil der kanonischen
Engines auf der Platte hat (z. B. GARDENER ja, USMC/Rinnsal nein), sieht die
bisher „funktionierenden" Namensräume jetzt fehlschlagen. Das ist die
beabsichtigte Semantik: Diese Aufrufe haben vorher in die falsche DB
geschrieben.
Migration — pro Namensraum entscheiden, statt global zu schalten:
[engines]
mode = "canonical"
[engines.garden]
mode = "canonical" # GARDENER ist vorhanden
[engines.mem]
mode = "bundled" # kein USMC auf diesem Host -> bewusst bundled
[engines.state]
mode = "bundled" # kein Rinnsal auf diesem Host -> bewusst bundledWer die bisherige Vermischung tatsächlich will, wählt bundled explizit — der
Unterschied ist, dass die Wahl jetzt sichtbar in der Konfiguration steht.
engine_summary() (Startlog) liest die Konfiguration, nicht den aufgelösten
Zustand. Es meldet daher garden=canonical, auch wenn die Engine unerreichbar
ist und Aufrufe fehlschlagen. Die danebenstehende ERROR-Zeile nennt den
tatsächlichen Zustand. Bewusst nicht geändert, um den Startbericht in diesem
Schritt nicht umzubauen.