ada85504f3
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
143 lines
11 KiB
Markdown
143 lines
11 KiB
Markdown
# Mission Control
|
||
|
||
Web-Dashboard zur Verwaltung eines **lokalen LLM-Stacks (`llama-swap`)** auf dem Bosgame M5.
|
||
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 + `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).
|
||
- **`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.
|
||
- **`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-WS **proxyt zum Hermes-Agent-Server (:8642)** + transcribe(Whisper)/tts(Piper)/status/pubkey + **Cockpit-Reads** agent/cron/skills (Phase 4)
|
||
- `hermes_ui.py` — **Reverse-Proxy** auf das eingebettete Hermes-Web-Dashboard (`/hermes-ui/`, HTTP + WS-Bridge `pty/ws/pub/events`, `X-Forwarded-Prefix`). Der Chat lebt jetzt hier (iframe), nicht mehr im Eigenbau.
|
||
- **`hermes_control.py`** — read-only Control-Plane: liest Agent-Status (`gateway_state.json` + Cron-Heartbeat), Cron-Jobs & Skills via `hermes`-CLI (`COLUMNS=400`, Rich-Tabelle geparst). TTL-Cache.
|
||
- **`model_caps.py`** — Modell-Capability-Tags (Phase 10): dependency-freier GGUF-Header-Reader (architecture/expert_count/context_length/parameter_count, offline) + cmd-Flags (`--jinja`/`--mmproj`) + Familien-Fallback → MoE/Tools(yes|likely|no)/Vision/Coder/Reasoning/Embedding. Genutzt von `models.py` (`meta.capabilities`).
|
||
|
||
**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`.
|
||
|
||
## Der Stack drumherum
|
||
|
||
- **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**: aktuell Eigenbau als Teil von Mission Control; **ab v9 das Nous Hermes-Agent-Framework** als eigener Dienst (`hermes-agent.service`, OpenAI-API auf `:8642`, base_url → llama-swap, MCP-Client bindet `mcp_memory.py` ein). Voice (Whisper STT + Piper TTS) bleibt in Mission Control und wrappt :8642.
|
||
|
||
## Entwickeln & Deployen
|
||
|
||
**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
|
||
```
|
||
|
||
**Bosgame-Zugang:**
|
||
```bash
|
||
ssh -i ~/.ssh/id_ed25519_hermes -o IdentitiesOnly=yes hitonabi@192.168.178.151
|
||
```
|
||
User: `hitonabi`. sudo NOPASSWD-Whitelist für `systemctl restart mission-control|llama-swap` und `journalctl`.
|
||
|
||
**Frontend-Build:**
|
||
```bash
|
||
cd frontend && npm run build # vor jedem git push
|
||
```
|
||
Output: `static/dist/main.js` + `static/dist/main.css` — wird committet (kein Build-Schritt auf dem Server).
|
||
|
||
**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).
|
||
|
||
**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_API_URL` | `http://127.0.0.1:8642/v1` | Hermes-Agent-Server (OpenAI-API) |
|
||
| `HERMES_API_KEY` | aus `~/.hermes/.env` | API-Key des Hermes-Servers (sonst Env) |
|
||
| `HERMES_HOME` | `~/.hermes` | Hermes-Agent State-Dir (Cockpit-Reads) |
|
||
| `HERMES_BIN` | `~/.local/bin/hermes` | Hermes-CLI (Cron/Skills-Reads) |
|
||
| `HERMES_DASHBOARD_URL` | `http://127.0.0.1:9119` | Hermes-Web-Dashboard (eingebettet via `/hermes-ui/`) |
|
||
| `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
|
||
|
||
- **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
|
||
|
||
- **`${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`).
|
||
- **system_status-Keys**: `/api/system/status` liefert verschachtelt (`cpu.percent`, `ram.used` in Bytes) — nicht flach. Konsumenten müssen entsprechend lesen.
|
||
- **Hermes-Server-Key**: `routers/hermes.py` proxyt zu `:8642` und liest den Key bei Bedarf aus `~/.hermes/.env` (`API_SERVER_KEY`) — MC muss als User `hitonabi` laufen, sonst kein Lesezugriff.
|
||
- **Hermes-Dashboard (Phase 5)**: braucht Extras `uv pip install --python ~/.hermes/hermes-agent/venv/bin/python -e ".[web,pty]"` und einen einmaligen UI-Build (erster `hermes dashboard`-Start ohne `--skip-build`; Output → `hermes_cli/web_dist`). Dienst: `~/.config/systemd/user/hermes-dashboard.service` (an `127.0.0.1:9119` gebunden, `--skip-build`). Wird unter `/hermes-ui/` per `X-Forwarded-Prefix` geproxyt — nie ohne diesen Header proxen, sonst absolute Asset-Pfade kaputt. `/hermes-ui/*` ist bewusst **ohne** MC-Token (LAN-only; Dashboard self-auth auf Loopback).
|
||
- **OpenCode Config**: Datei heißt `opencode.jsonc` (nicht `.json`), Key ist `"providers"` (Plural).
|
||
- **ConnectPanel hinter NPM-Proxy**: `location.hostname` zeigt Proxy-Domain → LAN-IP Override im Verbinden-Tab setzen (localStorage).
|
||
|
||
## Projektstatus
|
||
|
||
**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.
|
||
**v8.1 (Bug-Fix)**: system_status-Keys, Model-Routing, asyncio, sudo-PW für Engine-Update, Chat-History in sessionStorage.
|
||
**v8.2 (UX)**: Guide-Tabs + Begriffe oben, Mobile Bottom-Nav (≤520px), Download-Links, ConnectPanel-Fixes.
|
||
**v8.3 (Memory Import)**: Import aus Cloud-KIs (Claude/Gemini/ChatGPT) — zeilenweiser Batch-POST.
|
||
**v9 (Hermes-Agent-Adoption, Entscheidung 2026-06-23)**: Eigenbau-Agent → Nous Hermes-Agent-Framework (MIT). MC wird Control-Plane. Eigenes Memory bleibt via MCP erhalten. Phasen 0–3 live (harter Cutover: `hermes_agent.py` entfernt) **+ Phase 4**: Agent-**Cockpit** (Status/Cron/Skills) **+ Phase 5 (Kurswechsel)**: Eigenbau-Chat ersetzt durch das **eingebettete Hermes-Web-Dashboard** (`hermes-dashboard`-Dienst :9119, MC-Reverse-Proxy `/hermes-ui/`, iframe) **+ Phase 6**: volle lokale Rechte (`approvals.mode:auto`, `hooks_auto_accept`, `subagent_auto_approve`) **+ Phase 7**: Agent-Modell **Hermes 4 14B Q6_K** (llama-swap-Alias `hermes`, `--jinja`) **+ Phase 8**: Cockpit zeigt Lernen (`USER.md`) + Aktivität (`hermes insights`) **+ Phase 9**: `mcp_memory.py`-Tool-Beschreibungen proaktiv → geteiltes Memory wächst autonom mit. Offen: Kurator/Dedup fürs MC-SQLite, Self-Update für `hermes-gateway`+`hermes-dashboard`. Siehe ROADMAP v9.
|
||
|
||
**Memory-Architektur (entschieden 2026-06-24):** Zwei komplementäre Schichten, keine Doppelung-Panik. **MC-SQLite (via MCP) = geteilte „Verfassung"** für ALLE Tools (Hermes, Cline, OpenCode, Zed) — kuratierbare, deterministische Fakten/Regeln; wächst autonom durch proaktives `add_memory`. **Hermes-nativ (`~/.hermes/memories/USER.md` + `state.db` FTS5) = Hermes' privates Profil-/Gesprächsgedächtnis** (nur Hermes). Linie: „soll das jedes Tool wissen?" → MC-SQLite; „Hermes' Eindruck/Gesprächsfaden" → nativ. Dokumenten-„Second Brain" (Obsidian/RAG) ist davon getrennt und nur bei Bedarf zu bauen.
|
||
|
||
Offene v8-Einrichtungsschritte (kein Code, nur Setup):
|
||
- Piper Binary + Kerstin-Stimme installieren (Befehle im Hermes-Tab → ⚙ Setup)
|
||
- Windows OpenSSH Server aktivieren + SSH-Key einrichten
|
||
|
||
**Nordstern:** Den Bosgame nie wieder via SSH/Putty anfassen — 100 % Automatisierung / Klicki-Bunti.
|