fix+docs: Bug-Fixes, vollstaendige Dokumentation v8

Bug-Fixes:
- hermes_agent.py: asyncio.get_event_loop() → get_running_loop() (Python 3.10+)
- routers/hermes.py: Thread-Lock fuer Whisper-Init, HERMES_WINDOWS_USER Import,
  Piper stderr logging, __import__ Anti-Pattern entfernt
- routers/memory.py: SQLite WAL-Mode, Kategorie-Enum-Validierung (user/instruction/stable/versioned/ephemeral)
- HermesPanel.svelte: findLast() → reverse().find() (Browser-Kompatibilitaet)
- ConnectPanel.svelte: Hardcoded Username durch Platzhalter ersetzt

Docs:
- CLAUDE.md: komplett aktualisiert (v7+v8, Hermes, Memory, alle Env-Vars)
- ROADMAP.md: v7+v8 als erledigt, naechste Features (v8.1-v9.1)
- README.md: komplett neu geschrieben (Agentic OS Konzept, alle Features)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-06-23 14:48:22 +02:00
parent 5881a21d9a
commit 8539999627
9 changed files with 269 additions and 313 deletions
+86 -90
View File
@@ -1,127 +1,123 @@
# Mission Control
Web-Dashboard zur Verwaltung eines **lokalen LLM-Stacks (`llama-swap`)** auf dem Bosgame M5.
FastAPI-Backend + Vanilla-JS-Dashboard. **Leitprinzip: KISS — kein Build-Schritt, kein Frontend-Framework, keine Datenbank.** Concerns sind getrennt (SoC) — aber *ohne* Build: native ES-Module im Frontend, FastAPI-`APIRouter` im Backend.
FastAPI-Backend + Svelte 5-Frontend (Vite-Build). **Leitprinzip: KISS — SoC ohne Overhead.**
## Architektur
**Backend** (Top-Level-Helfer + ein Router je Bereich):
- **`app.py`** — dünner Einstieg: `FastAPI`, hängt die Router ein, liefert das statische UI aus, Exception-Handler + eine `Cache-Control: no-cache`-Middleware für `/` und `/static` (UI wirkt sofort nach rsync, kein Stale-JS im Browser).
- **`app.py`** — dünner Einstieg: `FastAPI`, hängt die Router ein, liefert das statische UI aus, Exception-Handler + `Cache-Control: no-cache`-Middleware.
- **`config.py`** — alle Env-Vars + die gemeinsame `ruamel.yaml`-Instanz. Auch `SOURCE_DIR`/`PROD_DIR` fürs Self-Update.
- **`auth.py`** — optionale Token-Auth: Header `X-MC-Token` **oder** Query `?token=` (Query ist Pflicht für WebSockets, da Browser dort keine Header senden).
- **`jobengine.py`** — In-Memory-Job-System (Threads + Subprocess) mit Live-Log. `start_job(..., stdin_data=, log_cmd=)`: Secrets (sudo-PW) gehen über **stdin**, die Log-Zeile wird sanitisiert (kein Passwort im Log/`ps`).
- **`llamaswap.py`** — spricht `llama-swap` an (`/running`, `/v1/models`, unload) und liest/schreibt dessen `config.yaml` per `ruamel.yaml` (Kommentare bleiben erhalten).
- **`hw_math.py`** — Odysseus-Fit-Mathe: VRAM/RAM-Bedarf, tps-Schätzung, **`max_ctx_for`** (optimaler Kontext aus Hardware), `extract_params_b`. Genutzt von cookbook + models.
- **`recipes.py`** — kuratierte Use-Case-Stacks (Daten, kein Code) fürs Cookbook 2.0.
- **`routers/*.py`** — ein Router je Bereich: `models.py` (`status` mit Meta/Caps/**optimal_ctx**, `download`, `register`, `update_model`, `unload`, `chat`), `jobs.py` (`jobs`), `maintenance.py` (`update` = **llama.cpp**, `self-update` = **MC selbst**, `os-update`, `service/{name}/restart`, `reboot`, `logs`-WebSocket), `system.py` (`status` + `stream`-WebSocket, Live-Metriken via psutil/sysfs), `cookbook.py` (`analyze`/`evaluate`/`recipes`/`install-recipe`), `integration.py` (`test` — Engine-Verbindung), `news.py` (`news` — RSS-Aggregation via stdlib). Alle unter `/api/*`.
- **`auth.py`** — optionale Token-Auth: Header `X-MC-Token` **oder** Query `?token=` (Query ist Pflicht für WebSockets).
- **`jobengine.py`** — In-Memory-Job-System (Threads + Subprocess) mit Live-Log. Secrets gehen über **stdin** (kein Leak in Log/`ps`).
- **`llamaswap.py`** — spricht `llama-swap` an und liest/schreibt dessen `config.yaml` per `ruamel.yaml`.
- **`hw_math.py`** — Odysseus-Fit-Mathe: VRAM/RAM-Bedarf, tps-Schätzung, `max_ctx_for`, `extract_params_b`.
- **`recipes.py`** — kuratierte Use-Case-Stacks fürs Cookbook 2.0.
- **`hermes_agent.py`** — Kern des Hermes-Agenten: Tool-Definitionen, Tool-Dispatcher, LLM-Loop (ReAct), Modell-Routing.
- **`mcp_memory.py`** — stdio-MCP-Server: Gedächtnis-Tools für Cline/OpenCode/Claude Code.
- **`routers/*.py`** — ein Router je Bereich:
- `models.py` — status/download/register/update_model/unload/chat
- `jobs.py` — Jobs-Liste
- `maintenance.py` — update/self-update/os-update/service-restart/reboot/logs-WebSocket
- `system.py` — status + stream-WebSocket (Live-Metriken psutil/sysfs)
- `cookbook.py` — analyze/evaluate/recipes/install-recipe/upgrades
- `integration.py` — test (Engine-Verbindungstest)
- `news.py` — RSS-Aggregation via stdlib
- `memory.py` — Gedächtnis-CRUD (SQLite, WAL-Mode), 5 Kategorien
- `hermes.py` — Chat-WebSocket/transcribe(Whisper)/tts(Piper)/status/pubkey
**Frontend** (`static/`, dünne Hülle + ES-Module, kein Build):
- **`index.html`** — nur Gerüst: Sidebar-Nav, Topbar, Alert-Banner, ein `.view`-Container je Bereich (Hash-Routing). Lädt `css/*` und `js/main.js` als Modul.
- **Design-System (v3):** **EINE** Akzentfarbe (Teal `#2dd4bf`) für alles Klickbare; Grün/Gelb/Rot **nur** für Status. Dichtes Control-Plane-Layout, Monospace-Zahlen, Bento. **Keine Inline-Styles in Panels** — alles über Klassen aus `components.css` (`.card`, `.tile`, `.qa`, `.fit-badge`, `.modal-*`, `.badge`, `.bar`/`.meter`, `.chip` …). Tokens in `css/base.css` (`:root`).
- **`js/core/*`** — `api.js` (Fetch + Token), `ui.js` (DOM-Helfer, Toast, Inline-Icon-Set, **`confirmModal`/`promptModal`** für Beginner-UX, `fmtBytes`/`fmtPct`), `nav.js` (beschrifteter View-Switch).
- **`js/panels/*`** — ein Panel je Bereich (`overview`, `models`, `server`, `cookbook`, `connect`=Verbinden, `news`, `jobs`=Aktivität, `guides`). Panel-Vertrag: `{ id, mount?(), onStatus?(s), onJobs?(jobs), onSystem?(sys) }`.
- **`js/main.js`** — bootet Panels, pflegt Topbar/Alert + **ehrlichen Security-Chip** (`status.secured`), WebSocket für Live-Metriken (`/api/system/stream`, 2 Hz), Polling (`/api/status` 3 s, `/api/jobs` 1.5 s).
- **Leitprinzip UX:** verständlich/idiotensicher — Klartext-Microcopy, Fachbegriffe übersetzt (CPU→Prozessor, VRAM→Grafikspeicher), geführte Aktionen, heikle Aktionen mit `confirmModal` (Klartext-Konsequenz).
**Frontend** (`frontend/src/`, Svelte 5 + Vite, Build → `static/dist/`):
- **`index.html`** — Gerüst: Sidebar-Nav (10 Tabs), Topbar, Alert-Banner, View-Container je Bereich.
- **`frontend/src/main.ts`** — bootet alle Svelte-Panels, Topbar/Alert, WebSocket Live-Metriken, Polling.
- **`frontend/src/panels/`** — je ein Panel: Overview, Models, Jobs(Aktivität), Server, Cookbook, Connect(Verbinden), News, Guides, Memory(Gedächtnis), Hermes.
- **`frontend/src/stores/`** — Svelte 5 Runes: `status.svelte.ts`, `jobs.svelte.ts`, `system.svelte.ts`.
- **Design-System (v3):** EINE Akzentfarbe Teal `#2dd4bf`. Grün/Gelb/Rot nur für Status. Tokens in `css/base.css`.
- **`mission-control.service`** — systemd-Unit (uvicorn auf Port 9000).
- **Konfiguration** rein über Env-Vars: `MC_LLAMA_SWAP_URL`, `MC_CONFIG_PATH`, `MC_MODELS_DIR`, `MC_CMD_TEMPLATE`, `MC_UPDATE_CMD`, `MC_DEFAULT_TTL`, `MC_TOKEN`, `MC_SOURCE_DIR` (Self-Update-Quelle, Default `~/mission-control`).
## Der Stack drumherum
## Der Stack drumherum (Kontext)
- **Bosgame M5**: AMD Strix Halo (gfx1151), Ubuntu 26.04, Kernel 7.0, ~124 GB GTT-Speicher. LAN-IP `192.168.178.151`.
- **Inferenz**: vorgebaute llama.cpp-ROCm-Binaries → `llama-swap` auf **`*:8080` (LAN-offen**, mit `-watch-config`) → die Guides-Integrationen funktionieren von anderen Geräten. Engine-Update via `MC_UPDATE_CMD=/usr/local/bin/update-llamacpp` (lädt llama.cpp-ROCm für gfx1151).
- **3 Modelle / 5 Rollen**: `coder`, `scout`, `vision` (Qwen3-Familie). Modelle werden geswappt, nicht parallel geladen.
- **Mission Control**: Produktiv unter `/opt/mission-control` (als Dienst), Source-Repo unter `~/mission-control`.
- **Bosgame M5**: AMD Strix Halo (gfx1151), Ubuntu 26.04, Kernel 7.0, ~124 GB GTT. LAN-IP `192.168.178.151`.
- **Inferenz**: llama.cpp-ROCm-Binaries → `llama-swap` auf `*:8080` (LAN-offen, `-watch-config`).
- **3 Modelle / 5 Rollen**: `coder` (Qwen3-30B-A3B), `scout` (Qwen3-8B), `vision` (Qwen3-VL).
- **Mission Control**: Produktiv unter `/opt/mission-control`, Source unter `~/mission-control`.
- **Gedächtnis**: SQLite unter `/srv/models/mission-control-memory.db` (5 Kategorien: user/instruction/stable/versioned/ephemeral).
- **Hermes Agent**: läuft als Teil von Mission Control (kein separater Dienst), Voice via Whisper (CPU) + Piper TTS.
## Entwickeln & Deployen
**Wo entwickelt wird:** primär auf einem **Windows-PC** (Repo z. B. `F:\Coding Stuff\mission-control`),
Zielsystem ist der **Linux-Bosgame**. Lokal nur Smoke-Test (rendert/bootet sauber?), echter
Funktionstest nur auf dem Bosgame (dort läuft llama-swap).
**Wo entwickelt wird:** Windows-PC (`F:\Coding Stuff\mission-control`), Ziel: Linux-Bosgame.
Lokal nur Smoke-Test (rendert/bootet?), echter Funktionstest auf dem Bosgame.
**Lokaler Smoke-Test (Windows):**
```bash
python -m venv .venv && .venv/Scripts/python -m pip install -r requirements.txt
.venv/Scripts/python -m uvicorn app:app --port 9001
```
Ohne llama-swap ist alles im Offline-Zustand (rote Pill, Warn-Banner) — das ist erwartet.
**Bosgame-Zugang (SSH, key-basiert, passwortlos):**
**Bosgame-Zugang:**
```bash
ssh -i ~/.ssh/id_ed25519_hermes -o IdentitiesOnly=yes hitonabi@192.168.178.151
```
User ist **`hitonabi`** (klein!). `sudo` braucht generell ein Passwort — **außer** einer engen NOPASSWD-Whitelist für genau `systemctl restart mission-control|llama-swap` und `journalctl` (dadurch laufen Self-Update, Dienst-Restarts und Log-Stream passwortlos). OS-Update/Reboot fragen das Passwort weiterhin ab.
User: `hitonabi`. sudo NOPASSWD-Whitelist für `systemctl restart mission-control|llama-swap` und `journalctl`.
**Self-Update statt manuellem Deploy:** Der Button „Mission Control aktualisieren" (`POST /api/self-update`) macht `git pull` (Source) → rsync → `systemctl restart` — der dokumentierte manuelle Weg unten ist nur noch Fallback/Erstinstallation.
**Frontend-Build (Vite + Svelte 5, ab Phase C):**
**Frontend-Build:**
```bash
cd frontend && npm install # einmalig
npm run build # vor jedem git push — Output: static/dist/main.js
cd frontend && npm run build # vor jedem git push
```
Das Build-Ergebnis (`static/dist/`) wird committet kein Build-Schritt auf dem Server.
Beim Entwickeln: `npm run dev` in `frontend/` startet den Vite Dev-Server (Port 5173) für HMR.
Neue Svelte-Panels kommen in `frontend/src/panels/`. Sobald ein Panel migriert ist,
wird es in `frontend/src/main.ts` importiert und das alte `static/js/panels/<panel>.js` nicht mehr gebraucht.
Output: `static/dist/main.js` + `static/dist/main.css` wird committet (kein Build-Schritt auf dem Server).
**Deploy-Kette (Windows → Gitea → Bosgame):**
1. Frontend bauen: `cd frontend && npm run build``static/dist/main.js` aktualisiert.
2. Lokal Smoke-Test`git push` (origin = Gitea `http://192.168.178.153:3000/Hitonabi/mission-control.git`).
3. Bosgame: `cd ~/mission-control && git pull --ff-only` (Source-Repo).
4. Nach `/opt` ausrollen — **ohne sudo**, `node_modules` ausnehmen:
```bash
rsync -a --exclude='.git' --exclude='.venv' --exclude='__pycache__' --exclude='*.pyc' --exclude='frontend/node_modules' ~/mission-control/ /opt/mission-control/
```
5. `sudo systemctl restart mission-control` (Passwort nötig). Prod = `:9000`, Dev-Spielwiese = `:9001`.
6. **Niemals direkt in `/opt` arbeiten.** Logs: `journalctl -u mission-control -f`.
**Deploy-Kette:**
```
npm run build → git push (Gitea :3000) → Bosgame: git pull → rsync → systemctl restart
```
Oder über den Self-Update-Button in Mission Control (empfohlen).
Statische Dateien werden je Request frisch von Platte gelesen → UI-Änderungen wirken schon nach
rsync; **Python-Code-Änderungen brauchen den Restart**.
**Self-Update-Button** (`POST /api/self-update`): git pull → rsync → restart — alles in einem Klick.
## Konfiguration (Env-Vars)
| Variable | Default | Zweck |
|---|---|---|
| `MC_LLAMA_SWAP_URL` | `http://127.0.0.1:8080` | llama-swap URL |
| `MC_CONFIG_PATH` | `/etc/llama-swap/config.yaml` | llama-swap Config |
| `MC_MODELS_DIR` | `/srv/models` | GGUF-Ablageort |
| `MC_TOKEN` | leer | Auth-Token (optional, LAN-only) |
| `MC_UPDATE_CMD` | leer | Engine-Update-Befehl |
| `MC_DEFAULT_TTL` | `300` | Sekunden bis Auto-Unload |
| `MC_SOURCE_DIR` | `~/mission-control` | Self-Update Quelle |
| `MC_MEMORY_DB` | `{MODELS_DIR}/mission-control-memory.db` | SQLite Gedächtnis |
| `HERMES_SIMPLE_MODEL` | `scout` | Modell für einfache Tasks |
| `HERMES_COMPLEX_MODEL` | `coder` | Modell für komplexe Tasks |
| `HERMES_WINDOWS_HOST` | leer | Windows-PC IP für SSH |
| `HERMES_WINDOWS_USER` | `TobisPC` | Windows SSH-Username |
| `HERMES_SSH_KEY` | `~/.ssh/id_ed25519_hermes_agent` | SSH-Key für Hermes |
| `PIPER_BIN` | `/opt/mission-control/piper/piper` | Piper TTS Binary |
| `PIPER_VOICE` | `.../de_DE-kerstin-low.onnx` | Piper Stimm-Modell |
| `WHISPER_MODEL` | `medium` | Whisper Modell-Größe |
## Konventionen
- **Frontend: Vite + Svelte 5** (Phase C). Neuer Bereich = `frontend/src/panels/<Bereich>Panel.svelte` + `routers/<bereich>.py` + `initNav`-Eintrag in `index.html`. Danach `npm run build` in `frontend/` → `static/dist/main.js` wird committet.
- Stores: `frontend/src/stores/status.svelte.ts`, `jobs.svelte.ts`, `system.svelte.ts` — Svelte 5 Runes (`$state`).
- Statische Hilfsfunktionen in `static/js/core/` (api.js, ui.js, nav.js) bleiben als gemeinsame Basis — werden von Vite gebündelt, nicht direkt geladen.
- **Backend SoC**: ein `routers/<bereich>.py` je Bereich, gemeinsame Logik in `config/auth/jobengine/llamaswap/hw_math`.
- Endpoint-URLs bleiben unter `/api/*`; neue Bereiche degradieren sauber, wenn ihre Quelle (sysfs, `amd-smi`, `systemctl`) fehlt (z. B. beim Entwickeln auf Windows).
- Funktion darf **nicht** von `localStorage` abhängen (nur das Token-Feld nutzt es, das ist ok).
- **Sicherheit**: Das Backend führt Shell-Befehle aus → ausschließlich im vertrauenswürdigen LAN betreiben, niemals offen ins Internet.
- **Neuer Bereich**: `routers/<bereich>.py` + `frontend/src/panels/<Bereich>Panel.svelte` + Nav/View in `index.html` + Import in `main.ts`. Danach `npm run build`.
- **Backend SoC**: gemeinsame Logik in `config/auth/jobengine/llamaswap/hw_math`. Alles unter `/api/*`.
- **Sicherheit**: Shell-Befehle → nur LAN. Secrets via stdin. Token optional.
- **SQLite**: WAL-Mode aktiv, `check_same_thread=False` — ausreichend für Single-User.
- **`asyncio.get_running_loop()`** statt `get_event_loop()` (Python 3.10+ kompatibel).
- **Memory-Kategorien**: `user` | `instruction` | `stable` | `versioned` | `ephemeral` — Enum in `routers/memory.py`.
## Gotchas (wichtig!)
## Gotchas
- **`${PORT}`** in der generierten `llama-swap`-Config muss **literal** stehen bleiben → beim Bauen des cmd-Strings `str.replace` benutzen, NICHT `.format` (sonst KeyError auf `PORT`).
- `llama-swap` muss mit **`-watch-config`** laufen, sonst greift das Auto-Einpflegen neuer Modelle nicht.
- HuggingFace-Downloads mit **`HF_HUB_DISABLE_XET=1`** (sonst reproduzierbarer Hänger bei ~6 MB).
- Vision-Modelle in llama.cpp brauchen zusätzlich **`--mmproj <projektor>` und `--jinja`**.
- **`${PORT}`** in llama-swap-Config `str.replace` statt `.format` (KeyError sonst).
- **`-watch-config`** bei llama-swap nötig für Auto-Einpflegen.
- **`HF_HUB_DISABLE_XET=1`** bei HuggingFace-Downloads (Hänger bei ~6 MB).
- **Vision-Modelle**: brauchen `--mmproj <projektor>` und `--jinja`.
- **Gitea-Push** kann transient mit „Failed to authenticate" fehlschlagen → einfach nochmal.
- **Piper TTS**: Binary + Stimm-Modell müssen separat installiert werden (nicht via pip).
- **Whisper**: lädt beim ersten Aufruf ~1.4 GB Modell herunter (einmalig, dann gecacht).
- **Hermes SSH-Key**: muss auf dem Bosgame generiert werden (`~/.ssh/id_ed25519_hermes_agent`).
## Gotchas v3 (zusätzlich)
## Projektstatus
- **`.qa` ist ein `<button>`** → die globale `button.danger`-Regel (Vollrot) schlägt durch. Für gefährliche Aktionszeilen `.qa-danger` nutzen (rotes Icon), NICHT `.danger`.
- **CSS rückwärtskompatibel halten:** Panels werden schrittweise migriert; beim Token-/Klassen-Umbau alte Klassennamen + Vars (`--hi`, `--red`, `--red-dim`) bedienen, sonst brechen noch nicht migrierte Panels.
- **Browser-ESM-Cache:** Beim lokalen Testen bustet ein Soft-Reload den Modul-Cache nicht zuverlässig → Preview-Server neu starten oder cache-bustend dynamisch importieren. In Prod erledigt das die `no-cache`-Middleware (einmal Strg+Shift+R nach dem ersten Deploy).
- **Gitea-Push** kann transient mit „Failed to authenticate user" fehlschlagen (Server kurz weg) → einfach erneut versuchen.
**v7 (Memory Layer)**: SQLite-Gedächtnis mit 5 Kategorien + MCP-Server für alle Tools live.
**v8 (Hermes Agent)**: Chat-Agent mit Voice (Whisper STT + Piper TTS), Tool Calling, WebSocket-Streaming live.
Offene v8-Schritte: Piper-Binary installieren, Windows OpenSSH Server + SSH-Key einrichten.
## Projektstatus & Roadmap
**v3 ist umgesetzt & live** (siehe `ROADMAP.md`): einheitliches Design-System, Beginner-UX
(Klartext/Führung), Security-Härtung (Passwort-Leak dicht, ehrlicher Chip), **Self-Update-Button**.
**v4 ist umgesetzt & live** („Der Lotse" — siehe `ROADMAP.md`): (1) optimale Kontextfenster
auto-ermittelt (`hw_math.max_ctx_for`, in Cookbook + Modelle-Konfig) → (2) Cookbook 2.0
use-case-getrieben mit Stack-Empfehlungen (`recipes.py`, „Komplettes Setup installieren") →
(3) „Verbinden"-Tab mit Connection-Test + Bild-/Vision-Flow → (4) News-Board (RSS via stdlib).
Alles automatisch aus Modellen + Hardware; KISS/SoC blieb (keine DB, keine neue Lib).
**v5 ist umgesetzt & live** („Anfänger-Lotse"): Klartext-Erklärungen (Swapping/Spitzenbedarf, Kontext-ⓘ,
Guides-Neubau), aktuelle Juni-2026-Modelle (Rezepte + dynamische GGUF-Auflösung `_pick_gguf`),
GGUF/MCP erklärt, empfohlene Tools + HF-Token (Einstellungen), News aus Qualitätsquellen +
**Upgrade-Vorschläge** (`/api/cookbook/upgrades` + `/install-model`, `UPGRADES` in `recipes.py`).
**v6 ist umgesetzt & live**: Stack zeigt echtes Modell; Top-News + Toolbar-Update-Badges
(`/api/updates`); News-Magazin-Layout; Guide neu mit Tutorials/Workflows + Konzept-Karten;
Cookbook-Modelle diversifiziert best-in-class (Qwen3/Gemma/Mistral/DeepSeek, neue Kategorie
„Nachdenken & Logik") + „Beste Wahl für dein System" (`recommended_id`).
Wir sind im **Feinschliff- und Wartungsmodus**.
**Nordstern:** den Server nie wieder via SSH/Putty anfassen müssen — 100 % Automatisierung / Klicki-Bunti.
**Nordstern:** Den Bosgame nie wieder via SSH/Putty anfassen — 100 % Automatisierung / Klicki-Bunti.