Files
mission-control-v2/docs/ARCHITEKTUR.md
T
HitonabiandClaude Opus 5.5 f4bbdad311 doku: Betriebsdoku neu (ARCHITEKTUR, BETRIEB, UPDATES, RADAR, WIEDERAUFBAU)
- ARCHITEKTUR.md (neu): Prozesse und Units, Rollen box/homelab und Partner-Instanz aus
  Phase 2a (kern/einstellungen.py, kern/zeit.py, kern/partner.py, /api/partner), Modell-Rollen,
  Bild-Weiche, wer welche Datei schreibt (inkl. mc2-quittiert.json), Meldeweg mit
  Telegram-Zweitweg, alle 62 /api-Routen, Herkunftspruefung, kurzer Ausblick Homelab-Teil.
- BETRIEB.md (neu, loest RUNBOOK.md und BACKUP.md ab): Handgriffe, Meldungen und Betreffzeilen,
  Waechter-Regeln (Partner nach 5 Takten, Spracherkennung nur wenn wach, Ausblenden-Knopf),
  Dienste schlafen/wecken, zweistufiger Deploy mit Prueftor, Sicherung und Zurueckspielen,
  Notfall, Pfade auf der Box.
- UPDATES.md (neu): Sonntags-Kette per Timer, Postchecks, Festhalten und Freigeben, Handbetrieb,
  Update-Verlauf, Fallen.
- RADAR.md (neu): Modell-Radar (Leitplanken, Nachtablauf, Pruefstand, Uebernehmen/Verwerfen,
  Merkliste) und Stack-Radar.
- WIEDERAUFBAU.md (neu, loest DISASTER_RECOVERY.md ab): entschlackte Schrittfolge, Anhang A
  (llama-swap-Unit und Drop-ins) behalten.

Stand der Aussagen: Code in main 75611be.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 17:37:09 +02:00

14 KiB

Architektur des Homelab Orchestrators (Teil Box-Wart)

Stand 24.09.2026, Code in main = 75611be. Für Agenten und Techniker. Versionen, Ports und Modelle mit Messwerten: wissen/STACK.md.

Der Homelab Orchestrator (vormals Mission Control 2, kurz „MC2") hat zwei Bereiche: „Box-Wart" für die KI-Box und „Homelab" für den Proxmox-PC. Zwei Instanzen, eine Oberfläche: Dieselbe Codebasis läuft auf der KI-Box (Rolle box) und später als Container auf dem Proxmox-PC (Rolle homelab). Die Homelab-Instanz ist noch nicht eingerichtet (Phase 3, braucht User-OK); dieses Dokument beschreibt deshalb vor allem die Rolle box.

Der Box-Wart hält die KI-Box aktuell, passt auf sie auf und sucht bessere Modelle. Er besteht aus vier eigenen Python-Prozessen, einer Bash-Schicht für Updates, Sicherung und Meldungen und aus Zustandsdateien unter /srv/models. Er steuert zwei fremde Programme: llama-swap mit llama.cpp (die Modelle) und Hermes (der Agent „Lucy" mit Telegram).

Überblick

flowchart LR
  subgraph Netz["Heimnetz"]
    BR["Browser (PC, Handy)"]
    OC["OpenChamber (PC)"]
    LD["Lucy-Desktop (PC)"]
    NQ["NerdQuiz (Arcane)"]
  end
  subgraph Box["KI-Box"]
    MC2["MC2 :9001<br/>Oberfläche, /api"]
    GW["mc2-gateway :9010<br/>/v1, Bild-Weiche"]
    ST["mc2-steward<br/>Wächter, Re-Warm"]
    RD["mc2-radar<br/>00:30"]
    HE["Hermes :8642<br/>Lucy, Telegram"]
    LS["llama-swap :8080"]
  end
  BR --> MC2
  OC -->|/v1| MC2
  LD -->|/api, /v1| MC2
  MC2 -->|/v1 roh| GW
  HE -->|/v1| GW
  GW --> LS
  NQ -->|/v1, Alias fast| LS
  RD -->|Baseline| LS
  ST -.->|prüft| MC2

Alle Modell-Anfragen enden bei llama-swap, das je Modell einen llama-server startet. NerdQuiz geht als einziger Client an MC2 vorbei direkt auf :8080. Nur den Radar-Kandidaten startet der Radar-Lauf nachts als eigenen llama-server auf :5899, neben llama-swap.

Prozesse

Prozess (Unit) Einstieg Aufgabe Eigener Zustand
mission-control-2 (:9001) backend/app.py Oberfläche aus frontend/dist; 62 /api-Routen; reicht /v1 roh an den Gateway weiter (MC_V1_UPSTREAM); reicht das Hermes-Dashboard unter /hermes-ui/ durch (HTTP und WebSocket); Erinnerungen; Update- und Download-Jobs als Kindprozesse (services/jobengine.py) Briefkasten, Erinnerungen, Routing-Policy, Hugging-Face-Zugang, ausgeblendete Hinweise
mc2-gateway (127.0.0.1:9010) backend/gateway_app.py /v1 mit model: auto und Bild-Weiche, Kontext-Warnung, /gw/health Token-Zähler (~/.hermes/token_stats.json)
mc2-steward backend/steward.py Re-Warm (alle 90 s, lädt das Hirn nach, wenn nichts geladen ist), Config-Watch (5 s), Wächter (jede Minute) mc2-waechter.json (einziger Schreiber)
mc2-radar (Timer 00:30) backend/radar_lauf.py Suche und Nachttest neuer Modelle mc2-radar.json, Baseline, /srv/models/radar/
Bash-Schicht deploy/*.sh Updates (autoupdate.sh mit update-swap.sh, update-engine.sh, Postchecks, self-repair.sh), Sicherung (backup.sh, restore.sh), Meldungen (notify.sh, morgenmeldung.sh), Vorwärmen (warmup.sh), projekte-sync.sh, Deploy (deploy.sh, pruefen.sh) Pins, Meldeprotokoll, Nacht-Warteschlange, Deploy-Log

Alle vier Python-Prozesse teilen die flachen Pakete backend/services/ und backend/kern/ (Import über PYTHONPATH und WorkingDirectory der Units). Router bleiben dünn, die Logik liegt in backend/services/ (siehe AGENTS.md).

Warum getrennte Prozesse (Umbau v3, 15.07.): Der Gateway ist klein, wird kaum angefasst und startet sich selbst neu (Restart=always, TimeoutStopSec=5). MC2 darf deshalb neu starten, ohne dass Anfragen von Lucy und OpenChamber abreißen. Der Steward überlebt MC2-Neustarts und kann MC2 selbst überwachen. Rückweg ist jeweils eine Zeile in deploy/mission-control-2.service (MC_V1_UPSTREAM bzw. MC_REWARM_ENABLED=0/MC_SENTRY_ENABLED=0 entfernen).

Fremde Dienste, die der Box-Wart nur steuert oder überwacht: llama-swap (System-Unit, --watch-config), hermes-gateway, hermes-builtin-ui, lucy-stimme, voice-service und box-console (beide schlafen seit 24.09.).

Rollen und Partner-Instanz (seit 24.09., Phase 2a)

  • backend/kern/einstellungen.py: MC_ROLLE (box oder homelab, Standard box), MC_INSTANZ (Anzeigename, Standard „Box-Wart" bzw. „Homelab"), MC_DATEN_DIR (Zustandsdateien; Standard /srv/models auf der Box, /var/lib/mc2 im Homelab), MC_PARTNER_URL (Basis-URL der anderen Instanz, leer = keine) und MC_PARTNER_NAME.
  • backend/kern/zeit.py: die eine Zeitzone MC_LOCAL_TZ (Standard Europe/Berlin) statt einer Kopie je Modul.
  • backend/kern/partner.py: Lebenszeichen der anderen Instanz über deren /api/health (5 s Zeitlimit, 15 s zwischengespeichert).
  • Rolle box hängt alle Box-Router ein. Rolle homelab lädt nur /api/health und /api/partner; die Homelab-Router folgen in Phase 3. steward.py betreibt Re-Warm und Config-Watch nur in der Rolle box; der Wächter prüft im Homelab nur Platte und Partner.
  • /api/health nennt rolle und instanz; engine_reachable, gateway_reachable und brain gibt es nur auf der Box.
  • GET /api/partner liefert das Lebenszeichen der anderen Instanz. /api/partner/<pfad> reicht an <partner>/api/<pfad> weiter (alle Methoden, nach der Herkunftsprüfung). partner/… und stream werden nicht weitergereicht: Zwei Instanzen sollen sich keine Anfrage endlos zureichen, und der Live-Strom würde offen bleiben.
  • Wächter: Antwortet die andere Instanz nicht, entsteht der Hinweis „ antwortet nicht", rot erst nach MC_WAECHTER_PARTNER_TAKTE = 5 Takten, damit ein Neustart kein Alarm ist.
  • Die Units setzen weder MC_ROLLE noch MC_PARTNER_URL: Die Box läuft in der Rolle box und ohne Partner, bis die zweite Instanz eingerichtet ist.

Modelle und Modell-Rollen

  • Rollen-Aliase statt Namen: Clients fragen hermes/fast (Hirn), coder/heavy (Coder), vision und coder-bild (Bild-Zwillinge), embed, reranker. Die Eintragsnamen ändern sich beim Tausch, die Aliase nicht.
  • Gruppen: brains (swap: false, persistent: true, exclusive: false) enthält nur das Hirn, es bleibt geladen. bild (swap: true, exclusive: false) enthält die zwei Zwillinge, höchstens einer ist geladen. Alles andere lädt bei Bedarf und geht nach ttl wieder raus.
  • Bild-Weiche (routers/gateway_proxy.py, services/router_logic.py): Hat der aktuelle Schritt ein Bild, geht die Anfrage an den Zwilling der Rolle (Coder-Ziele an coder-bild, alles andere an vision). Ältere Bilder beschreibt vision einmal als Text (Cache je Bild), damit der Rest beim schnellen Modell mit Draft bleibt.
  • model: auto ist die Chat-Spur: normal fast, bei langen oder schweren Anfragen heavy (Schwellen in der Routing-Policy mc2-routing.json, ohne Datei gelten die Env-Defaults).
  • Warm halten: warmup.sh (Root-Kopie in /usr/local/bin/llama-swap-warmup.sh) läuft nach jedem Start von llama-swap. Danach übernimmt der Re-Warm im Steward; welches Modell warm bleibt, sagt MC_WARMSET im Drop-in mc2-steward.service.d/warmset.conf (heute nur das Hirn).
  • Speicherregel: Warm-Set plus größtes Bedarfsmodell ≤ ~115 GB, sonst droht ein Kernel-OOM.

Wer schreibt was

Datei Schreiber Regel
/etc/llama-swap/config.yaml MC2 (Modelle, Rollen, Aufräumen, Hirn-Umstellung), Radar („Übernehmen"), deploy.sh, restore.sh Die lebende Datei ist die Wahrheit. MC2 schreibt nur unter llamaswap.config_sperre() (Thread-Sperre plus flock auf .mc2-config.lock) und atomar. deploy.sh überschreibt nur, wenn sich deploy/llama-swap.config.yaml im selben Deploy geändert hat. Jede Änderung lädt llama-swap neu und entlädt alle Modelle.
~/.hermes/config.yaml Hermes; MC2 nur bei der Hirn-Umstellung; autoupdate.sh und restore.sh beim Rückweg; self-repair.sh MC2 ändert nur model.default/model.model, atomar, vorher config.yaml.bak-hirn-<Zeit>; bei einem Lesefehler schreibt es nichts.
mc2-steward.service.d/warmset.conf deploy.sh; das Radar beim Übernehmen eines neuen Hirns Das Radar tauscht den Namen in MC_WARMSET und startet den Steward neu.
/srv/models/mc2-waechter.json nur der Steward MC2 liest und führt Knöpfe aus; der nächste Takt sieht das Ergebnis. Der Ordner folgt MC_DATEN_DIR.
/srv/models/mc2-quittiert.json nur MC2 (Knopf „Ausblenden bis zum nächsten Lauf") Der Steward liest; ein ausgeblendeter Werkzeugfehler-Hinweis kommt wieder, wenn der nächste Lauf des Jobs erneut Fehler hat.
/srv/models/mc2-radar.json (+ mc2-radar-baseline.json) Radar-Lauf und MC2 jede Änderung unter flock, atomar
/srv/models/mc2-pins.json autoupdate.sh hält fest; MC2 gibt frei Format: Baustein → pinned, version, grund, datum
/srv/models/mc2-announce.json (Briefkasten, 200 Einträge) nur MC2 Steward und Gateway liefern per HTTP (MC_ANNOUNCE_HTTP), notify.sh per POST /api/voice/announce
/srv/models/mc2-reminders.json nur MC2 Erinnerungs-Schleife und /api/reminders im selben Prozess
/srv/models/mc2-geheimnisse.json MC2 (Einstellungen) Hugging-Face-Zugang, Rechte 0600
/srv/models/mc2-discover.json MC2, Radar Cache der Hugging-Face-Entdeckung (12 h)
/srv/models/mc2-deploy.log deploy.sh eine Zeile je Deploy, auch gescheiterte
~/mc2-notify.log notify.sh (anhängen), morgenmeldung.sh (über 3 MB auf 2 MB kürzen) Quelle des Update-Verlaufs; der Wortlaut „QUEUED für Morgen-Digest" muss bleiben
~/.hermes/night-queue.txt notify.sh (anhängen), morgenmeldung.sh (übernehmen, senden) nicht gesendeter Stapel bleibt als .senden liegen

Meldeweg

deploy/notify.sh ist der eine Meldeweg: Die Meldung geht in Lucys Briefkasten (POST /api/voice/announce) und an Telegram. Zwischen 00:00 und 06:59 sammelt notify.sh alles Nicht-Dringende in der Nacht-Warteschlange; morgenmeldung.sh (Timer 07:00) schickt es als eine Nachricht. Dringend ist -d, ein Betreff mit „Alarm" oder „Notfall" oder ein Text, der mit „KRITISCH" beginnt. Lucy-Desktop fragt den Briefkasten ab (/api/voice/announcements) und spricht neue Einträge.

Telegram hat zwei Wege: zuerst hermes send --to telegram; scheitert das oder fehlt Hermes (die Homelab-Instanz hat keins), geht die Meldung direkt an die Telegram-Bot-API (seit 24.09., live bewiesen 15:41). Die Zugangsdaten liest notify.sh aus der Datei in MC_TELEGRAM_ENV (Standard ~/.hermes/.env: TELEGRAM_BOT_TOKEN, TELEGRAM_HOME_CHANNEL, optional TELEGRAM_HOME_CHANNEL_THREAD_ID) und gibt das Token über stdin an curl, damit es nicht in der Prozessliste steht. Erst wenn beide Wege scheitern, bleiben Protokoll und wall (MC_NOTIFY_OHNE_WALL=1 schaltet wall ab). Einzelheiten und Betreffzeilen: BETRIEB.md.

Schnittstellen

In der Rolle box 62 Routen unter /api: 60 des Box-Warts (seit 24.09., vorher 95) und 2 für die Partner-Instanz. In der Rolle homelab gibt es nur /api/health und die Partner-Routen.

Gruppe Routen
Cockpit (routers/boxwart.py) GET /start, GET /hinweise, POST /hinweise/{id}/aktion/{aktion}, GET /modelle/nutzung, GET /updates/verlauf, POST /updates/festgehalten/{baustein}/freigeben, GET /modelle/aufraeumen, POST /modelle/aufraeumen/loeschen
Radar (routers/radar.py) GET /radar, POST /radar/suche, POST /radar/{id}/uebernehmen, POST /radar/{id}/verwerfen
Modelle (routers/models.py) GET /models, GET /discover, POST /models/register, GET /hf/search, GET /hf/quants, POST /models/install, GET /jobs, POST /jobs/{id}/cancel, POST /models/{id}/role, POST /models/unload, POST /models/{id}/unload, POST /models/{id}/load, DELETE /models/{id}
Wartung (routers/maintenance.py) GET /maintenance/updates, GET /maintenance/update-details, POST check-updates, os-update, engine-update, swap-update, hermes-update, update-all, restart; GET /maintenance/logs; GET/POST /maintenance/geheimnisse
System (routers/system.py) GET /system/status, GET /system/services, POST /system/backup, GET /system/backups, POST /system/restart, GET /system/token-stats
Lucy und Sprache (routers/voice.py) POST /alarm, POST /voice/announce, GET /voice/announcements, POST /voice/stt, POST /voice/turn, POST /voice/chat, GET /lucy/stimme/health, POST /lucy/stimme/tts, POST /lucy/stimme/tts/stream
Sonstiges GET /health, GET /stream (Live-Strom, SSE), GET /routing, GET/POST /reminders, DELETE /reminders/{id}, GET /zeitmaschine, POST /zeitmaschine/restore
Partner (routers/partner.py) GET /partner (Lebenszeichen), /partner/{pfad} (Durchreiche, alle Methoden)

Dazu /v1/* (Weiterleitung an den Gateway), /hermes-ui/ und die Auslieferung der Oberfläche. Unbekannte /api-Pfade liefern einen Fehler (bei GET 404) statt der Startseite. Die MCP-Server in mcp/ sprechen dieselben Routen.

Herkunftsprüfung (backend/services/herkunft.py, statt Anmeldung): Schreibende Aufrufe (POST, PUT, PATCH, DELETE) auf /api/ mit dem Origin einer fremden Webseite lehnt MC2 mit 403 ab. Erlaubt bleiben Aufrufe ohne Origin (Skripte, curl), Origin: null bzw. file:// (Lucy-Desktop), dieselbe Adresse wie die Oberfläche und die Entwicklungs-Ports 5173, 5180, 5181. /v1 ist nicht betroffen.

Ausblick: der Homelab-Teil (Phasen 3 und 4)

Stand der Recherche vom 24.09.2026: Community-Script-Container aktualisiert man im Container mit update, ohne Rückfragen mit PHS_SILENT=1; die installierte Version steht in /root/.<app>. Die Proxmox-API kann keine Befehle in Containern ausführen und gibt die Liste der Host-Updates nur mit Schreibrecht heraus. Deshalb: ein Lese-Schlüssel, ein Proxmox-Webhook für Host-Pakete und ein kleiner Ausführer auf dem Host mit festen Aktionen. Container mit Bind-Mount (z. B. PBS) lassen sich nicht snapshotten, dort gilt „Backup statt Snapshot". Arcane meldet Image-Updates über eine eigene API mit Schlüssel. Ein Exit-Code 0 beweist kein gelungenes Update; geprüft wird unabhängig (HTTP-Probe, Dienststatus, Version). Fahrplan: wissen/OFFENE-FAEDEN.md.