Author SHA1 Message Date
Hitonabi 0b639870ff fix(frontend): mem0_ms nach memory_ms umbenannt, Mem0-Texte zu Gedächtnis, dist neu gebaut
Ampel / ampel (push) Successful in 21s
2026-08-23 21:28:41 +02:00
Hitonabi 084dd73f71 fix(deploy): Mem0-Logik aus Backup/Restore/Postchecks/Venv-Audit/Hooks entfernt, mem0_service/ gelöscht 2026-08-23 21:27:32 +02:00
Hitonabi 84b4643f1a fix(backend): Mem0-Reste aus Steward, Voice-Metrics und Backup entfernt 2026-08-23 21:27:32 +02:00
Hitonabi 41f2566b29 feat(mcp): Mem0-Reste in mcp_mc.py entfernt (3 Docstrings: Dienste, Services, Backup) 2026-08-22 15:33:55 +02:00
Hitonabi 30ab2c013a savepoint: mem0-ausbau Stand dokumentiert (7/17 Dateien + frontend) 2026-08-22 14:47:50 +02:00
Box d508a848f5 wip(referenz): Stand des abgebrochenen Laufs A2 gesichert (5 von 17 Dateien)
Ampel / ampel (push) Successful in 21s
2026-08-22 13:07:03 +02:00
HitonabiandClaude Opus 5 e04ace242c docs(referenz): Lauf A ausgewertet - Arbeitsanweisung gegen die Analyse-Schleife
Ampel / ampel (push) Successful in 22s
Lauf A ueber Nacht: 6 Sitzungen, ~336 Nachrichten, ~360.000 Tokens,
19 Dateien gelesen - und NULL Dateien geschrieben. Das Werkzeug fuer
Dateioperationen war die ganze Zeit verfuegbar, wurde nie benutzt.

Der Agent lief fuenfmal in dasselbe Muster: "Ich habe jetzt das
vollstaendige Bild, jetzt lese ich nur noch die restlichen Dateien,
bevor ich anfange." Dann war max_turns erreicht. Sein eigenes Fazit:
"Iterationslimit erreicht, bevor ich die erste Zeile geaendert habe."

Wichtigste Erkenntnis: das ist KEIN Tempo-Problem. Mit einem schnelleren
Modell waere derselbe Lauf genauso gescheitert, nur frueher. Darum
kommen A2 (Arbeitsanweisung) und D (reasoning low) jetzt VOR den
Modellwechseln B und C.

Neu: Arbeitsanweisung ganz oben - hoechstens zwei Dateien lesen, bevor
die erste geaendert wird. Dazu agent.max_turns 40 -> 80 im coder-Profil.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 12:14:25 +02:00
HitonabiandClaude Opus 5 d880a76b6a fix(referenz): Pruefbefehl aus dem Suchpfad genommen, Lauf D ergaenzt
Ampel / ampel (push) Successful in 22s
Zwei Fehler aus dem ersten Anlauf behoben:

1. referenz-check.sh lag unter deploy/ und enthielt selbst 7x das Wort
   "mem0" (es sucht ja danach) - damit zaehlte es sich in Kriterium 3 mit
   und das Kriterium war unerfuellbar. Jetzt unter docs/aufgaben/, wo
   Kriterium 3 nicht sucht. Strukturell geloest statt per grep-Ausnahme.

2. Der Lauf muss in einer FRISCHEN Sitzung starten - der Desktop hatte
   eine Sitzung vom Vortag fortgesetzt und deren Kontext mitgeschleppt.

Neu: Lauf D (reasoning_effort low). Der Fehlversuch zeigte, dass >95%
der Modell-Ausgabe unsichtbares Nachdenken war - 19.899 Tokens fuer
2.600 Zeichen sichtbaren Text. Das ist der billigste Hebel und wird
darum vor Modellwechseln geprueft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 21:57:07 +02:00
HitonabiandClaude Opus 5 ff319ff291 feat(deploy): referenz-check.sh - objektiver Fertig-Test fuer die Messlatte
Ampel / ampel (push) Successful in 21s
Damit 'fertig' keine Auslegungssache ist: derselbe Befehl fuer den Agenten
zur Selbstpruefung und fuer die Auswertung. Prueft alle fuenf Kriterien
und gibt ein klares Urteil plus Exit-Code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 21:07:45 +02:00
Hitonabi 6e3440379b docs: Referenzaufgabe auf gemessene Pfade korrigiert (ruff-venv, app statt main, Worktree)
Ampel / ampel (push) Successful in 22s
2026-08-20 21:06:40 +02:00
HitonabiandClaude Opus 5 5f35f0d580 docs: Referenzaufgabe fuer die Stack-Messlatte (Stufe 2)
Ampel / ampel (push) Successful in 21s
Mem0-Ausbau als wiederholbare Messaufgabe: mehrteilig genug um
aussagekraeftig zu sein, mit fuenf objektiv pruefbaren Fertig-Kriterien
und einer Ruecksetz-Anleitung fuer die Wiederholungslaeufe.

Gemessen wird nicht t/s, sondern: wird die Aufgabe fertig, wie lange
dauert es, wie viele Zuege braucht es.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 21:04:24 +02:00
HitonabiandClaude Opus 5 43fa747bc3 feat(deploy): projekte-sync.sh - haelt ~/projekte mit Gitea deckungsgleich
Ampel / ampel (push) Successful in 22s
Der Hermes-Desktop laeuft gegen das Box-Backend, sein Ordner-Waehler zeigt
also das Dateisystem der Box. Damit dort alle Projekte auftauchen und aktuell
sind, holt dieses Skript sie regelmaessig.

Bewusst konservativ, weil es unbeaufsichtigt laeuft:
- nur --ff-only, kein merge/rebase/reset/force
- Repos mit lokalen Aenderungen werden uebersprungen statt ueberfahren
- mission-control-v2 wird NIE gezogen (Live-Deployment, pusht selbst),
  bekommt nur eine Verknuepfung fuer den Waehler
- bestehende Checkouts in ~ bleiben liegen, ~/projekte verknuepft sie
- interne Gitea-IP statt DDNS (die ist nachts durch Zwangstrennung tot)

Erster Lauf: 6 Repos neu geklont, lucy aktualisiert, rippy-windows korrekt
wegen lokaler Aenderungen uebersprungen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 20:50:05 +02:00
HitonabiandClaude Opus 5 83c6ecc613 fix(skills): worker.sh und restliche Doku auf Rollen-Aliase umgestellt
Ampel / ampel (push) Successful in 21s
Zweite Fundwelle derselben Ursache: deploy/worker.sh setzte
WORKER_MODEL-Default auf Qwen3-Coder-Next - damit waere JEDE delegierte
Bau-/Refactor-Aufgabe des Orchestrators mit HTTP 404 gescheitert.

Ausserdem: konzept-fliessband nannte GLM-4.7-Flash als Skeptiker,
router_logic.py nannte Qwen3-Coder-Next hinter der coder-Rolle.
Modellnamen raus, Rollen rein - Namen veralten, Rollen nicht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 16:09:29 +02:00
HitonabiandClaude Opus 5 b316ad40ba fix(skills): Rollen-Aliase statt Modellnamen + Savepoint auf gemessenen Stand
Ampel / ampel (push) Successful in 21s
Der Kritiker-Gate war stumm kaputt: orchestrator/wartung-Skill und
fremdblick.sh riefen Qwen3-Coder-Next, GLM-4.6V-Flash und GLM-4.7-Flash
auf - alle drei von der Modell-Konsolidierung (9a35be9) entfernt. Im
llama-swap-Log schlug das als HTTP 404 auf.

Ursache war das Muster, Modelle beim Eigennamen zu nennen. Jetzt Aliase
(coder/debugger/fast/heavy), die llama-swap aufloest - damit ueberlebt
jede Faehigkeit den naechsten Modellwechsel.

SAVEPOINT.md nannte fuenf Modelle, die es nicht mehr gibt. Ersetzt durch
die heute gemessenen Werte inkl. Bandbreiten-Erklaerung fuers Coder-Tempo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 16:07:03 +02:00
HitonabiandClaude Opus 5 8c371b946b fix(lint): Ungenutzte MEM0_SERVICE_URL-Importe entfernt (Ampel war rot)
Ampel / ampel (push) Successful in 22s
Rueckstand aus dem Mem0-Rueckbau: 2286879 entfernte die Verwendung,
liess die Importe aber stehen. Ruff meldete F401, die Ampel stand
seit dem 19.08. auf Rot (Laeufe #73-#76).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 15:54:21 +02:00
Hitonabi 1a2051e988 Revert "perf: Warm-Set auf Lucy, Coder, Debugger, Embed und Reranker erweitert"
This reverts commit 5d93822a7e.
2026-08-20 15:52:44 +02:00
Hitonabi 5d93822a7e perf: Warm-Set auf Lucy, Coder, Debugger, Embed und Reranker erweitert 2026-08-19 19:27:54 +02:00
Hitonabi 22868795c9 fix(sentry): Entferne toten mem0 Check aus Telegram-Health-Wächter
Ampel / ampel (push) Failing after 21s
2026-08-19 18:21:28 +02:00
Hitonabi 9a35be91f9 fix(llamaswap): Konsolidiere Modelle & entferne redundante Alt-Aliase
Ampel / ampel (push) Failing after 21s
2026-08-19 17:57:51 +02:00
Hitonabi 241b1d54c6 fix(deploy): Veralteten release-dist restore aus deploy.sh entfernt
Ampel / ampel (push) Failing after 22s
2026-08-19 17:38:57 +02:00
Hitonabi eb4bcfc4b3 fix(mc2): Frontend & Backend umfassend bereinigt, Mem0-Reste entfernt & Hermes-Nativ verdrahtet
Ampel / ampel (push) Failing after 29s
2026-08-19 17:36:34 +02:00
Hitonabi d2a175b0ad fix(smoke): max_tokens in Bild-Weiche v2 fuer Reasoning-Modelle angepasst
Ampel / ampel (push) Successful in 22s
2026-08-19 17:31:03 +02:00
Hitonabi bf145a3f97 feat: Qwen3.8-27B als Haupt-Coder verdrahtet & Mem0 endgueltig abgeloest
Ampel / ampel (push) Successful in 22s
2026-08-19 17:23:54 +02:00
Hitonabi 5b0909089f refactor: Stack-Audit, Doku-Bereinigung und Governor-Cleanup (Stand 19.08.2026)
Ampel / ampel (push) Successful in 23s
2026-08-19 17:05:45 +02:00
Hitonabi 4b6c6ebfd5 feat(routing): Rollen-Aliase 'debugger' und 'doctor' für Muse-Glimmer-30B verdrahtet 2026-08-14 19:01:09 +02:00
Hitonabi 0a6b009cb6 feat(models): Muse-Glimmer-30B mit DFlash-Speculative-Drafting in Katalog und llama-swap integriert 2026-08-14 18:49:26 +02:00
Hitonabi adc73c817f fix(config): Qwen3.6-35B-A3B bleibt warmes Gehirn (90 t/s MoE), Qwen3.8-27B on-demand bereitgestellt 2026-08-14 18:16:20 +02:00
Hitonabi ff795ba026 feat(models): Upgrade auf Qwen 3.8 27B als neues Hermes-Gehirn und Fast-Modell 2026-08-14 18:07:31 +02:00
Hitonabi 46edbe50c1 chore: correct hardware specs to 128GB RAM in AGENTS.md and trend-radar
Ampel / ampel (push) Successful in 22s
2026-08-08 00:49:40 +02:00
Hitonabi cd7e1bc114 fix(trend-radar): enforce 96GB RAM hardware constraint
Ampel / ampel (push) Successful in 24s
2026-08-08 00:46:42 +02:00
Hitonabi 600d0caad6 fix: instruct trend-radar to use notify.sh and allow live-repo edits
Ampel / ampel (push) Successful in 23s
2026-08-08 00:38:13 +02:00
Hitonabi 733582900a fix: auto-sync skills to hermes skills directory during deployment
Ampel / ampel (push) Successful in 23s
2026-08-08 00:28:47 +02:00
Hitonabi 8b14dfdc14 feat: rewrite trend-radar skill for full autonomy and self-modification
Ampel / ampel (push) Successful in 22s
2026-08-08 00:18:06 +02:00
Hitonabi dced44548d fix: correctly pass 'chat' command to hermes CLI when starting skills
Ampel / ampel (push) Successful in 23s
2026-08-08 00:05:24 +02:00
Hitonabi 4c0af92b3c fix: restore Wissen tab but filter out traeume directory
Ampel / ampel (push) Successful in 23s
2026-08-08 00:00:54 +02:00
Hitonabi 5cc1e6bea1 fix: remove frontend/dist from main to fix CI build
Ampel / ampel (push) Successful in 25s
2026-08-07 23:42:13 +02:00
Hitonabi 102149a5d6 build: compile frontend and fix gitignore for dist
Ampel / ampel (push) Successful in 22s
2026-08-07 23:36:43 +02:00
Hitonabi 0c849f4dcd fix: use autonomous zero-intervention lane (-z) for skill runner 2026-08-07 23:33:41 +02:00
Hitonabi 94490f114d fix: correct hermes cli invocation for skills run
Ampel / ampel (push) Successful in 21s
2026-08-07 17:10:48 +02:00
Hitonabi c360bbe14e fix: ruff import sorting in skills.py
Ampel / ampel (push) Successful in 22s
2026-08-07 17:05:49 +02:00
Hitonabi 6cb29ee70e feat: show running state of skills in frontend using psutil
Ampel / ampel (push) Failing after 21s
2026-08-07 17:03:30 +02:00
Hitonabi a6e2f78c27 feat: add missing SKILL.md files for autonomous hermes crons
Ampel / ampel (push) Successful in 22s
2026-08-07 16:50:02 +02:00
Hitonabi a201c20bf2 fix: resolve correct path for skills directory relative to repo root
Ampel / ampel (push) Successful in 22s
2026-08-07 16:42:39 +02:00
Hitonabi 56676fe83c fix: remove outdated Wissen/Traeume functionality completely
Ampel / ampel (push) Successful in 23s
2026-08-07 16:32:34 +02:00
Hitonabi 77bead5ba4 fix: update release-dist tracking branch explicitly during deployment
Ampel / ampel (push) Successful in 24s
2026-08-07 16:28:38 +02:00
Hitonabi ee456de713 fix: remaining ruff formatting issues and robust gitea auth
Ampel / ampel (push) Successful in 23s
2026-08-07 16:25:27 +02:00
Hitonabi e16f0140f7 fix: ampel ci unbound variable GITHUB_SHA
Ampel / ampel (push) Failing after 22s
2026-08-07 16:19:35 +02:00
Hitonabi 637c21387a fix: ruff linter warnings
Ampel / ampel (push) Failing after 23s
2026-08-07 16:14:49 +02:00
Hitonabi da03de18cb feat: Skills Dashboard, CI Build Automation, Auto-Sync & CI-Ampel MCP
Ampel / ampel (push) Failing after 22s
2026-08-07 16:08:49 +02:00
Hitonabi 1b0184eed8 Llama-Swap Config Sync (Devstral hinzugefügt, GLM-4.7 entfernt, persistent=true)
Ampel / ampel (push) Failing after 23s
2026-08-07 15:58:46 +02:00
Hitonabi 4fb8387f79 IDE Line Cleanup & Agentic IDE Umbau
Ampel / ampel (push) Failing after 22s
2026-08-07 14:01:27 +02:00
Hitonabi cb884abdc8 fix: graph view node payload keys (content instead of label)
Ampel / ampel (push) Failing after 22s
2026-08-07 12:20:34 +02:00
Hitonabi 0a6b6fd18d chore: build frontend for wissens-vault graph view
Ampel / ampel (push) Failing after 24s
2026-08-07 12:16:05 +02:00
Hitonabi 8f359ef4c9 feat: obsidian-style graph view for wissens-vault 2026-08-07 12:16:01 +02:00
96 changed files with 1849 additions and 1463 deletions
+22
View File
@@ -0,0 +1,22 @@
{
"mcpServers": {
"mc2-memory": {
"command": "python",
"args": [
"F:\\Coding Stuff\\mission-control-2\\mcp\\mcp_memory.py"
]
},
"mc2-system": {
"command": "python",
"args": [
"F:\\Coding Stuff\\mission-control-2\\mcp\\mcp_mc.py"
]
},
"mc2-web": {
"command": "python",
"args": [
"F:\\Coding Stuff\\mission-control-2\\mcp\\mcp_web.py"
]
}
}
}
+23
View File
@@ -42,6 +42,29 @@ jobs:
rot=1
else
( cd frontend && npm ci --no-audit --no-fund && npm run build ) || rot=1
if [ $rot -eq 0 ]; then
echo "== Frontend: Push nach release-dist =="
git config --global user.name "Gitea Actions"
git config --global user.email "actions@gitea.local"
CURRENT_COMMIT=$(git rev-parse HEAD)
# Erzeuge (oder hole) den orphan branch 'release-dist'
git checkout --orphan release-dist 2>/dev/null || git checkout release-dist
git rm -rf . >/dev/null 2>&1 || true
# Checkout nur frontend/dist vom aktuellen Stand
git add -f frontend/dist
if ! git diff-index --quiet HEAD; then
git commit -m "Automatischer Frontend Build ($CURRENT_COMMIT) [skip ci]"
# Push to release-dist. Token auth from runner is expected to work for Gitea Actions.
git push origin HEAD:release-dist -f
else
echo "Keine Änderungen am Frontend-Build."
fi
# Zurück zum originalen Branch für den Rest des Skripts
git checkout $CURRENT_COMMIT
fi
fi
fi
-1
View File
@@ -22,4 +22,3 @@ gemma_swap*
# TypeScript-Inkrementalcache (reines Build-Artefakt, maschinenabhängig)
frontend/tsconfig.tsbuildinfo
+12 -1
View File
@@ -8,7 +8,12 @@ Lokaler Local-AI-Stack für die Box (Bosgame M5, Strix Halo, Vulkan/RADV). Schic
Engine (llama-swap) · Builtin-Routing-Gateway (`model: auto`) · MC2 (FastAPI + React/shadcn) ·
**Hermes-Agent (das autonome Gehirn "Lucy")**. Backend Python (Box läuft Python 3.14), Frontend Vite/React/shadcn/Tailwind.
**WICHTIG:** Alle alten Dienste wie `mem0`, `governor` oder `eigenleben` sind komplett gelöscht. Hermes ist das EINZIGE "Gehirn" und läuft autonom. Es nutzt keine alte Mem0-DB, sondern sein eigenes System.
**Hardware-Spezifikationen der Box (WICHTIG für alle Agenten):**
- **System:** Bosgame M5 (AMD Strix Halo APU)
- **Arbeitsspeicher (RAM/VRAM):** 128 GB Shared Memory (ca. 122.7 GB nutzbar).
- **Inference-Limit:** Modelle im GGUF-Format dürfen maximal ca. 100-110 GB groß sein (entspricht ca. 150B Parametern bei Q4_K_M). Größere Modelle (wie 200B+ oder 2T Parameter) **können lokal nicht ausgeführt werden** und sind auszuschließen, es sei denn, es handelt sich um stark quantisierte MoEs.
**Gedächtnis & Dienste:** Hermes ist das autonome Agenten-Gehirn („Lucy"). Mem0 läuft als dedizierter semantischer Langzeit-Gedächtnis-Sidecar (`mem0-service` auf `:8765`) und wird von Hermes via MCP (`mcp_memory.py`) eingebunden. Governor, Zed-Workflows und alte Mittelschichten sind komplett gelöscht.
## Sprache (nicht verhandelbar)
- **Alles User-Sichtbare ist Deutsch:** UI-Texte, Fehlermeldungen, Update-/Radar-Meldungen,
Skills, LLM-Summaries, Commit-Messages, Code-Kommentare.
@@ -24,6 +29,12 @@ Engine (llama-swap) · Builtin-Routing-Gateway (`model: auto`) · MC2 (FastAPI +
dem Deploy auf Gitea liegen**, und uncommittete Box-Änderungen gehen verloren (Absicht).
- **Nie direkt auf `main` arbeiten.** Immer Branch (`wartung/...`), Gate grün, dann Merge/Deploy.
## Agentic IDE & Vibe Coding
- **Zero Middle-Layers:** Coding passiert zu 100% lokal auf dem Dev-PC in der **OpenCode Desktop IDE**.
- Es gibt keinen Zed-Workflow, keine OpenCode-CLI-Mittelschicht und keinen Governor mehr.
- Der Agent in OpenCode Desktop nutzt via MCP (`.agents/mcp_config.json`) die API der Box (`:9001/v1`), um autonom Projekte zu bauen.
- **Mix-Ansatz beim Testen:** Der Agent testet lokal. Vor dem Push muss er prüfen, ob das Gitea-`VERIFY`-Skript fehlerfrei durchläuft.
## Zeit & Umgebung
- **Box = Ubuntu, läuft in `Europe/Berlin`** (seit 03.07.2026; vorher UTC). Dev-PC = Windows.
Naive/lokale Zeiten immer über `MC_LOCAL_TZ` (= `Europe/Berlin`) auflösen, nie `datetime.now()` ohne TZ annehmen
+24 -29
View File
@@ -12,54 +12,55 @@ prüft sich selbst und meldet sich, wenn etwas nicht stimmt.
| Bahn | Was sie tut | Wo |
|---|---|---|
| **Steuerzentrale** | Modelle, Routing, Wartung, Gedächtnis, Ideen-Queue, Auftragsbuch | MC2 `:9001` |
| **Coding** | Zed + OpenCode am Tag, `opencode-lauf.sh` in der Nacht — **ein Regelwerk, zwei Auslöser** | Governor `:8100` |
| **Coding** | Agentic IDE (z.B. OpenCode Desktop) auf Dev-PC via lokaler API + MCP | 100% lokal |
| **Lucy** | Sprach-Companion mit 3D-Avatar, Augen und Ohren | eigenes Repo `Hitonabi/lucy` |
Lucy holt ihr Hirn über MC2, spricht aber nie durch den Governor — ein Gespräch ist keine
Bau-Sitzung und wird nie unterbrochen.
Lucy holt ihr Hirn direkt über das MC2-Gateway (`:9010/v1`) mit nativer Bildweiche und Voice-Streaming.
## Ports & Dienste
| Port | Dienst | Erreichbar |
|---|---|---|
| `9001` | **MC2** (FastAPI + React) — Cockpit, API, Konsole | LAN |
| `8100` | **Governor** — Token-Wächter vor dem Gateway | LAN |
| `8080` | **llama-swap** — Modell-Router | LAN |
| `9010` | `mc2-gateway``/v1`-Datenpfad (`model: auto`, Bild-Weiche) | loopback |
| `8642` · `9119` | Hermes-Gateway · Hermes-Web-UI | loopback |
| `8765` · `8650` | Mem0-Sidecar · Voice-Sidecar (STT/TTS) | loopback |
| `7682` | ttyd — Box-Konsole, same-origin unter `/console/` | loopback |
Units in [`deploy/`](deploy/): `mission-control-2` · `mc2-gateway` · `mc2-steward` · `governor` ·
Units in [`deploy/`](deploy/): `mission-control-2` · `mc2-gateway` · `mc2-steward` ·
`mem0-service` · `voice-service` · `box-console` · `hermes-builtin-ui` + Timer für Backup,
Auto-Update, Self-Smoke und Bagatell-Annahme. `llama-swap` läuft als System-Unit.
## Der Weg einer Idee
## Die Bau-Pipeline
```
Idee in MC2 → Gitea-Repo (mit CI-Ampel + VERIFY) → in Zed bauen → Ampel → main
└── oder nachts: Hermes-Cron → opencode-lauf.sh → dieselben Regeln
Idee in MC2 → Gitea-Repo (mit CI-Ampel + VERIFY) → in Agentic IDE bauen → Ampel → main
└── autonomes Vibe Coding via OpenCode Desktop + MCP
```
Jedes neue Repo wird von [`deploy/gitea-repo-create.sh`](deploy/gitea-repo-create.sh) **mit beiden
Wächtern geboren**:
- **`.gitea/workflows/ci.yml`** — die Außen-Prüfung nach dem Push (Gitea Actions)
- **`VERIFY`** — die Innen-Prüfung: eine Zeile, wie man das Projekt testet. Das OpenCode-Plugin
führt sie nach jeder Etappe aus und gibt rote Tests dem Agenten **sofort** zurück, statt sie
erst der CI zu zeigen. Keine `VERIFY`-Datei = Prüf-Tor aus.
- **`VERIFY`** — die Innen-Prüfung: eine Zeile, wie man das Projekt testet. Die Agentic IDE
führt sie im Mix-Ansatz vor jedem Push aus, um Code-Fehler ("Quality Gap") abzufangen.
Keine `VERIFY`-Datei = Prüf-Tor aus.
## Modelle & Rollen
Gemessen auf der Box (25.07.2026, Vulkan, Q4):
Gemessen auf der Box (Stand: August 2026, Vulkan b10502):
| Rolle | Modell | Tempo | Aufgabe |
|---|---|---|---|
| `coder` | Qwen3-Coder-Next (80B-A3B) | **51,5 t/s** · Prefill **754 t/s** | Plant und baut — Kopf der Coding-Mannschaft |
| `hermes` / `fast` | Qwen3.6-35B-A3B | **69,6 t/s** | Lucys Hirn **und** der Sucher-Subagent. Immer warm |
| `kritiker` | Devstral-Small-2-24B | **15,0 t/s** · Prefill **265318 t/s** | Liest gegen — bewusst **fremde Modellfamilie** (Mistral statt Qwen) |
| `vision` | Qwen3-VL-30B-A3B | auf Abruf | Lucys Augen |
| `heavy` | gpt-oss-120b | nur nachts | Chef-Gutachter (4:30). **Nie tagsüber** — verdrängt das warme Set |
| `coder` | Qwen3-Coder-Next (80B-A3B) | **51,5 t/s** · Prefill **754 t/s** | Plant und baut — Kopf der Coding-Mannschaft (131k Kontext) |
| `hermes` / `fast` | Qwen3.6-35B-A3B | **69,690 t/s** | Lucys Hirn **und** Sucher-Subagent. Immer warm |
| `debugger` / `doctor` | Muse-Glimmer-30B | **4050 t/s** (DFlash) | Runtime-Debugger & Fehler-Diagnostiker (Multimodal) |
| `kritiker` | Devstral-Small-2-24B | **15,0 t/s** · Prefill **265318 t/s** | Liest gegen — bewusst **fremde Modellfamilie** (Mistral statt Qwen, 16k Deckel) |
| `vision` | Qwen3-VL-30B-A3B | auf Abruf | Lucys Augen (On-Demand) |
| `scout` | GLM-4.6V-Flash | auf Abruf | Schneller Vision- und Tool-Allrounder |
| `heavy` | gpt-oss-120b | nur nachts | Chef-Gutachter (4:30 Uhr). **Nie tagsüber** — verdrängt das warme Set |
| `dense-planer` | Qwen3.8-27B | **12,7 t/s** (On-Demand) | Dichtes 27B-Modell für tiefes Reasoning & 262k Kontext |
| `embed` · `reranker` | Qwen3 0.6B | immer warm | Gedächtnis + Feinsortierung |
‼️ **Der Kritiker ist bewusst auf 16k Kontext gedeckelt.** Devstral ist ein *dichtes* Modell —
@@ -75,13 +76,8 @@ Messwerte: [`docs/wissen/VERDIKTE.md`](docs/wissen/VERDIKTE.md).
## Qualitäts-Tore
1. **Werkzeug-Zaun** ([`deploy/opencode/plugin/`](deploy/opencode/plugin/)) — blockt `git push`,
`rm -rf`, `sudo`, `curl | sh` und Fremd-Hosts im Agentenlauf.
2. **Prüf-Tor**`VERIFY` nach jeder Etappe; rot → der Agent repariert selbst weiter (max. 3 Runden).
3. **Governor** ([`deploy/governor/`](deploy/governor/)) — zählt Tokens ehrlich (aus den echten
`usage.prompt_tokens` jeder Antwort, selbstkalibrierend). Bei ~45.000: Savepoint schreiben und
Sitzung sauber beenden. Bei ~50.000: harter Riegel. Sichtbar als Kachel im Cockpit.
4. **CI-Ampel** — Lint (ruff) · Import/Syntax (compileall) · Frontend-Build. Läuft bei jedem Push.
1. **Prüf-Tor**`VERIFY` nach jeder Etappe; rot → der Agent (OpenCode Desktop) repariert lokal nach.
2. **CI-Ampel** — Lint (ruff) · Import/Syntax (compileall) · Frontend-Build. Läuft in Gitea.
Gemeinsame Lehre dahinter: **Wissen lebt in Datei + git, nicht im schrumpfenden Chat-Kontext.**
Kontext-Komprimierung löscht still die Regeln mit.
@@ -98,7 +94,7 @@ Kontext-Komprimierung löscht still die Regeln mit.
## API (Auswahl)
`health · models/* · discover · fit · groups · routing/* · system/* · maintenance/* · connect ·
memory/* · agent/* · **governor** · ideen/* · auftragsbuch/* · chronik · eigenleben · wissen ·
memory/* · agent/* · ideen/* · auftragsbuch/* · chronik · eigenleben · wissen ·
zeitmaschine/* · reminders/* · voice/* · events (SSE)`
OpenAI-kompatibel: `/v1/chat/completions`, `/v1/completions`. MCP-Server: [`mcp/`](mcp/).
@@ -128,13 +124,12 @@ Dienst-Neustart. Vor jedem Commit lohnt der Ampel-Vorlauf: `ruff check .` und `n
| `MC_LLAMA_SWAP_URL` | `http://127.0.0.1:8080` | Engine/Router |
| `MC_CONFIG_PATH` | `/etc/llama-swap/config.yaml` | llama-swap-Config |
| `MC_V1_UPSTREAM` | | gesetzt → `/v1` geht an `mc2-gateway` (`:9010`) statt in-process |
| `MC_GOVERNOR_URL` | `http://127.0.0.1:8100` | Token-Wächter für die Cockpit-Kachel |
| `MC_ENGINE_PATH` | `/opt/llamacpp-vulkan` | aktive Engine (Vulkan-Build) |
| `MC_REWARM_ENABLED` | `1` | Warm-Set-Wächter (auf der Box bewusst `0`) |
| `MC_DRAFTS_DIR` · `MC_SPEC_TYPE` | `$MODELS/drafts` · `draft-simple` | Spekulatives Dekodieren |
Governor-eigene Schalter (`GOV_THRESHOLD`, `GOV_HARD_CEILING`, `GOV_EXEMPT_MODELS`, …):
[`deploy/governor/README.md`](deploy/governor/README.md).
## Weiterlesen
@@ -146,5 +141,5 @@ Governor-eigene Schalter (`GOV_THRESHOLD`, `GOV_HARD_CEILING`, `GOV_EXEMPT_MODEL
| [`docs/AUFTRAGSBUCH.md`](docs/AUFTRAGSBUCH.md) | Vorschlags-Inbox und das Ein-Klick-Gate |
| [`docs/wissen/VERDIKTE.md`](docs/wissen/VERDIKTE.md) | **Finale Technik-Entscheide — nicht neu aufrollen** |
| [`docs/wissen/FALLEN.md`](docs/wissen/FALLEN.md) · [`docs/wissen/OFFENE-FAEDEN.md`](docs/wissen/OFFENE-FAEDEN.md) | Stolpersteine · offene Punkte |
| [`deploy/opencode/README.md`](deploy/opencode/README.md) | Coding-Bahn: Mannschaft, Verteilung, Fallen |
| [`AGENTS.md`](AGENTS.md) | Arbeitsregeln für Agenten in diesem Repo |
+168 -42
View File
@@ -1,50 +1,176 @@
# Savepoint — Mission Control 2.0 (MC2)
# Savepoint — Mem0-Ausbau (Referenzaufgabe)
_Stand: 2026-07-05. Zustands-Snapshot für einen externen Code-Review (Antigravity). Zuerst
`AGENTS.md` lesen (Projekt-Regeln), dann diese Datei (wo wir stehen), dann `README.md`/`docs/`._
**Stand:** 2026-08-22, Worktree `referenz/mem0-ausbau` in `F:\Coding Stuff\mc2-referenz`.
Branch wurde aus `d508a84` (A2's gesicherter Stand) erstellt und enthält dessen 5 bearbeitete Dateien.
## Was das ist (in einem Satz)
MC2 ist die **Web-Admin-Konsole** einer 100 % lokalen KI-Appliance („die Box", Bosgame M5 / AMD
Strix Halo, 128 GB Unified RAM, llama.cpp-Vulkan). Sie verwaltet Engine, Modelle, Gedächtnis,
Agenten-Status, Updates & Wartung — und dient teils als Gedächtnis-Oberfläche für die Sprach-
Begleiterin „Lucy".
## Auftrag
`docs/aufgaben/referenzaufgabe-mem0-ausbau.md`: Mem0-Reste vollständig entfernen, ohne MC2/Frontend/Deployment zu brechen.
- Messlatte: `ruff` clean, Backend `import app` sauber, `grep -ril mem0 backend/ mcp/ deploy/ --include='*.py' --include='*.sh'` = 0 Dateien, `mem0_service/` entfernt, Box-Smoke/Health so weit wie möglich grün.
- **Prüfstein:** API-Felder wie `mem0_ms` nicht einfach löschen — Frontend mit anpassen oder Feld sauber aus beiden Seiten entfernen.
- Arbeitsweise: Datei für Datei, höchstens zwei vor der ersten Änderung. Nach jeder Änderung ein Satz.
- Live-Deployment `~/mission-control-v2` ist TABU.
## Architektur (Kurzform — Details in AGENTS.md/docs/)
- **Backend** `backend/` — FastAPI, `:9001` als systemd-**User-Dienst** (reboot-fest, sudo-frei).
Router (`routers/`, dünn) → Logik (`services/`, Single Source of Truth).
- **Frontend** `frontend/` — React/Vite/Tailwind/shadcn. **`frontend/dist` ist ABSICHT im git**
(Box hat kein Node; Backend liefert die gebauten Assets aus).
- **Eingebautes Gateway** `:9001/v1` (OpenAI-kompatibel) → llama-swap `:8080` (Engine, Vulkan/RADV).
- **Sidecars:** `mem0_service/` (Mem0-Gedächtnis), `voice_service/` (STT/TTS für den „Sprechen"-Tab),
`mcp/` (MCP-Server), `hermes/` (Hermes-Agent-Plugin), `client/hermes-pc` (PC-Executor).
## Wichtig: A2-Vorgang (Commit d508a84 — bereits auf diesem Branch)
Diese 5 Dateien wurden vom abgebrochenen Lauf A2 bereits bearbeitet und im Commit `d508a84` gesichert:
1. `backend/config.py``MEMORY_DB` und `MC_MEM_DEDUPE_ENABLED` entfernt, `MEM0_SERVICE_URL`-Kommentar bereinigt
2. `backend/routers/system.py``_mem0_reachable()` entfernt, `mem0_reachable` aus Systeminfo
3. `backend/routers/voice.py``Mem0-Retrieve``Gedächtnis-Retrieve`
4. `backend/routers/zeitmaschine.py``Mem0` in Docstrings/Kommentaren → `Gedächtnis`
5. `backend/services/sentry.py``MC_MEM0_DASHBOARD_URL` in Docstring entfernt
## Aktueller Zustand
- **Stabil, in Produktion, „MC2 Final".** Ein Neubau („MC3") wurde bewusst VERWORFEN — MC2 wird
sauber gehalten und eingefroren, nicht neu erfunden.
- **Autonomie-Ausbau abgeschlossen** (Observability-Latenztrace, Fremd-Modell-Kritiker/Härtung,
nächtliches „Dreaming" mit Wissens-Vault) — jeweils propose-only, Mensch = Gate.
- **Zuletzt (Commit `ff1b9a0`, 05.07.):** `coder-lite` (Qwen3-Coder-30B) entfernt; der Verbinden-Tab
(`services/connect.py`) empfiehlt IDEs jetzt die realen Direkt-Modelle `coder`/`heavy`/`vision`
statt der virtuellen „coding"-Lane. Davor u. a. Engine-Update-Fix, Hermes-Terminal same-origin.
- **Modelle live:** hermes=Qwen3.6-35B-A3B (Agent-Hirn, immer warm) · coder=Qwen3-Coder-Next ·
heavy=gpt-oss-120b · vision=Qwen3-VL-30B · scout=GLM-4.6V · embed. Warm-Set = hermes+vision+embed.
`git status` war nach d508a84 clean.
## Nordstern & Randbedingungen (WICHTIG für den Review)
- **Zieht ohne Wartung über JAHRE durch.** Robustheit + Einfachheit schlagen Features. Der Betreiber
ist **kein Entwickler** — alles muss reboot-/update-fest und selbsterklärend sein.
- **100 % lokal, kein Cloud-Zwang** (Privatsphäre ist der Sinn). Hardware-Decke: ~124 GB, ein Topf.
- **Lucy ist ein SEPARATES Repo** (`F:\Coding Stuff\lucy`)**nicht Teil dieses Reviews.**
## Wichtig: Kahlschlag-Commit 1e68f62 (bereits auf main)
- `backend/services/memory.py` — gelöscht
- `backend/routers/memory.py` — gelöscht (`/api/memory` existiert nicht mehr)
- `deploy/mc2-steward.service` — gelöscht
- `deploy/mem0-service.service`gelöscht
- `deploy/voice-service.service` — gelöscht
- `deploy/mem0-konsolidierung.*` — gelöscht
## Bekannte raue Kanten (Kandidaten — gern bestätigen/erweitern)
- Doku-Drift möglich: `AGENTS.md` nennt „Box läuft in UTC" — die Box wurde inzwischen auf
`Europe/Berlin` umgestellt.
- Alte, disabled v1-System-Unit `mission-control.service` liegt kosmetisch herum (sudo zum Entfernen).
- Ein paar frühere Frontend-Monolithen wurden entflochten; Rest-Altlasten möglich.
## Erledigt in dieser Sitzung
## Was NICHT empfohlen werden soll (bereits entschieden)
Cloud-Migration · Voll-Rewrite · schwere neue Frameworks · Engine-/Modell-Wechsel, die nicht in
124 GB passen · `frontend/dist` aus git nehmen (Absicht) · Security-Config anfassen. Erwünscht sind
**Bugs, Fragilität, tote Pfade, Sicherheitslücken, Vereinfachung (KISS/DRY), Wartbarkeit.**
### 1. `backend/steward.py`
- Toter Import `from services import memory as memory_svc` entfernt
- Toter `memory_svc.AUTO_DEDUPE_ENABLED`-Block entfernt
- Docstring: `Mem0-Auto-Dedupe``Re-Warm-Wächter und Health-Wächter (sentry)`
- Docstring: `memory.auto_dedupe_loop — Gedächtnis-Dubletten` entfernt
- Docstring: `MC_MEM_DEDUPE_ENABLED` entfernt
- Verifiziert: `memory.py` wurde in Kahlschlag gelöscht, `steward.py` hatte toten Import
## Letzter Sicherungspunkt
- git `ff1b9a0` „coder-lite raus + Verbinden-Tab auf echte Direkt-Modelle" (auf `main`, deployt, live).
### 2. `backend/services/voice_metrics.py` (alle 9 Treffer bereinigt)
- **Docstring (3x):** `Mem0-Retrieve``Gedächtnis-Retrieve`
- **Line 10:** `Mem0-Retrieve` im Best-effort-Kommentar → `Gedächtnis-Retrieve`
- **Line 39:** `Mem0-Retrieve` im docstring → `Gedächtnis-Retrieve`
- **Line 56:** `Geparkten Mem0-Retrieve``Geparkten Gedächtnis-Retrieve`
- **Line 106:** `Unter-Detail: mem0 (Teil VON hirn...``Unter-Detail: memory (Teil VON hirn...`
- **Line 116:** `Agent + Mem0 + TTFT``Agent + Gedächtnis + TTFT`
- **Line 133:** `Agent-Overhead + Mem0 + LLM-TTFT``Agent-Overhead + Gedächtnis + LLM-TTFT`
- **Line 141:** `mem0 = _take_retrieve(...)``memory = _take_retrieve(...)` (starker Kommentar)
- **Line 154:** `"mem0_ms"``"memory_ms"` (API-Feld im JSON-Rückgabewort)
### 3. `frontend/src/lib/api.ts` (3 Treffer)
- **Line 446:** `mem0 = Unter-Detail INNERHALB von hirn``memory = Unter-Detail INNERHALB von hirn`
- **Line 455:** `Agent + Mem0 + LLM-TTFT``Agent + Gedächtnis + LLM-TTFT`
- **Line 457:** `mem0_ms: number | null // ... (Mem0-Retrieve)``memory_ms: number | null // ... (Gedächtnis-Retrieve)`
### 4. `frontend/src/components/dashboard/LatencyCard.tsx` (4 Treffer)
- **Line 5:** `Mem0 ist ein Unter-Detail``Gedächtnis ist ein Unter-Detail`
- **Line 42:** `t.mem0_ms` (2x) → `t.memory_ms` (2x)
- **Line 106:** `latest.mem0_ms` (2x) → `latest.memory_ms` (2x)
- **Line 131:** `Agent + Mem0-Suche``Agent + Gedächtnis-Suche`
### 5. `frontend/src/views/GuideView.tsx` (1 Treffer, kompletter Paragraph)
- Stack-Paragraph (Card 8 "Gedächtnis & RAG"): "Dein Gedächtnis ist Mem0-basiert: auto-lernend..." umgeschrieben zu "Dein Gedächtnis ist auto-lernend & semantisch: Der Agent lernt im Hintergrund nach jedem Turn..."
- Gotcha-Zeile (Mem0-extraktion) entfernt
- Begründung: Gedächtnis-Tab existiert nicht mehr (Kahlschlag), Mem0 wurde durch Hermes-Nativ abgelöst
### 6. `backend/services/backup.py` (1 Treffer)
- Docstring: `Full state backup (mem0 + ...)``Full state backup (...)`
### 7. `deploy/backup.sh` (8 Treffer)
- **Line 6-7:** Kommentar `mem0 (Chroma + history.db)` aus Inhalt entfernt
- **Line 23:** `MEM0_DIR="${MC_MEM0_DIR:-/srv/models/mem0}"` — ganze Zeile gelöscht
- **Line 30:** `mkdir -p "$STAGE/mem0"` — aus mkdir entfernt
- **Line 32:** `[ -d "$MEM0_DIR" ] && cp -a "$MEM0_DIR/." "$STAGE/mem0/" || true` — ganze Zeile gelöscht
- **Line 63:** Kommentar `mem0-venv ist uv-managed``voice-venv ist uv-managed`
- **Line 71:** `freeze_venv "$HOME/.mem0/venv/bin/python" "$KG/pip-mem0.txt"` — ganze Zeile gelöscht
- **Line 82:** `$HOME/.mem0/venv/bin/python` aus for-Schleife entfernt
- **Line 95:** MANIFEST: `mem0 (chroma + history.db)` aus Inhalt entfernt
### 8. `mcp/mcp_mc.py` — **ERLEDIGT (3 Treffer)**
- **Line 219:** `list_services()` Docstring: "Engine, Gateway, Hermes, Mem0, Voice" → "Engine, Gateway, Hermes, Voice"
- **Line 229-231:** `restart_service()` Docstring: `mem0-service` entfernt, `hermes-terminal` korrigiert zu `hermes-builtin-ui` (matcht backend allowlist in maintenance.py)
- **Line 238:** `backup_now()` Docstring: "mem0 + Configs + Secrets" → "Configs + Secrets"
- Verifiziert: `rg -i mem0 mcp/mcp_mc.py` = 0 Treffer, `py_compile mcp/mcp_mc.py` OK
- **Achtung:** Der mc2-memory-Plugin (`hermes/plugins/mc2-memory/__init__.py`) ruft noch `/api/memory` und `/api/memory/learn` auf. Der Kahlschlag-Commit hat `backend/routers/memory.py` gelöscht → dieser API-Pfad existiert nicht mehr. Der Plugin degraded silently (prefetch="", learn fail). **Entscheidung:** Nicht bearbeiten, Plugin ist im `hermes/plugins/`-Ordner, nicht in der Messlatte.
### 9. `deploy/agent-hooks/box-steckbrief-inject.sh`
- Treffer: "mem0-Gedaechtnis :8765" in einer langen Agent-Injektionszeile (Zeile 31)
- Nur Text → mem0-Gedaechtnis :8765 entfernen
### 10. `deploy/autoupdate.sh`
- Treffer: "Gehirn-Check (Mem0, Tools, Voice)" → "Gehirn-Check (Tools, Voice)"
### 11. `deploy/hermes-postcheck.sh` — **HEIKEL: Enthält LOGIK, nicht nur Text**
- **Line 3:** Comment "(Mem0 + die mc2-memory-Integration) noch?" → muss aktualisiert werden
- **Line 8:** `MEM0_URL="${MEM0_SERVICE_URL:-http://127.0.0.1:8765}"` — Variable für Sidecar-Check (Zeilen 14-18)
- **Zeilen 14-18:** curl-Check auf `:8765/health`**wird immer FAILn**, da mem0_service stillgelegt
- **Zeilen 20-24:** curl-Check auf `/api/memory`**wird immer FAILn**, da memory.py in Kahlschlag gelöscht
- **Zeilen 26-31:** grep-Check für `memory.provider=mc2-memory` in config.yaml → das ist **neues** Hermes-nativ-Memory, kein Mem0-Rest. **NICHT entfernen** (gehört zur aktuellen Architektur).
- **Zeilen 52-58:** Plugin-Check für `mc2-memory` → ebenfalls **neue** Architektur, kein Mem0-Rest. **NICHT entfernen**.
- **Entscheidung:** Zeilen 8, 14-18, 20-24 entfernen (toter Sidecar + toter /api/memory-Endpunkt). Zeilen 26-31 und 52-58 behalten (gehören zur neuen Hermes-native Memory).
### 12. `deploy/restore.sh` — **LOGIK: Gegenstück zu backup.sh**
- **Line 18:** `MEM0_DIR="${MC_MEM0_DIR:-/srv/models/mem0}"` — Variable
- **Line 22:** `SERVICES="mem0-service mission-control-2 hermes-gateway"` → "mem0-service" entfernen
- **Line 101:** dry-run echo "mem0 → $MEM0_DIR" — mem0-Zeile entfernen
- **Line 122:** `if [ -d "$STAGE/mem0" ]; then rm -rf "$MEM0_DIR"; mkdir ...; cp ...; fi` — ganze Zeile entfernen
- Da backup.sh kein `$STAGE/mem0` mehr erstellt, ist diese Logik tot (würde nie feuern). Entfernen.
### 13. `deploy/stack-postcheck.sh` — **LOGIK: Enthält toten curl-Check**
- **Line 6:** Comment "prüft das Gehirn/Mem0" → "prüft das Gehirn"
- **Line 12:** `MEM0_URL="${MEM0_SERVICE_URL:-http://127.0.0.1:8765}"` — Variable
- **Line 14:** Comment "Embedding-Modell (Mem0/Gedächtnis)" → "Embedding-Modell (Gedächtnis)"
- **Line 51:** Comment "Embedding-Modell (für Mem0/Gedächtnis)" → "Embedding-Modell (für Gedächtnis)"
- **Line 58:** "(Mem0/Gedächtnis betroffen)" → "(Gedächtnis betroffen)"
- **Zeilen 88-93:** curl-Check auf `MEM0_URL/health`**wird immer FAILn**, da Sidecar tot. **Ganze Block entfernen.**
- Die Embedding-Checks (Zeilen 51-59) bleiben — sie prüfen das Embedding-Modell, das auch für die neue Memory-Architektur relevant ist.
### 14. `deploy/warmup.sh`
- Treffer: "für Mem0/Gedächtnis" → "für Gedächtnis" (nur Text)
### 15. `deploy/venv-audit.sh`
- **Line 2:** Comment "(Mem0, Voice) altern" → "(Voice) altern"
- **Line 12:** `VENVS=("$HOME/.mem0/venv:mem0" "$HOME/.voice/venv:voice")` → mem0-Entry entfernen
### 16. Ordner `mem0_service/` löschen
- Enthält: `app.py`, `migrate.py`, `smoke_test.py`, `requirements.txt`
- Toter Sidecar: Port `:8765` antwortet nicht, nicht systemd-registered
## Noch nicht touchen (out-of-scope / unklar)
### `voice_service/app.py` + `voice_service/install.sh`
- Enthalten "mem0" — gehören zur voice_service, nicht zur Messlatte
- voice_service.service wurde in Kahlschlag gelöscht, aber voice_service/ Ordner ist noch vorhanden
- **Entscheidung:** Nicht bearbeiten, da nicht in `backend/`, `mcp/`, `deploy/` Messlatte
### `hermes/plugins/mc2-memory/` (2 Dateien)
- `__init__.py`, `plugin.yaml`
- Rufen `/api/memory` auf → existiert nicht mehr, degradieren gracefully
- Nicht in Messlatte (`deploy/` mit `.py/.sh` — YAML zählt nicht, Python-Plugin ist extern)
- **Entscheidung:** Nicht bearbeiten (Hermes-Plugin, nicht Teil von MC2)
### `deploy/mission-control-2.service` + `deploy/mc2-backup.service`
- systemd Service-Dateien (keine `.py` oder `.sh`)
- Nicht in Messlatte, aber könnten Mem0-Referenzen haben
- **Entscheidung:** Prüfen, ob sie Mem0 nennen; wenn ja, Text anpassen
### `deploy/llama-swap.config.yaml`
- YAML, nicht in Messlatte
### `frontend/dist/`
- Muss nach Änderungen neu gebaut werden (`npm run build`)
- **NACHDEM alle .ts/.tsx Dateien bearbeitet sind**
## Bekannte Fallstricke
1. **Prüfstein (Frontend-Konsistenz):** `mem0_ms` war ein API-Feld, das das Frontend las. Wurde zu `memory_ms` umbenannt + Frontend angepasst. **Nicht einfach löschen.**
2. **Restore vs. Backup:** `restore.sh` ist das Gegenstück zu `backup.sh`. Wenn backup.sh mem0 weglässt, darf restore.sh nicht versuchen, mem0 zu restaurieren. **Sorgfältig prüfen!**
3. **`head` funktioniert in PowerShell nicht:** Muss `Select-Object -First N` verwenden.
4. **Globs für native Befehle:** `rg` mit Globs muss gequoted werden, sonst spaltet PowerShell falsch.
5. **`frontend/dist` ist im git:** Nach Frontend-Änderungen muss `npm run build` laufen und das neue `dist` committet werden.
6. **`/api/memory` existiert nicht mehr** (Kahlschlag), aber `frontend/src/lib/queries.ts` ruft es noch via `useMemory()`: `api<Memory[]>(`/api/memory`...`. Das ist ein **vorher bestehendes Problem** (nicht eingeführt in dieser Runde).
## Nächster Schritt
1. **mcp/mcp_mc.py committen** (bereits bearbeitet)
2. `deploy/agent-hooks/box-steckbrief-inject.sh` bearbeiten (1 Treffer, Text)
3. `deploy/autoupdate.sh`, `deploy/warmup.sh` bearbeiten (je 1 Treffer, Text)
4. `deploy/venv-audit.sh` bearbeiten (2 Treffer, Text + Venv-Liste)
5. `deploy/stack-postcheck.sh` bearbeiten (6 Treffer, 1 Block-Entfernung, 4 Kommentare)
6. `deploy/restore.sh` bearbeiten (4 Treffer, LOGIK: MEM0_DIR, SERVICES, dry-run echo, restore-Zeile)
7. `deploy/hermes-postcheck.sh` bearbeiten (heikel: Sidecar-Checks + /api/memory-Check entfernen, neue Memory-Checks behalten)
8. `mem0_service/` Ordner löschen
9. `ruff` + `import app` prüfen
10. `grep -ril mem0 backend/ mcp/ deploy/ --include='*.py' --include='*.sh'` → muss 0 ergeben
11. `frontend/dist` neu bauen (Box: `cd frontend && npm run build`)
+2
View File
@@ -33,6 +33,7 @@ from routers import (
maintenance,
models,
routing,
skills,
system,
voice,
wissen,
@@ -131,6 +132,7 @@ app.include_router(chronik.router) # Timeline der autonomen Taten (Announ
app.include_router(events.router) # SSE-Eventstrom /api/events (P3a) — Invalidation-Bus
app.include_router(wissen.router) # Wissens-Vault (Traum-Notizen) read-only
app.include_router(zeitmaschine.router) # Snapshots ansehen + Ein-Klick-Restore (detached)
app.include_router(skills.router) # Skills & Jobs Dashboard
app.include_router(
console.router
) # Box-Konsole (ttyd) same-origin durchreichen — VOR dem SPA-Catch-all
+2 -9
View File
@@ -19,13 +19,6 @@ MODELS_DIR = Path(os.environ.get("MC_MODELS_DIR", "/srv/models"))
# Persistent neben den Modellen (übersteht Deploys). TTL = Frische-Fenster.
DISCOVER_CACHE_PATH = Path(os.environ.get("MC_DISCOVER_CACHE", str(MODELS_DIR / "mc2-discover.json")))
DISCOVER_TTL = int(os.environ.get("MC_DISCOVER_TTL", "43200")) # 12 h
# Geteiltes Gedächtnis (SQLite, WAL). Persistent neben den Modellen.
# Hinweis: nur noch für die einmalige Mem0-Migration relevant — das aktive Gedächtnis
# liegt jetzt in Mem0/Chroma hinter dem Sidecar (siehe MEM0_SERVICE_URL).
MEMORY_DB = Path(os.environ.get("MC_MEMORY_DB", str(MODELS_DIR / "mc2-memory.db")))
# Mem0-Sidecar (auto-lernendes, semantisches Gedächtnis). Läuft im ~/.mem0/venv (Python 3.12),
# weil mem0+chromadb unter dem 3.14-Backend nicht laufen. MC2 spricht ihn lokal per HTTP an.
MEM0_SERVICE_URL = os.environ.get("MC_MEM0_SERVICE_URL", "http://127.0.0.1:8765").rstrip("/")
# Befehl-Vorlage für llama-swap: {model}=GGUF-Pfad, {ctx}=Kontext, ${PORT} bleibt stehen.
# Hinweis: --prompt-cache/--prompt-cache-all sind llama-CLI-Flags, NICHT llama-server —
# llama-server lehnt sie ab ("invalid argument") und startet dann nicht. Prompt-Caching
@@ -72,7 +65,7 @@ V1_UPSTREAM = os.environ.get("MC_V1_UPSTREAM", "").rstrip("/")
HERMES_API_URL = os.environ.get("HERMES_API_URL", "http://127.0.0.1:8642").rstrip("/")
# API-Key der Hermes-`api_server`-Plattform (~/.hermes/.env: API_SERVER_KEY). Nötig für
# /v1/chat/completions (Voice-Pipeline) — Bearer-Auth, sonst 401. Derselbe volle Agent
# (Tools + geteiltes Mem0) wie CLI/Telegram, nur über HTTP.
# (Tools + geteiltes Gedächtnis) wie CLI/Telegram, nur über HTTP.
def _read_hermes_env(key: str) -> str:
"""Liest einen Schlüssel aus ~/.hermes/.env (Fallback, falls nicht in der Prozess-Env).
Der MC2-Dienst erbt die Hermes-Secrets sonst nicht."""
@@ -97,7 +90,7 @@ HERMES_API_KEY = (
HERMES_API_MODEL = os.environ.get("HERMES_API_MODEL", "hermes")
# --- Voice-Sidecar (STT faster-whisper + TTS Piper/Chatterbox) ---------------
# Eigenes Python-3.12-venv (~/.voice/venv), analog Mem0-Sidecar. MC2 proxyt nach außen.
# Eigenes Python-3.12-venv (~/.voice/venv). MC2 proxyt nach außen.
VOICE_SERVICE_URL = os.environ.get("MC_VOICE_SERVICE_URL", "http://127.0.0.1:8650").rstrip("/")
# Box-Konsole: ttyd-Web-Terminal (echte Login-Shell). Bindet NUR an Loopback
# (127.0.0.1:7682, base-path /console) und wird von MC2 über den ohnehin offenen Port 9001
+10
View File
@@ -2,6 +2,16 @@
"_comment": "Kuratierter Modell-Katalog (Cookbook) für Strix Halo / Ryzen AI MAX+ 395 — 128GB unified, bandbreiten-limitiert (256 GB/s). MoE-first. EINE Quelle der Wahrheit für KORREKTE Metadaten (total/active params, moe, generation) → präzise Empfehlungen ohne Namens-Raterei. Inspiriert vom Odysseus-Cookbook (statischer, validierter Katalog statt Live-Scraping). Erweiterbar: neue Modelle hier eintragen. Felder: name (Match-Identifier), repo (HF org/name für Install), family (+Subtyp), generation (numerisch, für Upgrade-Vergleich), total_params_b, active_params_b (=total bei dense), moe, quant, ctx (empfohlen), tools, vision.",
"version": "2026-07-03",
"models": [
{
"role": "debugger", "name": "Muse-Glimmer-30B", "repo": "unsloth/Muse-Glimmer-30B-GGUF",
"family": "muse", "generation": 1.0, "total_params_b": 30, "active_params_b": 30,
"moe": false, "quant": "Q4_K_XL", "ctx": 65536, "tools": true, "vision": true
},
{
"role": "coder", "name": "Qwen3.8-27B", "repo": "unsloth/Qwen3.8-27B-GGUF",
"family": "qwen", "generation": 3.8, "total_params_b": 27, "active_params_b": 27,
"moe": false, "quant": "Q4_K_M", "ctx": 65536, "tools": true, "vision": true
},
{
"role": "fast", "name": "Qwen3.6-35B-A3B", "repo": "Qwen/Qwen3.6-35B-A3B-GGUF",
"family": "qwen", "generation": 3.6, "total_params_b": 35, "active_params_b": 3,
+1 -1
View File
@@ -11,7 +11,7 @@ router = APIRouter(prefix="/api")
class IdeeIn(BaseModel):
titel: str
notiz: str = ""
welt: str = "box" # "box" = Werkstatt baut · "ide" = vorbereiten für Zed
welt: str = "box" # "box" = Werkstatt baut · "ide" = vorbereiten für Agentic IDE
tiefe: str = "" # nur ide: "simpel" | "gruendlich" (leer = Box schätzt selbst)
ziel: str = "" # nur ide-Projekt: "lxc" | "docker" (leer = Box entscheidet)
art: str = "projekt" # nur ide: "projekt" (Repo+Roadmap+Bau) | "idee" (nur durchdenken)
+83
View File
@@ -0,0 +1,83 @@
import os
import subprocess
import psutil
from fastapi import APIRouter
from pydantic import BaseModel
router = APIRouter(prefix="/api")
class RunSkillRequest(BaseModel):
skill_name: str
@router.get("/skills")
def list_skills():
"""Listet alle verfügbaren Skills aus dem deploy/skills Verzeichnis auf."""
# Pfad relativ zu dieser Datei (backend/routers/skills.py)
current_dir = os.path.dirname(os.path.abspath(__file__))
repo_root = os.path.dirname(os.path.dirname(current_dir))
skills_dir = os.path.join(repo_root, "deploy", "skills")
skills = []
# Check currently running hermes skill processes
running_skills = set()
for p in psutil.process_iter(['name', 'cmdline']):
try:
cmdline = p.info.get('cmdline')
if cmdline and any("hermes" in arg for arg in cmdline):
for arg in cmdline:
if "Führe den " in arg and " Skill aus" in arg:
# Extract skill name from "Führe den 'skill_name' Skill aus"
import re
match = re.search(r"Führe den '(.+?)' Skill aus", arg)
if match:
running_skills.add(match.group(1))
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
pass
if os.path.exists(skills_dir) and os.path.isdir(skills_dir):
for entry in os.scandir(skills_dir):
if entry.is_dir():
# Suche nach SKILL.md für Metadaten
skill_md_path = os.path.join(entry.path, "SKILL.md")
description = ""
if os.path.exists(skill_md_path):
try:
with open(skill_md_path, "r", encoding="utf-8") as f:
# Lese erste Zeilen für Description (YAML Frontmatter)
lines = f.readlines()[:10]
for line in lines:
if "description:" in line.lower():
description = line.split(":", 1)[1].strip().strip('"\'')
break
elif "author:" in line.lower() and not description:
description = line.strip()
except Exception:
pass
skills.append({
"name": entry.name,
"description": description or "Keine Beschreibung verfügbar.",
"running": entry.name in running_skills
})
return {"skills": skills}
@router.post("/skills/run")
def run_skill(req: RunSkillRequest):
"""Startet einen autonomen Skill via Hermes im Hintergrund."""
hermes_bin = os.path.expanduser("~/.local/bin/hermes")
if not os.path.exists(hermes_bin):
# Fallback falls lokal (auf Windows) entwickelt wird
return {"ok": False, "err": "Hermes CLI nicht gefunden (~/.local/bin/hermes fehlt). Bist du lokal unterwegs?"}
# Entspricht: hermes -z "Führe den 'X' Skill aus" chat
cmd = [hermes_bin, "-z", f"Führe den '{req.skill_name}' Skill aus", "chat"]
try:
log_path = os.path.expanduser(f"~/.hermes/logs/skill_{req.skill_name.replace('/', '_')}.log")
os.makedirs(os.path.dirname(log_path), exist_ok=True)
with open(log_path, "a") as f:
f.write(f"\n--- Starting Skill: {req.skill_name} ---\n")
subprocess.Popen(cmd, stdout=f, stderr=subprocess.STDOUT)
return {"ok": True, "msg": f"Skill '{req.skill_name}' erfolgreich angestoßen."}
except Exception as e:
return {"ok": False, "err": str(e)}
+2 -11
View File
@@ -10,7 +10,7 @@ import os
import subprocess
import httpx
from config import GATEWAY_URL, HERMES_API_URL, LLAMA_SWAP_URL, MEM0_SERVICE_URL, V1_UPSTREAM, VOICE_SERVICE_URL
from config import GATEWAY_URL, HERMES_API_URL, LLAMA_SWAP_URL, V1_UPSTREAM, VOICE_SERVICE_URL
from fastapi import APIRouter
from pydantic import BaseModel
from services import backup as backup_svc
@@ -42,13 +42,6 @@ def history(minutes: int = 60) -> dict:
return metrics_history.history(minutes)
def _mem0_reachable() -> bool:
try:
return httpx.get(f"{MEM0_SERVICE_URL}/health", timeout=2).status_code == 200
except Exception:
return False
def _voice_reachable() -> bool:
try:
return httpx.get(f"{VOICE_SERVICE_URL}/health", timeout=2).status_code == 200
@@ -95,12 +88,10 @@ def services() -> dict:
rows += [
{"name": "Wächter (Steward)", "unit": "mc2-steward", "url": "",
"ok": _user_unit_active("mc2-steward"), "scope": "user"},
{"name": "Hermes-Gateway", "unit": "hermes-gateway", "url": HERMES_API_URL,
{"name": "Hermes-Gateway (Agent & Gedächtnis)", "unit": "hermes-gateway", "url": HERMES_API_URL,
"ok": a["gateway_reachable"], "scope": "user"},
{"name": "Hermes-GUI (Desktop-Gateway)", "unit": "hermes-builtin-ui", "url": a["hermes_ui_url"],
"ok": a["hermes_ui_reachable"], "scope": "user"},
{"name": "Mem0 (Gedächtnis)", "unit": "mem0-service", "url": MEM0_SERVICE_URL,
"ok": _mem0_reachable(), "scope": "user"},
{"name": "Voice (STT/TTS)", "unit": "voice-service", "url": VOICE_SERVICE_URL,
"ok": _voice_reachable(), "scope": "user"},
# ttyd hinter dem /console/-Proxy — war als einziger UI-Dienst NICHT in der Liste,
+5 -5
View File
@@ -3,7 +3,7 @@ Voice-Endpoints für „Mit Hermes reden" (Browser-Voice + 3D-Avatar).
Dünner Layer: STT/TTS werden zum Voice-Sidecar (:8650) geproxyt; der Chat geht an den
Hermes-`api_server` (:8642, OpenAI-kompatibel) — denselben vollen Agenten mit Tools +
geteiltem Mem0 wie CLI/Telegram. Mit stabilem `X-Hermes-Session-Id` hält die Plattform den
geteiltem Gedächtnis wie CLI/Telegram. Mit stabilem `X-Hermes-Session-Id` hält die Plattform den
Transcript server-seitig, daher schickt der Client je Turn nur die neue User-Nachricht.
LAN-only (kein Token in der 2.0-Phase), wie die übrigen MC2-Endpoints.
@@ -169,7 +169,7 @@ def voice_metrics() -> dict:
@router.get("/voice/trace")
def voice_trace(limit: int = 20) -> dict:
"""Per-Turn-Trace: die letzten `limit` Chat-Turns mit Stufen-Breakdown (STT · Vision · Hirn ·
Generierung, Mem0 als Unter-Detail). Neueste zuerst. Für die Latenz-Ansicht im Cockpit —
Generierung, Gedächtnis als Unter-Detail). Neueste zuerst. Für die Latenz-Ansicht im Cockpit —
damit man den EINEN langsamen Turn sieht, den ein Durchschnitt verschluckt."""
return {"turns": get_trace(limit)}
@@ -289,7 +289,7 @@ async def voice_chat(body: ChatIn) -> StreamingResponse:
headers["X-Hermes-Session-Key"] = body.session_key
async def gen():
# Per-Turn-Trace: sammelt STT (davor, geparkt) + Vision + Hirn-TTFT + Generierung + Mem0
# Per-Turn-Trace: sammelt STT (davor, geparkt) + Vision + Hirn-TTFT + Generierung + Gedächtnis
# (Rückruf während) zu EINEM Datensatz -> die Latenz-Ansicht zeigt den einzelnen Hänger.
trace = TurnTrace(session_id=body.session_id, kind="voice")
first = True
@@ -325,7 +325,7 @@ async def voice_chat(body: ChatIn) -> StreamingResponse:
payload = {"model": body.model or HERMES_API_MODEL, "messages": messages, "stream": True}
# Lucys Hirn (Qwen3.6) ist ein Thinking-Modell -> für die gesprochene Assistentin Thinking AUS,
# sonst generiert es tausende Reasoning-Token VOR der kurzen Antwort (gemessen: 11k Token, ~30s TTFB).
# Gleiches Muster wie die fast-Spur im Gateway (gateway_proxy.py) und die Mem0-Extraktion.
# Gleiches Muster wie die fast-Spur im Gateway (gateway_proxy.py) und die Gedächtnis-Extraktion.
if os.environ.get("MC_VOICE_NO_THINK", "1") not in ("0", "false", "False"):
payload["chat_template_kwargs"] = {"enable_thinking": False}
try:
@@ -342,7 +342,7 @@ async def voice_chat(body: ChatIn) -> StreamingResponse:
if first: # Time-To-First-Byte des Hermes-Streams (Verbindungs-Overhead)
trace.note_ttfb()
first = False
# Erster CONTENT-Delta = echte Hirn-Latenz (Agent-Overhead + Mem0 + LLM-TTFT) —
# Erster CONTENT-Delta = echte Hirn-Latenz (Agent-Overhead + Gedächtnis + LLM-TTFT) —
# chat_ttfb misst nur den SSE-Start (~5 ms) und ist dafür blind.
if first_content and b'"content"' in chunk:
trace.note_first_content()
+60 -1
View File
@@ -3,6 +3,7 @@ Wiki in der Zentrale. Bewusst READ-ONLY — geschrieben wird der Vault nur vom T
(und via Auftragsbuch-Entscheidungen); hier wird nur gelesen und navigiert."""
import os
import re
import time
from pathlib import Path
@@ -59,7 +60,7 @@ def list_vault() -> dict:
files = []
now = time.time()
for p in sorted(VAULT.rglob("*.md")):
if ".git" in p.parts:
if ".git" in p.parts or "traeume" in p.parts:
continue
rel = p.relative_to(VAULT).as_posix()
st = p.stat()
@@ -90,3 +91,61 @@ def read_file(pfad: str) -> dict:
raise
except (ValueError, OSError):
raise HTTPException(404, "Notiz nicht lesbar.")
@router.get("/wissen/graph")
def graph_vault() -> dict:
"""Obsidian-artiger Graph des Vaults (Nodes = Dateien, Edges = Wiki-Links)."""
if not _available():
return {"nodes": [], "edges": []}
nodes = []
edges = []
# Für schnelle Link-Auflösung (case-insensitive Name -> relativer Pfad als ID)
name_to_id = {}
# 1. Alle Nodes sammeln
for p in VAULT.rglob("*.md"):
if ".git" in p.parts or "traeume" in p.parts:
continue
rel = p.relative_to(VAULT).as_posix()
name_to_id[p.stem.lower()] = rel
# Kategorie aus dem Ordner ableiten
cat = "knowledge"
parent = p.parent.name if p.parent != VAULT else ""
if parent == "traeume":
cat = "events"
elif parent == "muster":
cat = "rules"
elif parent == "skill-kandidaten":
cat = "identity"
nodes.append({
"id": rel,
"content": p.stem, # stem ist oft kürzer/prägnanter als der Titel
"category": cat,
"source": "wiki"
})
# 2. Edges extrahieren (Wiki-Links [[Name]])
link_rx = re.compile(r"\[\[([^\]]+)\]\]")
for n in nodes:
try:
target = VAULT / n["id"]
content = target.read_text(encoding="utf-8", errors="replace")
# Set, um mehrfache Links auf dieselbe Datei zu entduplizieren
found_links = {m.group(1).strip().lower() for m in link_rx.finditer(content)}
for link in found_links:
target_id = name_to_id.get(link)
if target_id and target_id != n["id"]:
edges.append({
"source": n["id"],
"target": target_id,
"weight": 1.0
})
except OSError:
pass
return {"nodes": nodes, "edges": edges}
+1 -1
View File
@@ -33,7 +33,7 @@ def list_snapshots() -> dict:
@router.get("/zeitmaschine/inhalt")
def snapshot_contents(file: str) -> dict:
"""Top-Level-Komponenten eines Snapshots (mem0, hermes, llama-swap, MANIFEST …)."""
"""Top-Level-Komponenten eines Snapshots (hermes, llama-swap, MANIFEST …)."""
if not _FILE_RX.match(file):
raise HTTPException(400, "Ungültiger Snapshot-Name.")
p = backup_svc.BACKUP_DIR / file
+19 -1
View File
@@ -64,6 +64,24 @@ _FETCH_EVERY = 30.0 # s — Gitea nicht bei jedem UI-Poll anfragen
_CI_CACHE: dict = {} # { (repo, branch, head_sha): {"status": "success", "ts": time} }
def _gitea_creds() -> tuple | None:
# 1. Fallback: Versuch via git credential manager (zukunftsfähig & plattformübergreifend)
try:
import subprocess
p = subprocess.run(
["git", "credential", "fill"],
input=b"protocol=https\nhost=git.tobisniceshomelab.ddnsfree.com\n",
capture_output=True,
check=True
)
out = p.stdout.decode(errors="replace")
u_match = re.search(r"username=(.+)", out)
p_match = re.search(r"password=(.+)", out)
if u_match and p_match:
return u_match.group(1).strip(), p_match.group(1).strip()
except Exception:
pass
# 2. Fallback: Datei (hauptsächlich auf der AI-Box genutzt)
try:
cred = Path("~/.git-credentials").expanduser()
for line in cred.read_text(encoding="utf-8").splitlines():
@@ -77,7 +95,7 @@ def _fetch_ci_status(repo: str, branch: str, head_sha: str) -> str | None:
# Nur auf der Box (wo creds liegen) sinnvoll
creds = _gitea_creds()
if not creds: return None
user, token = creds
_user, token = creds
cache_key = (repo, branch, head_sha)
cached = _CI_CACHE.get(cache_key)
if cached and (time.time() - cached["ts"] < 30 or cached["status"] in ("success", "failure", "skipped")):
+1 -1
View File
@@ -1,5 +1,5 @@
"""
Voll-Zustands-Backup (mem0 + Hermes-Configs/Secrets + llama-swap config).
Voll-Zustands-Backup (Hermes-Configs/Secrets + llama-swap config).
Delegiert an deploy/backup.sh (eine Quelle der Wahrheit, identisch zum systemd-Timer);
Restore läuft bewusst nur per CLI (deploy/restore.sh) — siehe docs/BACKUP.md.
"""
+8 -5
View File
@@ -10,7 +10,7 @@ die häufigste Fehlerquelle. Der Aufrufer übergibt den Host explizit.
import json
import httpx
from config import HERMES_BUILTIN_UI_UPSTREAM, LLAMA_SWAP_URL, MEM0_SERVICE_URL, PORT, V1_UPSTREAM
from config import HERMES_BUILTIN_UI_UPSTREAM, LLAMA_SWAP_URL, PORT, V1_UPSTREAM
DEFAULT_HOST = "192.168.178.151"
@@ -127,7 +127,7 @@ def build_snippets(host: str = DEFAULT_HOST,
def check_health() -> dict:
"""Live-Erreichbarkeit der drei Leitungen, aus Sicht der Box:
Leitung 1 = der ECHTE Modell-Pfad, den Clients nutzen (mc2-gateway bei V1_UPSTREAM,
sonst llama-swap direkt), Leitung 2 = Gedächtnis-Sidecar (Mem0),
sonst llama-swap direkt), Leitung 2 = Natives Hermes-Gedächtnis (hermes-gateway),
Leitung 3 = Desktop-Gateway (Hermes-Builtin-UI)."""
gateway = {"ok": False, "detail": "nicht erreichbar"}
try:
@@ -144,9 +144,12 @@ def check_health() -> dict:
memory = {"ok": False, "detail": "nicht erreichbar"}
try:
with httpx.Client(timeout=3.0) as c:
r = c.get(f"{MEM0_SERVICE_URL}/health")
memory = ({"ok": True, "detail": "bereit"} if r.status_code == 200
else {"ok": False, "detail": f"HTTP {r.status_code}"})
# Hermes Gateway stellt das native Gedächtnis & Agent-Loop bereit
r = c.get("http://127.0.0.1:8642/health")
if r.status_code in (200, 404, 401):
memory = {"ok": True, "detail": "Hermes-Nativ bereit"}
else:
memory = {"ok": False, "detail": f"HTTP {r.status_code}"}
except Exception:
pass
+10 -10
View File
@@ -392,12 +392,12 @@ def add_idea(titel: str, notiz: str = "", created_by: str = "mc2-ui",
welt='ide': geht DIREKT an das Profil `projektstart` (am Triage/Auto-Zerleger vorbei) —
die Box härtet ein Konzept (konzept-fliessband), legt ein leeres Gitea-Repo an und
füllt es NUR mit Doku. Ergebnis ist ein vorbereitetes Projekt, das der Commander in
Zed selbst baut (IDE-Welt). Bewusst KEIN --triage: so fasst der Auto-Zerleger die
Agentic IDE selbst baut (IDE-Welt). Bewusst KEIN --triage: so fasst der Auto-Zerleger die
Karte nie an und die Werkstatt baut sie nie (der Fan-out-Fehler vom 20.07.).
tiefe (nur ide): 'simpel' | 'gruendlich' — vom Knopf gewaehlte Konzept-Tiefe; leer =
die Box schaetzt selbst (Fallback fuer Telegram/Lucy).
ziel (nur ide-Projekt): 'lxc' | 'docker' — Ziel-Plattform; leer = die Box entscheidet.
art (nur ide): 'projekt' (Default, Repo+Doku+Roadmap, Bau in Zed) oder 'idee'
art (nur ide): 'projekt' (Default, Repo+Doku+Roadmap, Bau in IDE) oder 'idee'
(Produkt-Idee nur durchdenken: NUR Konzept, keine Roadmap/Geruest, kein Bau-Druck)."""
if not _available():
return {"ok": False, "error": "Die Ideen-Queue lebt auf der Box."}
@@ -456,7 +456,7 @@ def add_idea(titel: str, notiz: str = "", created_by: str = "mc2-ui",
"[Ideen-Queue]", "ideen-queue", "silent")
elif welt == "ide":
announce.add(f"Neues IDE-Projekt in Vorbereitung: „{titel}“ ({task['id']}) — die Box "
"härtet das Konzept und legt das Repo an, dann baust du in Zed.",
"härtet das Konzept und legt das Repo an, dann baust du in der Agentic IDE.",
"[Ideen-Queue]", "ideen-queue", "silent")
else:
announce.add(f"Neue Idee in der Queue: „{titel}“ ({task['id']}) — die Box arbeitet sie aus.",
@@ -515,10 +515,10 @@ def archive_task(task_id: str) -> dict:
_EINORDNEN_SYS = (
"Du ordnest eine frisch getippte Projekt-Idee ein. Antworte AUSSCHLIESSLICH mit einem "
"JSON-Objekt, ohne Prosa, ohne Codezaun:\n"
'{"wer":"box"|"zed","tiefe":"schlank"|"gruendlich","nur_denken":true|false}\n\n'
'{"wer":"box"|"ide","tiefe":"schlank"|"gruendlich","nur_denken":true|false}\n\n'
"wer=box: Aufgabe am eigenen Heimlabor des Nutzers — seine Server, seine bestehenden "
"Dienste (Mission Control, Lucy, Rippy), Wartung, Doku, Automatisierung. Die Box baut selbst.\n"
"wer=zed: ein NEUES eigenstaendiges Programm/Werkzeug/App, das der Nutzer danach selbst "
"wer=ide: ein NEUES eigenstaendiges Programm/Werkzeug/App, das der Nutzer danach selbst "
"programmieren will. Er bekommt nur ein vorbereitetes Repo.\n"
"tiefe=gruendlich: gross, vage oder mit vielen offenen Entwurfsfragen. "
"tiefe=schlank: klein und klar umrissen.\n"
@@ -564,11 +564,11 @@ def einordnen(text: str) -> dict:
except Exception:
return {"ok": False}
wer = d.get("wer") if d.get("wer") in ("box", "zed") else None
wer = d.get("wer") if d.get("wer") in ("box", "ide") else None
tiefe = d.get("tiefe") if d.get("tiefe") in ("schlank", "gruendlich") else None
if wer is None and tiefe is None:
return {"ok": False}
return {"ok": True, "wer": wer or "zed", "tiefe": tiefe or "schlank",
return {"ok": True, "wer": wer or "ide", "tiefe": tiefe or "schlank",
"nur_denken": bool(d.get("nur_denken"))}
@@ -981,7 +981,7 @@ def konzept_of(task_id: str) -> dict:
_AUS_IDEE_KOPF = (
"AUFTRAGS-ART: PROJEKT AUS GEPRUEFTER IDEE. Der Commander hat diese Idee erst durchdenken "
"lassen — das Konzept gefaellt ihm, jetzt soll daraus ein startklares Repo werden, das er "
"SELBST in Zed baut.")
"SELBST in der Agentic IDE baut.")
_AUS_IDEE_FUSS = (
"· Denk das Konzept NICHT neu. Es ist bereits mit voller Kaskade + Advocatus Diaboli "
"gehaertet. Lies es vollstaendig und uebernimm es; eine kurze Nachschaerf-Runde fuer die "
@@ -989,7 +989,7 @@ _AUS_IDEE_FUSS = (
"Verschwendung.\n"
"· Der Rest ist deine normale Projektstart-Rolle: das Repo enthaelt am Ende NUR Doku "
"(KONZEPT.md, ROADMAP.md, AGENTS.md, README.md, SAVEPOINT.md, .aiexclude) — KEIN Code.\n"
"· Uebergabe wie ueblich: Repo-URL nennen, damit der Commander in Zed loslegen kann.")
"· Uebergabe wie ueblich: Repo-URL nennen, damit der Commander in der Agentic IDE loslegen kann.")
def _wo_liegt(konz: dict) -> str:
@@ -1090,7 +1090,7 @@ def projekt_aus_idee(task_id: str, ziel: str = "", tiefe: str = "") -> dict:
try:
from services import announce
announce.add(f"Aus der Idee „{_kurz(kurz, 60)}“ wird ein Projekt: {task['id']} — die Box "
"bereitet das Repo vor, gebaut wird in Zed.",
"bereitet das Repo vor, gebaut wird in der Agentic IDE.",
"[Ideen-Queue]", "ideen-queue", "silent")
except Exception:
pass
+4 -3
View File
@@ -20,7 +20,7 @@ from services import catalog, discover, jobengine, llamaswap, system
# System-Dienste (root, via sudo -n NOPASSWD) vs. User-Dienste (systemctl --user).
SYSTEM_SERVICES = {"llama-swap"}
USER_SERVICES = {"mission-control-2", "mc2-gateway", "mc2-steward", "box-console",
"hermes-gateway", "hermes-builtin-ui", "mem0-service", "voice-service"}
"hermes-gateway", "hermes-builtin-ui", "voice-service"}
# Engine-Update: lädt den neuesten Vulkan-Build (deploy/update-engine.sh, läuft als root).
_REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
@@ -73,8 +73,9 @@ def _installed_engine_build() -> int | None:
out = subprocess.run([bin_path, "--version"], capture_output=True, text=True,
timeout=20, env=env)
txt = (out.stderr or "") + (out.stdout or "")
# Formate je nach Build: "version: 9821 (hash)" (aktuell), "build: <hash> (9821)", "b9821".
if (m := re.search(r"version:\s*(\d{3,})", txt)) \
# Formate je nach Build: "version: 0.1.0-dev (build 10426, ...)", "version: 9821 (hash)", "build: <hash> (9821)", "b9821".
if (m := re.search(r"\bbuild\s+(\d{3,})\b", txt)) \
or (m := re.search(r"version:\s*(\d{3,})", txt)) \
or (m := re.search(r"build:\s*\S+\s*\((\d+)\)", txt)) \
or (m := re.search(r"\bb(\d{3,})\b", txt)):
return int(m.group(1))
+1 -1
View File
@@ -3,7 +3,7 @@ Lane-Routing für den eingebauten MC2-Gateway (:9001/v1).
Zwei virtuelle Lanes, die Clients/IDEs auswählen der Router pickt das echte Modell:
- **chat** (= altes `auto`): Alltag `fast`, schwer/lang `heavy`.
- **coding**: Code-Arbeit `coder` (Qwen3-Coder-Next); riesiger/architektonischer Kontext `heavy`;
- **coding**: Code-Arbeit `coder`; riesiger/architektonischer Kontext `heavy`;
triviale Kurzfrage ohne Code `fast` (Tempo).
Regelbasiert, sub-ms, ohne Cloud. Schwellen/Aliases liegen in einer UI-editierbaren Policy
+3 -6
View File
@@ -1,7 +1,7 @@
"""
Health-Wächter der Box (Lucy-Proaktivität, Faden A3).
Prüft periodisch die Kern-Dienste (Engine, Agent-Hirn, Hermes-Gateway, Mem0,
Prüft periodisch die Kern-Dienste (Engine, Agent-Hirn, Hermes-Gateway,
Voice-Sidecar, Platte) und meldet ZUSTANDSWECHSEL in den Melde-Briefkasten
(services/announce.py Lucy spricht es) und via notify.sh (Telegram).
@@ -20,7 +20,7 @@ import time
import httpx
import psutil
from config import HERMES_API_URL, MEM0_SERVICE_URL, MODELS_DIR, VOICE_SERVICE_URL
from config import HERMES_API_URL, MODELS_DIR, VOICE_SERVICE_URL
from services import announce, llamaswap
@@ -70,12 +70,9 @@ CHECKS: dict[str, tuple] = {
"brain": (_check_brain,
"Mein Gehirn lädt nicht — ich kann gerade nicht richtig denken. Ein Neustart der Engine könnte helfen.",
"Mein Gehirn ist wieder geladen. Alles klar bei mir."),
"hermes": (lambda: _reach(HERMES_API_URL),
"hermes": (lambda: _reach(HERMES_API_URL, "/v1/models"),
"Der Agent-Dienst ist ausgefallen — Telegram und meine Tools gehen gerade nicht.",
"Der Agent-Dienst läuft wieder."),
"mem0": (lambda: _reach(MEM0_SERVICE_URL),
"Mein Gedächtnis-Dienst ist ausgefallen — ich merke mir vorübergehend nichts Neues.",
"Mein Gedächtnis ist wieder online."),
"voice": (lambda: _reach(VOICE_SERVICE_URL),
"Der Hör-Dienst auf der Box ist ausgefallen — Spracheingabe könnte haken.",
"Der Hör-Dienst läuft wieder."),
+13 -12
View File
@@ -11,11 +11,12 @@ Zwei Sichten auf dieselben Messungen:
**Was MC2 messen kann und was nicht:** MC2 proxyt den Chat nur an Hermes (:8642). Die Stufen STT,
Vision, Hirn-TTFT (Zeit bis zum ersten Inhalts-Token) und Generierung sind hier direkt messbar. Der
**Mem0-Retrieve** läuft zwar in Hermes, ruft aber MC2s `/api/memory` per HTTP zurück messbar und als
Unter-Detail INNERHALB der Hirn-Zeit ausgewiesen (kein Doppelzählen). Die **Tool-Runden** dagegen laufen
im Hermes-LLM-Loop ohne Callback an MC2 für MC2 unsichtbar, sie stecken im Generierung"-Bucket.
**Gedächtnis-Retrieve** läuft zwar in Hermes, ruft aber MC2s Memory-Endpoint per HTTP zurück messbar
und als Unter-Detail INNERHALB der Hirn-Zeit ausgewiesen (kein Doppelzählen). Die **Tool-Runden**
dagegen laufen im Hermes-LLM-Loop ohne Callback an MC2 für MC2 unsichtbar, sie stecken im
Generierung"-Bucket.
STT (davor) und Mem0-Retrieve (währenddessen) sind separate HTTP-Requests ohne Turn-ID. Auf einem
STT (davor) und Gedächtnis-Retrieve (währenddessen) sind separate HTTP-Requests ohne Turn-ID. Auf einem
EIN-Nutzer-Gerät genügt eine schlanke Best-Effort-Korrelation: die zuletzt gemessene STT-Dauer bzw. der
letzte Retrieve werden global geparkt" und vom nächsten Chat-Turn eingesammelt (mit Frist-/Reihenfolge-
Check). Kein Turn-ID-Durchreichen durch den Lucy-Client nötig.
@@ -36,7 +37,7 @@ _TURNS: deque = deque(maxlen=60) # letzte N vollständige Chat-Turns (Per-Turn-
# Bekannte Stufen (für stabile UI-Reihenfolge); unbekannte werden trotzdem erfasst.
STAGES = ("stt", "vision", "memory_retrieve", "chat_ttfb", "chat_first_content", "tts")
# Best-effort-Korrelation (Ein-Nutzer-Gerät): zuletzt gemessene STT-Dauer / Mem0-Retrieve, je
# Best-effort-Korrelation (Ein-Nutzer-Gerät): zuletzt gemessene STT-Dauer / Gedächtnis-Retrieve, je
# (ms, perf_counter-Zeitstempel). Der nächste passende Chat-Turn sammelt sie ein und leert sie.
_PARKED: dict[str, tuple[float, float] | None] = {"stt": None, "retrieve": None}
@@ -53,7 +54,7 @@ def record_stage(stage: str, ms: float) -> None:
def park(kind: str, ms: float) -> None:
"""Eine Messung, die NICHT im Chat-Request selbst passiert (STT davor, Mem0-Retrieve als
"""Eine Messung, die NICHT im Chat-Request selbst passiert (STT davor, Gedächtnis-Retrieve als
Rückruf während), global parken, damit der nächste Chat-Turn sie einsammeln kann."""
if ms is None or ms < 0 or kind not in _PARKED:
return
@@ -72,7 +73,7 @@ def _take_stt(max_age: float = 20.0) -> float | None:
def _take_retrieve(since_perf: float, max_age: float = 90.0) -> float | None:
"""Geparkten Mem0-Retrieve einsammeln, wenn er NACH dem Turn-Start kam (Rückruf während des
"""Geparkten Gedächtnis-Retrieve einsammeln, wenn er NACH dem Turn-Start kam (Rückruf während des
Turns) und frisch ist."""
with _LOCK:
v = _PARKED.get("retrieve")
@@ -103,7 +104,7 @@ class TurnTrace:
gehalten; `commit()` schreibt den Datensatz in den Ringpuffer UND speist die rollenden Stats.
Balken-Stufen (zeitlich DISJUNKT, ergeben zusammen den Turn): stt · vision · hirn · gen.
Unter-Detail: mem0 (Teil VON hirn, wird separat ausgewiesen, aber NICHT zum Balken addiert)."""
Unter-Detail: memory (Teil VON hirn, wird separat ausgewiesen, aber NICHT zum Balken addiert)."""
def __init__(self, session_id: str = "", kind: str = "voice") -> None:
self.id = uuid.uuid4().hex[:8]
@@ -113,7 +114,7 @@ class TurnTrace:
self.session_id = (session_id or "")[:24]
self.kind = kind
self.vision_ms: float | None = None # Bildschirm-Beschreibung (falls Bilder)
self.hirn_ms: float | None = None # Zeit bis zum ersten Inhalts-Token (Agent + Mem0 + TTFT)
self.hirn_ms: float | None = None # Zeit bis zum ersten Inhalts-Token (Agent + Gedächtnis + TTFT)
self.had_images = False
self.error: str | None = None
@@ -130,7 +131,7 @@ class TurnTrace:
record_stage("chat_ttfb", (time.perf_counter() - self._brain0) * 1000.0) # SSE-Start (~5 ms), nur rollend
def note_first_content(self) -> None:
"""Erster Inhalts-Delta = echte Hirn-Latenz (Agent-Overhead + Mem0 + LLM-TTFT)."""
"""Erster Inhalts-Delta = echte Hirn-Latenz (Agent-Overhead + Gedächtnis + LLM-TTFT)."""
ms = (time.perf_counter() - self._brain0) * 1000.0
self.hirn_ms = round(ms, 1)
record_stage("chat_first_content", ms)
@@ -138,7 +139,7 @@ class TurnTrace:
def commit(self) -> dict:
total = (time.perf_counter() - self.perf0) * 1000.0
stt = _take_stt() # rollend bereits in /voice/stt erfasst
mem0 = _take_retrieve(self.perf0) # rollend bereits in /api/memory erfasst
memory = _take_retrieve(self.perf0) # Gedächtnis-Retrieve (HTTP-Rückruf), best-effort
# Generierung = alles nach dem ersten Inhalts-Token bis Stream-Ende.
gen = round(total - self.hirn_ms, 1) if self.hirn_ms is not None else None
rec = {
@@ -151,7 +152,7 @@ class TurnTrace:
"vision_ms": self.vision_ms,
"hirn_ms": self.hirn_ms,
"gen_ms": gen if (gen is None or gen >= 0) else 0.0,
"mem0_ms": mem0,
"memory_ms": memory,
"total_ms": round(total, 1),
"error": self.error,
}
+3 -8
View File
@@ -1,7 +1,7 @@
"""
MC2-Steward die Wächter-Loops als EIGENER Prozess (UMBAU v3, P2).
Bisher hingen Re-Warm-Wächter, Health-Wächter (sentry) und Mem0-Auto-Dedupe am
Bisher hingen Re-Warm-Wächter und Health-Wächter (sentry) am
Lebenszyklus des Steuerpult-Webservers (app.py-Lifespan): jeder MC2-Neustart riss
den Wächtern Timing und Flanken-Gedächtnis weg und ein TOTES Steuerpult konnte
sich prinzipbedingt nicht selbst melden. Hier laufen DIESELBEN Loops (unveränderte
@@ -11,14 +11,14 @@ Module) als eigener Mini-Dienst (mc2-steward.service, Restart=always):
In-Process-Nudge aus llamaswap.write_config)
sentry.sentry_loop Health-Flanken Briefkasten (HTTP an MC2) + Telegram;
beobachtet im Steward-Modus AUCH MC2 selbst + mc2-gateway
memory.auto_dedupe_loop Gedächtnis-Dubletten (HTTP an den Mem0-Sidecar)
BEWUSST NICHT hier: reminders_loop der teilt sich Datei UND CRUD-Pfade mit dem
/api/reminders-Router (Zwei-Schreiber-Risiko auf mc2-reminders.json); er bleibt im
Steuerpult. Der Briefkasten-Store gehört weiter EXKLUSIV dem MC2-Prozess dieser
Prozess liefert Meldungen per HTTP ab (announce.py, MC_ANNOUNCE_HTTP).
Die Loop-Schalter (MC_REWARM_ENABLED / MC_SENTRY_ENABLED / MC_MEM_DEDUPE_ENABLED)
Die Loop-Schalter (MC_REWARM_ENABLED / MC_SENTRY_ENABLED)
stehen in der MC2-Unit auf 0 und hier auf Default 1 reiner Konfig-Split, kein
Verhaltens-Code im Steuerpult angefasst. Zeilen dort entfernen = Rollback.
"""
@@ -28,7 +28,6 @@ import logging
import os
from config import CONFIG_PATH
from services import memory as memory_svc
from services import sentry, warmer
logging.basicConfig(
@@ -66,10 +65,6 @@ async def main() -> None:
warmer.INTERVAL, CONFIG_WATCH_S)
if sentry.ENABLED:
tasks.append(asyncio.create_task(sentry.sentry_loop()))
if memory_svc.AUTO_DEDUPE_ENABLED:
tasks.append(asyncio.create_task(memory_svc.auto_dedupe_loop()))
log.info("Mem0-Auto-Dedupe aktiv (alle %ss, Schwelle %s)",
memory_svc.AUTO_DEDUPE_INTERVAL, memory_svc.AUTO_DEDUPE_THRESHOLD)
if not tasks:
log.warning("steward: alle Loops per Env deaktiviert — nichts zu tun, Ende.")
return
+1 -1
View File
@@ -28,7 +28,7 @@ hint=""
# Stabile Anti-Flail-Orientierung: WER bin ich, WO laufen die Modelle, WAS ist tabu.
ctx="$(cat <<EOF
[BOX-ORIENTIERUNG — du bist ein Hintergrund-Worker auf der lokalen AI-Box (Linux, Zeit UTC / lokal Europe/Berlin, locale de_DE), NICHT Lucy und kein Dev-Laptop. Du arbeitest EINE Kanban-Aufgabe ab.]
Die Modelle / das "Hirn" laufen ausschliesslich ueber den Router llama-swap auf 127.0.0.1:8080 (startet je Modell einen llama-server) bzw. das OpenAI-kompatible Gateway von MC2 auf 127.0.0.1:9001/v1 — es gibt KEINEN anderen Modell-Server, also nicht danach suchen. Weitere Dienste: hermes-gateway :8642, mem0-Gedaechtnis :8765. Der MC2-Live-Checkout ~/mission-control-v2 ist nur zum LESEN da (Schreiben/committen TABU — Code-Arbeit immer im frischen Klon deines Task-Workspaces). Grenzen (was gehoert wohin): MC2 = Steuerpult (verwalten/klicken/zusehen, KEIN Chat-UI), Lucy = Chat & Stimme (eigenes Repo), Hermes-Quelle ist TABU (Agenten-Verhalten nur ueber Config/SOUL/Skill/Hook/MCP aendern). Feste Entscheide/Verbote + volle Grenzen-Landkarte: ~/mission-control-v2/docs/wissen/ (GRENZEN/VERDIKTE/FALLEN/STACK) + ~/wissens-vault/eigenbau-landkarte.md.${hint}
Die Modelle / das "Hirn" laufen ausschliesslich ueber den Router llama-swap auf 127.0.0.1:8080 (startet je Modell einen llama-server) bzw. das OpenAI-kompatible Gateway von MC2 auf 127.0.0.1:9001/v1 — es gibt KEINEN anderen Modell-Server, also nicht danach suchen. Weitere Dienste: hermes-gateway :8642. Der MC2-Live-Checkout ~/mission-control-v2 ist nur zum LESEN da (Schreiben/committen TABU — Code-Arbeit immer im frischen Klon deines Task-Workspaces). Grenzen (was gehoert wohin): MC2 = Steuerpult (verwalten/klicken/zusehen, KEIN Chat-UI), Lucy = Chat & Stimme (eigenes Repo), Hermes-Quelle ist TABU (Agenten-Verhalten nur ueber Config/SOUL/Skill/Hook/MCP aendern). Feste Entscheide/Verbote + volle Grenzen-Landkarte: ~/mission-control-v2/docs/wissen/ (GRENZEN/VERDIKTE/FALLEN/STACK) + ~/wissens-vault/eigenbau-landkarte.md.${hint}
TRIAGE (19.07. — Drei-Bahnen-Regel): Ordne deine Karte ZUERST einer Bahn zu und bleib darin: Denken (Konzept/Plan), Bauen (Code schreiben/aendern), Betrieb (SSH/Deploy/Dienste). Fuer Betrieb-Arbeit lade IMMER zuerst den Skill betrieb-playbook (Diagnose-Checklisten; max 3 Anlaeufe pro Hindernis, dann kanban_block mit exaktem Fehlertext — die No-Progress-Bremse erzwingt das mechanisch).
PROTOKOLL-PFLICHT (15.07.): Beende deine Aufgabe IMMER mit dem Tool-Aufruf kanban_complete (fertig) oder kanban_block (Frage/Sackgasse). Eine Text-Zusammenfassung OHNE diesen Aufruf zaehlt als Absturz (Protokollverstoss) und blockiert die Karte — genau so ging am 14.07. eine fertige Arbeit verloren.
REPO-WEGWEISER mission-control-v2 (NICHT raten/suchen — so liegt es): backend/app.py = Einstieg + Router-Mounting · backend/routers/*.py = REST-Endpunkte · backend/services/*.py = Logik · backend/gateway_app.py = /v1-Gateway-Prozess (:9010) · frontend/src/views/*.tsx = Ansichten · frontend/src/lib/api.ts = Typen+Fetch · frontend/src/lib/queries.ts = TanStack-Query-Hooks (Polling) · frontend/src/nav.ts = Sidebar · deploy/ = Skripte/Units/Hooks/Skills · Projekt-Regeln: AGENTS.md (Wurzel) + docs/wissen/.
+1 -1
View File
@@ -123,7 +123,7 @@ check_hermes(){
done
if [ "$state" = "done" ]; then
SUMMARY+=("$name: $behind Commits eingespielt, Gehirn-Check grün.")
notify "Hermes-Agent aktualisiert ($behind Commits). Gehirn-Check (Mem0, Tools, Voice) grün — alles läuft."
notify "Hermes-Agent aktualisiert ($behind Commits). Gehirn-Check (Gedächtnis, Tools, Voice) grün — alles läuft."
return
fi
# Rot/Timeout: ERST Selbstreparatur versuchen (Config-Bruch selbst ziehen, neue Version behalten) —
+6 -9
View File
@@ -3,8 +3,8 @@
# zurückkommt. Erzeugt EIN Tarball mc2-state-<ts>.tar.gz, behält die letzten N.
# Läuft per systemd-Timer (täglich), manuell, oder über den UI-Snapshot-Button.
#
# Inhalt: mem0 (Chroma + history.db) · ~/.hermes (config.yaml, .env, plugins/) ·
# /etc/llama-swap/config.yaml · known-good/ (Versions-Manifest, s. u.)
# Inhalt: ~/.hermes (config.yaml, .env, plugins/) · /etc/llama-swap/config.yaml ·
# known-good/ (Versions-Manifest, s. u.)
# NICHT enthalten (bewusst): GGUF-Modelle (riesig, neu ladbar), MC2-Code (Git), venvs.
#
# ACHTUNG: das Tarball enthält ~/.hermes/.env (Secrets) → chmod 600, nicht in Git.
@@ -20,16 +20,14 @@ RETAIN="${MC_BACKUP_RETAIN:-14}"
# Off-Box-Ziel (leer = deaktiviert). Default: Proxmox-Host, eigener Key mit minimalem Recht.
OFFSITE="${MC_BACKUP_OFFSITE:-root@192.168.178.108:/var/lib/vz/mc2-backups}"
OFFSITE_KEY="${MC_BACKUP_OFFSITE_KEY:-$HOME/.ssh/mc2_offsite}"
MEM0_DIR="${MC_MEM0_DIR:-/srv/models/mem0}"
LSWAP="${MC_CONFIG_PATH:-/etc/llama-swap/config.yaml}"
HERMES="${HERMES_HOME:-$HOME/.hermes}"
TS="$(date +%Y%m%d-%H%M%S)"
STAGE="$(mktemp -d)"
trap 'rm -rf "$STAGE"' EXIT
mkdir -p "$STAGE/mem0" "$STAGE/hermes" "$STAGE/llama-swap"
mkdir -p "$STAGE/hermes" "$STAGE/llama-swap"
[ -d "$MEM0_DIR" ] && cp -a "$MEM0_DIR/." "$STAGE/mem0/" || true
[ -f "$HERMES/config.yaml" ] && cp -a "$HERMES/config.yaml" "$STAGE/hermes/" || true
[ -f "$HERMES/.env" ] && cp -a "$HERMES/.env" "$STAGE/hermes/" || true
[ -d "$HERMES/plugins" ] && cp -a "$HERMES/plugins" "$STAGE/hermes/plugins" || true
@@ -60,7 +58,7 @@ SRC="${MC2_SRC:-$HOME/mission-control-v2}"
HERMES_AGENT="${HERMES_AGENT_DIR:-$HOME/.hermes/hermes-agent}"
UV="$(command -v uv || echo "$HOME/.local/bin/uv")"
# pip freeze je venv; mem0-venv ist uv-managed (kein pip-Modul) → uv als Fallback.
# pip freeze je venv; voice-venv ist uv-managed (kein pip-Modul) → uv als Fallback.
freeze_venv() { # $1 = python-Pfad, $2 = Zieldatei
if [ ! -x "$1" ]; then echo "venv fehlt: $1" > "$2"; return 0; fi
"$1" -m pip freeze > "$2" 2>/dev/null \
@@ -68,7 +66,6 @@ freeze_venv() { # $1 = python-Pfad, $2 = Zieldatei
|| echo "pip freeze fehlgeschlagen: $1" > "$2"
}
freeze_venv "$SRC/backend/.venv/bin/python" "$KG/pip-backend.txt"
freeze_venv "$HOME/.mem0/venv/bin/python" "$KG/pip-mem0.txt"
freeze_venv "$HOME/.voice/venv/bin/python" "$KG/pip-voice.txt"
{
@@ -79,7 +76,7 @@ freeze_venv "$HOME/.voice/venv/bin/python" "$KG/pip-voice.txt"
echo "llama.cpp : $(LD_LIBRARY_PATH=/opt/llamacpp-vulkan /usr/local/bin/llama-server --version 2>&1 | grep -m1 -o 'version: .*' || echo '?')"
echo "llama-swap : $(/usr/local/bin/llama-swap --version 2>/dev/null | head -1 || echo '?')"
echo "python-venvs :"
for P in "$SRC/backend/.venv/bin/python" "$HOME/.mem0/venv/bin/python" "$HOME/.voice/venv/bin/python"; do
for P in "$SRC/backend/.venv/bin/python" "$HOME/.voice/venv/bin/python"; do
echo " $P$("$P" --version 2>&1 || echo 'fehlt')"
done
} > "$KG/versions.txt" || true
@@ -92,7 +89,7 @@ cat > "$STAGE/MANIFEST.txt" <<EOF
mc2-state backup
created : $TS
host : $(hostname)
inhalt : mem0 (chroma + history.db), hermes (config.yaml, .env, plugins/), llama-swap (config.yaml), known-good/ (Versions-Manifest: pip freeze, Engine, Modelle)
inhalt : hermes (config.yaml, .env, plugins/), llama-swap (config.yaml), known-good/ (Versions-Manifest: pip freeze, Engine, Modelle)
restore : bash ~/mission-control-v2/deploy/restore.sh mc2-state-$TS.tar.gz
EOF
+23 -2
View File
@@ -6,14 +6,35 @@ echo "Starte MC2 Deployment..."
# 1. Neuesten Code holen
git pull origin main
# 2. Abhängigkeiten prüfen (falls sich was geändert hat)
echo "Aktualisiere Python Abhängigkeiten..."
backend/.venv/bin/pip install -r backend/requirements.txt
# 3. Systemd Units neu laden (falls sich .service Dateien geändert haben)
# 3. Llama-Swap Konfiguration prüfen und synchronisieren
if ! cmp -s deploy/llama-swap.config.yaml /etc/llama-swap/config.yaml; then
echo "Llama-Swap Konfiguration hat sich geändert, synchronisiere..."
# NOPASSWD für llama-swap Neustart ist vorausgesetzt (siehe AGENTS.md)
sudo cp deploy/llama-swap.config.yaml /etc/llama-swap/config.yaml
sudo systemctl restart llama-swap
fi
# 4. Systemd Units neu laden (falls sich .service Dateien geändert haben)
systemctl --user daemon-reload
# 4. Dienste neu starten
# 4.5 Hermes Skills synchronisieren
echo "Synchronisiere Hermes Skills..."
mkdir -p ~/.hermes/skills
# Kopiere alle Skills aus dem Repo, ersetze - durch _ in den Ordnernamen für Hermes Kompatibilität
for skill_dir in deploy/skills/*; do
if [ -d "$skill_dir" ]; then
skill_name=$(basename "$skill_dir" | tr '-' '_')
mkdir -p ~/.hermes/skills/"$skill_name"
cp -r "$skill_dir"/* ~/.hermes/skills/"$skill_name"/
fi
done
# 5. Dienste neu starten
echo "Starte Backend neu..."
systemctl --user restart mc2-gateway.service mission-control-2.service
+1 -14
View File
@@ -1,28 +1,15 @@
#!/usr/bin/env bash
# Post-Update-Check nach einem Hermes-Agent-Update: läuft unser geteiltes „Gehirn"
# (Mem0 + die mc2-memory-Integration) noch? Exit 0 = alles ok, sonst 1 → der Job wird
# (die mc2-memory-Integration) noch? Exit 0 = alles ok, sonst 1 → der Job wird
# im UI rot, damit ein kaputtes Gehirn sofort auffällt.
set -uo pipefail
MC_URL="${MC_URL:-http://127.0.0.1:9001}"
MEM0_URL="${MEM0_SERVICE_URL:-http://127.0.0.1:8765}"
HERMES="${HERMES_HOME:-$HOME/.hermes}"
fail=0
echo "=== Hermes Post-Update: Gehirn-Check ==="
if curl -sf -m 5 "$MEM0_URL/health" >/dev/null 2>&1; then
echo "PASS · Mem0-Sidecar erreichbar"
else
echo "FAIL · Mem0-Sidecar NICHT erreichbar"; fail=1
fi
if curl -sf -m 8 "$MC_URL/api/memory" >/dev/null 2>&1; then
echo "PASS · /api/memory antwortet"
else
echo "FAIL · /api/memory antwortet nicht"; fail=1
fi
if grep -qE "^[[:space:]]*provider:[[:space:]]*'?mc2-memory'?" "$HERMES/config.yaml" 2>/dev/null \
&& grep -qE "memory_enabled:[[:space:]]*true" "$HERMES/config.yaml" 2>/dev/null; then
echo "PASS · memory.provider=mc2-memory aktiv"
+22 -37
View File
@@ -10,7 +10,7 @@ models:
# lief aber NIE live — bewusst bei Q8_0 geblieben: Qualität vor ein paar GB, seit der
# Warm-Set-Diät 07.07. ist RAM nicht mehr der Engpass.)
cmd: |
llama-server -m /srv/models/Qwen3.6-35B-A3B-MTP-GGUF/Qwen3.6-35B-A3B-UD-Q4_K_M.gguf --host 127.0.0.1 --port ${PORT} -c 131072 -ngl 999 -fa on --no-mmap --jinja --parallel 2 -cram 16384 -ctk q8_0 -ctv q8_0 --spec-type draft-mtp --spec-draft-n-max 3
llama-server -m /srv/models/Qwen3.6-35B-A3B-MTP-GGUF/Qwen3.6-35B-A3B-UD-Q4_K_M.gguf --host 127.0.0.1 --port ${PORT} -c 131072 -ngl 999 -fa on --no-mmap --jinja --parallel 2 -cram 16384 -ctk q8_0 -ctv q8_0 --spec-type draft-dflash --spec-draft-model /srv/models/Qwen3.6-35B-A3B-DFlash-GGUF/giocom-Qwen3.6-35B-A3B-DFlash-Q8_0.gguf
ttl: 0
aliases:
- hermes
@@ -21,17 +21,33 @@ models:
out: [text]
tools: true
context: 65536
Qwen3-Coder-Next:
Qwen3.8-27B:
# Qwen 3.8 27B: Dichtes 27B-Modell mit hybrider Linear-Attention (48/64 Schichten linear),
# nativem Multimodal-Support (mmproj-BF16) und Tool-Calling via --jinja.
# Unser neuer Haupt-Coder mit 262k Kontext, Thinking Mode und extrem sauberer Code-Qualität.
cmd: |
llama-server -m /srv/models/Qwen3-Coder-Next-GGUF/Qwen3-Coder-Next-Q4_K_M.gguf --host 127.0.0.1 --port ${PORT} -c 131072 -ngl 999 -fa on --no-mmap --jinja --cache-reuse 256 -cram 16384
ttl: 600
llama-server -m /srv/models/Qwen3.8-27B-GGUF/Qwen3.8-27B-Q4_K_M.gguf --host 127.0.0.1 --port ${PORT} -c 131072 -ngl 999 -fa on --no-mmap --mmproj /srv/models/Qwen3.8-27B-GGUF/mmproj-BF16.gguf --jinja --parallel 2 -cram 16384 -ctk q8_0 -ctv q8_0
ttl: 5400
aliases:
- coder
capabilities:
in: [text]
in: [text, image]
out: [text]
tools: true
context: 131072
context: 65536
Muse-Glimmer-30B:
# Meta Muse Glimmer 30B: Dichtes 30B-Agenten-Modell mit DFlash-Speculative-Drafting
# und 1.8B Perception Multimodal Projector. Ideal als Runtime-Debugger & Fehler-Diagnostiker.
cmd: |
llama-server -m /srv/models/Muse-Glimmer-30B-GGUF/Muse-Glimmer-30B-UD-Q4_K_XL.gguf --host 127.0.0.1 --port ${PORT} -c 65536 -ngl 999 -fa on --no-mmap --mmproj /srv/models/Muse-Glimmer-30B-GGUF/mmproj-kquant.gguf --jinja --spec-type draft-dflash --spec-draft-model /srv/models/Muse-Glimmer-30B-GGUF/dflash-kquant.gguf
ttl: 600
aliases:
- debugger
capabilities:
in: [text, image]
out: [text]
tools: true
context: 65536
Qwen3-Embedding-0.6B:
cmd: |
llama-server -m /srv/models/Qwen3-Embedding-0.6B-GGUF/Qwen3-Embedding-0.6B-Q8_0.gguf --host 127.0.0.1 --port ${PORT} --embedding --pooling last -ngl 999 -fa on --no-mmap -c 8192
@@ -52,11 +68,6 @@ models:
tools: true
context: 32768
Qwen3-VL-30B-A3B-Instruct:
# Lucys AUGEN — seit 07.07. ON-DEMAND (User-Entscheid: „Gehirn + Embedding reichen
# komplett; Vision nur wenn Lucy auf dem Desktop an ist oder die IDE-Lane sie braucht").
# NICHT mehr in brains; ttl 900 = 15 Min Nachlauf nach dem letzten Blick. Die Lucy-App
# wärmt die Augen beim Start selbst an (useVoiceAgent-Warm-Gate). Vorher: warm, ttl 0 —
# das kostete ~19 GB Dauerbelegung und verdrängte den 60-GB-Chef-Gutachter.
cmd: |
llama-server -m /srv/models/Qwen3-VL-30B-A3B-Instruct-GGUF/Qwen3-VL-30B-A3B-Instruct-Q4_K_M.gguf --host 127.0.0.1 --port ${PORT} -c 32768 -ngl 999 -fa on --no-mmap --mmproj /srv/models/Qwen3-VL-30B-A3B-Instruct-GGUF/mmproj-F16.gguf --jinja
ttl: 900
@@ -66,32 +77,6 @@ models:
in: [text, image]
out: [text]
context: 32768
GLM-4.6V-Flash:
cmd: |
llama-server -m /srv/models/GLM-4.6V-Flash-GGUF/GLM-4.6V-Flash-Q4_K_M.gguf --host 127.0.0.1 --port ${PORT} -c 131072 -ngl 999 -fa on --no-mmap --mmproj /srv/models/GLM-4.6V-Flash-GGUF/mmproj-BF16.gguf --jinja
ttl: 300
aliases:
- scout
capabilities:
in: [text, image]
out: [text]
tools: true
context: 131072
GLM-4.7-Flash:
# KRITIKER (07.07., Rollen-Audit): Fremd-Vendor-Zweitmeinung für fremdblick-Prosa +
# Chef-Gutachter-Vertretung — reines Textmodell, 30B-A3B (MIT, Unsloth-Dynamic-Quant).
# Die Vision-Schwester GLM-4.6V-Flash bleibt als scout für Bild-Jobs. --jinja ist
# Pflicht (Chat-Template, sonst kaputte Tool-Calls).
cmd: |
llama-server -m /srv/models/GLM-4.7-Flash-GGUF/GLM-4.7-Flash-UD-Q4_K_XL.gguf --host 127.0.0.1 --port ${PORT} -c 65536 -ngl 999 -fa on --no-mmap --jinja
ttl: 600
aliases:
- kritiker
capabilities:
in: [text]
out: [text]
tools: true
context: 65536
Qwen3-Reranker-0.6B:
# Gedächtnis-Zweitstufe (07.07., Rollen-Audit): ordnet Mem0-Suchtreffer nach echter
# Relevanz (/v1/rerank, Mungert-GGUF — Community-Konvertierungen liefern oft Nullscores!).
+124
View File
@@ -0,0 +1,124 @@
#!/usr/bin/env bash
# Projekte-Sync (20.08.2026): haelt ~/projekte mit Gitea deckungsgleich.
#
# Warum: Der Hermes-Desktop laeuft gegen das Box-Backend, sein Ordner-Waehler zeigt
# also das Dateisystem der BOX. Damit dort alle Projekte auftauchen, muessen sie hier
# liegen — und aktuell sein.
#
# Regeln (bewusst konservativ — dieses Skript laeuft unbeaufsichtigt):
# • NUR fast-forward (`--ff-only`). Kein merge, kein rebase, kein reset, kein force.
# • Repos mit lokalen Aenderungen werden UEBERSPRUNGEN, nicht ueberfahren.
# • Live-Deployments werden NIE gezogen (mission-control-v2 wird aus dem Deploy
# bedient und pusht selbst) — sie bekommen nur eine Verknuepfung, damit sie im
# Waehler auftauchen.
# • Bestehende Checkouts in ~ bleiben, wo sie sind; ~/projekte bekommt eine
# Verknuepfung darauf statt eines zweiten Klons.
# • Interne Gitea-IP, NICHT die DDNS-Domain: die ist nachts durch die
# Zwangstrennung zeitweise tot (Lehre vom 24.07.).
#
# Aufruf: projekte-sync.sh [--dry-run]
# Exit: 0 = alles gut · 1 = mindestens ein Repo hatte ein Problem
set -uo pipefail
GITEA="${GITEA_URL:-http://192.168.178.153:3000}"
ZIEL="${PROJEKTE_DIR:-$HOME/projekte}"
LOG="${PROJEKTE_SYNC_LOG:-$HOME/.hermes/logs/projekte-sync.log}"
DRY=0
[ "${1:-}" = "--dry-run" ] && DRY=1
# Repos, die NICHT nach ~/projekte geklont werden (Live-Deployment oder Altlast).
# mission-control-v2: laeuft aus ~/mission-control-v2, Dienste arbeiten daraus.
# mission-control: archivierte v1.
NIE_ZIEHEN="mission-control-v2"
UEBERSPRINGEN="mission-control"
mkdir -p "$ZIEL" "$(dirname "$LOG")"
FEHLER=0
log() { printf '%s %s\n' "$(date '+%F %T')" "$*" | tee -a "$LOG"; }
TOKEN=""
for f in "$HOME/.config/gitea/create-token"; do
[ -r "$f" ] && TOKEN="$(tr -d '[:space:]' < "$f")" && break
done
if [ -z "$TOKEN" ]; then
log "ABBRUCH: kein Gitea-Token unter ~/.config/gitea/create-token"
exit 1
fi
REPOS="$(curl -s -m 20 -H "Authorization: token $TOKEN" \
"$GITEA/api/v1/user/repos?limit=100" \
| python3 -c 'import sys,json; [print(r["name"]) for r in json.load(sys.stdin)]' 2>/dev/null)"
if [ -z "$REPOS" ]; then
log "ABBRUCH: Gitea lieferte keine Repo-Liste ($GITEA)"
exit 1
fi
log "=== Sync-Lauf ($(printf '%s\n' "$REPOS" | wc -l) Repos gemeldet)$([ $DRY -eq 1 ] && echo ' [Trockenlauf]') ==="
# Vorhandenen Checkout eines Repos finden (oberste Home-Ebene bevorzugt).
finde_checkout() {
local name="$1" d
for d in "$HOME/$name" "$ZIEL/$name"; do
[ -d "$d/.git" ] && { printf '%s' "$d"; return 0; }
done
return 1
}
verknuepfe() { # $1=Zielpfad des echten Checkouts, $2=Repo-Name
local ziel="$1" name="$2" link="$ZIEL/$name"
[ "$ziel" = "$link" ] && return 0
[ -L "$link" ] && [ "$(readlink -f "$link")" = "$(readlink -f "$ziel")" ] && return 0
[ -e "$link" ] && [ ! -L "$link" ] && { log " ! $name: $link existiert und ist keine Verknuepfung — nichts getan"; return 1; }
[ $DRY -eq 1 ] && { log " ~ $name: wuerde verknuepfen -> $ziel"; return 0; }
ln -sfn "$ziel" "$link" && log " ~ $name: verknuepft -> ${ziel/$HOME/\~}"
}
for name in $REPOS; do
case " $UEBERSPRINGEN " in *" $name "*) log " - $name: uebersprungen (Altlast)"; continue;; esac
if checkout="$(finde_checkout "$name")"; then
case " $NIE_ZIEHEN " in
*" $name "*)
log " = $name: Live-Deployment, kein Pull"
verknuepfe "$checkout" "$name"
continue;;
esac
if [ -n "$(git -C "$checkout" status --porcelain 2>/dev/null)" ]; then
log " ! $name: lokale Aenderungen — uebersprungen (nichts ueberfahren)"
verknuepfe "$checkout" "$name"
continue
fi
if [ $DRY -eq 1 ]; then
log " ~ $name: wuerde pullen (${checkout/$HOME/\~})"
else
vorher="$(git -C "$checkout" rev-parse --short HEAD 2>/dev/null)"
if out="$(git -C "$checkout" pull --ff-only 2>&1)"; then
nachher="$(git -C "$checkout" rev-parse --short HEAD 2>/dev/null)"
if [ "$vorher" = "$nachher" ]; then
log " = $name: aktuell ($vorher)"
else
log " + $name: $vorher -> $nachher"
fi
else
log " ! $name: Pull fehlgeschlagen — $(printf '%s' "$out" | tail -1)"
FEHLER=1
fi
fi
verknuepfe "$checkout" "$name"
else
if [ $DRY -eq 1 ]; then
log " ~ $name: wuerde klonen nach ${ZIEL/$HOME/\~}/$name"
elif out="$(git clone -q "$GITEA/Hitonabi/$name.git" "$ZIEL/$name" 2>&1)"; then
log " + $name: neu geklont"
else
log " ! $name: Klonen fehlgeschlagen — $(printf '%s' "$out" | tail -1)"
FEHLER=1
fi
fi
done
log "=== Ende (Fehler: $FEHLER) ==="
exit $FEHLER
+10 -10
View File
@@ -1,12 +1,12 @@
# Projektstart-Worker (Profil „projektstart") — IDE-Vorbereiter
Du bist der **IDE-Vorbereiter** der AI-Box. Du bearbeitest EINE Kanban-Aufgabe: eine Idee des
Commanders, die er **selbst in seiner IDE (Zed / OpenCode) bauen** will — nicht die Box.
Commanders, die er **selbst in der Agentic IDE bauen** will — nicht die Box.
Dein Ergebnis ist **kein Code**, sondern ein **startklares, leeres Projekt-Repo**: ein
wasserdicht durchdachtes Konzept plus alle Steuerungs-Dokumente, mit denen der Commander in Zed
wasserdicht durchdachtes Konzept plus alle Steuerungs-Dokumente, mit denen der Commander in der Agentic IDE
dem Coder sofort einen sauberen Auftrag geben kann. Du denkst und bereitest vor — **gebaut wird
in Zed, von ihm.**
in der Agentic IDE, von ihm.**
## Schritt 0: WIEDERAUFNAHME — immer zuerst, nie aus Reflex neu anfangen
@@ -84,13 +84,13 @@ genau diese Dateien — sonst **nichts**, kein Code, kein Gerüst:
offene Entscheidungen, was der Advocatus Diaboli geprüft hat).
- **`ROADMAP.md`** — der Etappen-/Meilenstein-Plan aus dem Konzept: jede Etappe für sich
lauffähig, in Reihenfolge, mit klarem „fertig wenn …".
- **`AGENTS.md`** — Arbeits-Konventionen für den Zed-Coder (nach dem `projekt-start`-Ritual):
- **`AGENTS.md`** — Arbeits-Konventionen für die Agentic IDE (nach dem `projekt-start`-Ritual):
Deutsch/Nicht-Entwickler, Plan-vor-Code, kleine Schritte, beweisen statt behaupten, Savepoints,
Box-Modelle je Aufgabe (plan=heavy, build=coder). Kurz, konkret, projektbezogen.
**Ganz oben hinein — wörtlich:** „Dieses Projekt ist von der AI-Box vorbereitet: KONZEPT.md ist
bereits gehärtet, ROADMAP.md ist der Plan. Beim ersten Chat den Skill **`projekt-uebernehmen`**
nutzen (einlesen → nachfragen → Etappe 1) — das Konzept wird NICHT neu geplant."
Das ist die Übergabe an den Agenten in Zed; ohne diese Zeile fängt er womöglich von vorn an.
Das ist die Übergabe an die Agentic IDE; ohne diese Zeile fängt er womöglich von vorn an.
**Dazu diese drei HARTEN Regeln — wörtlich in die AGENTS.md (Wasserdicht-Runde 22.07.,
Lehre aus rippy: Prosa-Regeln wurden komplett ignoriert, darum stehen sie ab jetzt überall):**
„1. Die **CI-Ampel** (Gitea Actions, liegt seit der Repo-Anlage unter `.gitea/workflows/ci.yml`
@@ -100,9 +100,9 @@ genau diese Dateien — sonst **nichts**, kein Code, kein Gerüst:
MakeMKV lossless' wurde still durch lossy HandBrake ersetzt.)
3. Deployment NUR über `./deploy.sh` (idempotent, EIN fester Compose-Projektname) —
nie freihändig per SSH zusammenbauen. (rippy-Lehre: Doppel-Anlage `Rippy`+`rippy` auf der VM.)"
- **`README.md`** — was das Projekt ist, in Alltagssprache; erste Schritte zum Loslegen in Zed.
- **`SAVEPOINT.md`** — Startzustand: „vorbereitet — Bau beginnt in Zed", Stand/nächste Schritte/
offene Fragen (lebendes Übergabe-Dokument für den ersten Zed-Chat).
- **`README.md`** — was das Projekt ist, in Alltagssprache; erste Schritte zum Loslegen in der Agentic IDE.
- **`SAVEPOINT.md`** — Startzustand: „vorbereitet — Bau beginnt in der Agentic IDE", Stand/nächste Schritte/
offene Fragen (lebendes Übergabe-Dokument für den ersten Chat).
- **`.aiexclude`** — gitignore-Syntax: was die KI NICHT lesen soll (`.env`, Secrets, große Daten).
Committe (`git add -A && git commit`) und pushe auf **main dieses NEUEN Repos** — das ist korrekt:
@@ -112,7 +112,7 @@ Prüfe nach dem Push mit `git ls-remote origin main`, dass es wirklich angekomme
### 4. Übergabe — `kanban_complete`
Schließe die Karte mit einer Zusammenfassung ab, die das Auftragsbuch dem Commander zeigt:
> „Projekt **<name>** ist vorbereitet. Repo: `<clone-url>`. Öffne es in Zed und wirf dem Coder
> „Projekt **<name>** ist vorbereitet. Repo: `<clone-url>`. Öffne es in der Agentic IDE und wirf dem Coder
> **KONZEPT.md** vor — der Plan steht in **ROADMAP.md**. Die Box baut ab hier nicht mehr mit."
Nenne knapp, was das Konzept-Härten ergab (Rollen-Cast, was der Skeptiker fand, welche Etappen).
@@ -130,7 +130,7 @@ DURCHDACHT haben — er hat sich NICHT entschieden, sie zu bauen. Dann gilt abwe
`AGENTS.md`, KEIN `SAVEPOINT.md`, KEIN `.aiexclude`, kein Geruest. Das entsteht erst, wenn der
Commander die Idee spaeter als Projekt startet.
- Schritt 4 (Uebergabe) NICHT als Bau-Aufforderung: sag, was die Idee waere, was sie braeuchte und
was dagegen spricht — und dass die Entscheidung bei ihm liegt. Kein „oeffne in Zed und bau".
was dagegen spricht — und dass die Entscheidung bei ihm liegt. Kein „öffne in der IDE und bau".
## Sonderfall: „UEBERARBEITUNG" (der Commander hat das Konzept gelesen)
Steht im Auftrag **`AUFTRAGS-ART: IDEE PRUEFEN — UEBERARBEITUNG`**, existiert das Konzept schon
+1 -4
View File
@@ -15,11 +15,10 @@ set -euo pipefail
MODELS_DIR="${MC_MODELS_DIR:-/srv/models}"
DEST_DIR="$MODELS_DIR/mc2-backups"
MEM0_DIR="${MC_MEM0_DIR:-/srv/models/mem0}"
LSWAP="${MC_CONFIG_PATH:-/etc/llama-swap/config.yaml}"
HERMES="${HERMES_HOME:-$HOME/.hermes}"
SRC="${MC2_SRC:-$HOME/mission-control-v2}"
SERVICES="mem0-service mission-control-2 hermes-gateway"
SERVICES="mission-control-2 hermes-gateway"
OFFSITE="${MC_BACKUP_OFFSITE:-root@192.168.178.108:/var/lib/vz/mc2-backups}"
OFFSITE_KEY="${MC_BACKUP_OFFSITE_KEY:-$HOME/.ssh/mc2_offsite}"
OFFSITE_SSH="ssh -i $OFFSITE_KEY -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new"
@@ -98,7 +97,6 @@ echo "--- Inhalt ---"; cat "$STAGE/MANIFEST.txt" 2>/dev/null || true; echo "----
if [ "$DRY" = 1 ]; then
echo "[dry-run] würde zurücksetzen:"
[ -d "$STAGE/mem0" ] && echo " • mem0 → $MEM0_DIR (wird ersetzt)"
[ -f "$STAGE/hermes/config.yaml" ] && echo "$HERMES/config.yaml"
[ -f "$STAGE/hermes/.env" ] && echo "$HERMES/.env"
[ -d "$STAGE/hermes/plugins" ] && echo "$HERMES/plugins/"
@@ -119,7 +117,6 @@ bash "$SRC/deploy/backup.sh" || echo " (Pre-Restore-Backup fehlgeschlagen — f
echo "→ Dienste stoppen…"
systemctl --user stop $SERVICES 2>/dev/null || true
if [ -d "$STAGE/mem0" ]; then rm -rf "$MEM0_DIR"; mkdir -p "$MEM0_DIR"; cp -a "$STAGE/mem0/." "$MEM0_DIR/"; fi
[ -f "$STAGE/hermes/config.yaml" ] && { mkdir -p "$HERMES"; cp -a "$STAGE/hermes/config.yaml" "$HERMES/"; }
[ -f "$STAGE/hermes/.env" ] && cp -a "$STAGE/hermes/.env" "$HERMES/"
[ -d "$STAGE/hermes/plugins" ] && { mkdir -p "$HERMES/plugins"; cp -a "$STAGE/hermes/plugins/." "$HERMES/plugins/"; }
+1 -1
View File
@@ -115,7 +115,7 @@ def chunk(t,d):
c=t+d; return struct.pack(">I",len(d))+c+struct.pack(">I",zlib.crc32(c))
png=b"\x89PNG\r\n\x1a\n"+chunk(b"IHDR",struct.pack(">IIBBBBB",w,h,8,2,0,0,0))+chunk(b"IDAT",zlib.compress(raw))+chunk(b"IEND",b"")
url="data:image/png;base64,"+base64.b64encode(png).decode()
print(json.dumps({"model":"coder","stream":False,"max_tokens":40,"messages":[{"role":"user","content":[{"type":"image_url","image_url":{"url":url}},{"type":"text","text":"Welche Farbe hat das angehaengte Bild? Antworte nur mit der Farbe."}]}]}))
print(json.dumps({"model":"coder","stream":False,"max_tokens":150,"messages":[{"role":"user","content":[{"type":"image_url","image_url":{"url":url}},{"type":"text","text":"Welche Farbe hat das angehaengte Bild? Antworte nur mit der Farbe."}]}]}))
PYIN
)
BW_HDR=$(mktemp); BW_OUT=$(mktemp)
+1 -1
View File
@@ -31,7 +31,7 @@ $HERMES cron create --name trend-radar --deliver origin "15 5 * * 6" "Führe den
$HERMES cron create --name pruefstand --deliver origin "0 1 * * 0" "Prüfe, ob offene Prüfstand-Tickets im Kanban liegen. Wenn ja, führe den 'pruefstand' Skill aus."
# 4. LLM Wiki: Jeden Montag um 03:00
$HERMES cron create --name llm-wiki --deliver origin "0 3 * * 1" "Führe den 'llm_wiki' Skill aus. Analysiere den Code und aktualisiere autonom die Wissensdatenbank in docs/wissen/."
$HERMES cron create --name llm-wiki --deliver origin "0 3 * * 1" "Führe den 'llm_wiki' Skill aus. Analysiere den Code und aktualisiere autonom die Wissensdatenbank in ~/wissens-vault/."
echo "Autonome Crons erfolgreich eingerichtet!"
$HERMES cron list
+20
View File
@@ -0,0 +1,20 @@
---
name: autonomie
description: "Gesundheitsprüfungen der Box: Überprüft Logs (Syslog, Journalctl), checkt ob alle Container/Dienste laufen und leitet bei Bedarf selbstständige Reparaturen ein. Meldet Status via Telegram."
version: 1.0.0
author: MC2
platforms: [linux]
metadata:
hermes:
tags: [wartung, autonomie, health, mc2]
---
# Autonomie & Self-Repair
Dieser Skill ermöglicht es der AI-Box, sich selbst zu überprüfen und bei Problemen zu reparieren.
## Ablauf
1. **Dienste prüfen**: Führt `systemctl --user status` für alle relevanten MC2-Dienste aus.
2. **Logs analysieren**: Prüft die letzten Fehler im `journalctl` (User und System-Ebene, falls Berechtigung vorhanden).
3. **Selbstreparatur (Self-Repair)**: Falls bekannte Fehler auftauchen (z.B. Deadlocks, Speicherprobleme, hängende Ports), wird ein definierter Wiederanlauf (z.B. Neustart des Dienstes via `systemctl --user restart`) getriggert.
4. **Meldung**: Das finale Ergebnis wird kompakt an den Commander via Telegram (über `deploy/notify.sh`) gesendet.
+1 -1
View File
@@ -44,7 +44,7 @@ heavy-Rollen einer Runde am Stueck (heavy bleibt warm), DANN der Skeptiker (ein
aus — fuer diesen Hintergrund-Job gewollt. heavy hat nur **32k Kontext** → halte das Konzept-
Dokument kompakt und schleppe KEINE alten Runden-Protokolle mit (siehe Kontext-Uebergabe).
- **REASONING-MODELLE brauchen Token-Luft (verifiziert 14.07.).** heavy (gpt-oss) UND der Skeptiker
(GLM-4.7-Flash) „denken" erst ausfuehrlich (im `reasoning_content`), bevor die eigentliche Antwort
(`fast`) „denken" erst ausfuehrlich (im `reasoning_content`), bevor die eigentliche Antwort
in `content` landet — `worker.sh`/`fremdblick.sh` lesen NUR `content`. Zu knappes `max_tokens` =
das Modell verbraucht alles beim Denken, `content` bleibt LEER, das Skript meldet „FEHLGESCHLAGEN"
(obwohl das Modell lief). GLM verbrennt real ~2000 Tokens Denken fuer 2 Zeilen Antwort. Deshalb
+21
View File
@@ -0,0 +1,21 @@
---
name: llm_wiki
description: "Analysiert autonom Änderungen am Codebase oder Stack und aktualisiert die Markdown-Dateien im wissens-vault, sodass die Dokumentation immer aktuell ist."
version: 1.0.0
author: MC2
platforms: [linux, macos, windows]
metadata:
hermes:
tags: [docs, wiki, knowledge, mc2]
---
# LLM Wiki (Auto-Docs)
Dieser Skill stellt sicher, dass das `wissens-vault` (die "Träume" und Notizen der Box) synchron zur echten Code-Realität bleibt.
## Ablauf
1. **Git-Log Analyse**: Liest die Commits seit dem letzten Lauf aus dem Repository.
2. **Impact-Analyse**: Bestimmt, welche Architektur-Teile, Skripte oder Frontend-Komponenten modifiziert wurden.
3. **Vault-Update**: Sucht im `~/wissens-vault/` nach den betroffenen Markdown-Dokumenten (wie z.B. `STACK.md` oder `FALLEN.md`).
4. **Dokumentation schreiben**: Überschreibt oder ergänzt die Dateien mit den neuesten Fakten und löscht veraltete Passagen, um Halluzinationen in der Zukunft zu vermeiden.
5. **Commit**: Committet die angepassten Docs direkt in den Branch und schließt damit die Wissenslücke.
+21
View File
@@ -0,0 +1,21 @@
---
name: morning_report
description: "Sammelt Metriken, Vorfälle der Nacht und Neuigkeiten (z.B. Hacker News) und schickt morgens eine kompakte Sysadmin-Morgenlage via Telegram."
version: 1.0.0
author: MC2
platforms: [linux, macos, windows]
metadata:
hermes:
tags: [report, admin, daily, mc2]
---
# Sysadmin Morgenlage (Morning Report)
Dieser Skill fungiert als dein persönlicher, KI-gesteuerter Sysadmin, der dir jeden Morgen (oder manuell getriggert) einen Statusbericht liefert.
## Ablauf
1. **System-Metriken abrufen**: Ruft den Health-Status und Load der Box ab.
2. **Logs durchkämmen**: Sucht nach Auffälligkeiten, Fehlern oder Crashes in der Nacht.
3. **News Fetching**: Liest die Top-Posts von Hacker News (oder anderen Tech-Feeds), filtert sie nach AI-Relevanz und fasst die 3 wichtigsten Entwicklungen zusammen.
4. **Auftragsbuch Check**: Prüft, ob neue unentschiedene Vorschläge im Auftragsbuch liegen.
5. **Report Generierung**: Bündelt all diese Informationen in eine kompakte, handyfreundliche (sehr kurze) Nachricht und schickt sie via Telegram an den Commander.
+6 -6
View File
@@ -60,7 +60,7 @@ Vorschlag ausweisen, was fehlt oder wacklig ist.
NIEMALS auf `main` committen, NIEMALS mergen, NIEMALS deployen oder Dienste neu starten.
- **Nur im Worktree arbeiten.** Die Live-Instanz `~/mission-control-v2` bleibt unberuehrt.
- **Warm-Set-Schutz (Speicher vs Latenz).** Worker werden ueber **`worker.sh`** aufgerufen.
Fuer die Umsetzung von Code nutzt du **`Qwen3-Coder-Next`** (laedt klein neben dem Warm-Set).
Fuer die Umsetzung von Code nutzt du **`coder`** (laedt klein neben dem Warm-Set).
Fuer die initiale **Planung** nutzt du explizit den Chef-Gutachter **`gpt-oss-120b`** (heavy),
auch wenn dessen Laden (~15s) Lucys Sprech-Latenz temporaer aussetzt. Das ist fuer diesen
Hintergrund-Prozess gewollt.
@@ -83,7 +83,7 @@ Bevor du selbst Tasks aufteilst, befragst du zwingend **`gpt-oss-120b`** nach ei
printf '%s' "Auftrag: $auftrag" | WORKER_MODEL=gpt-oss-120b WORKER_ROLE=plan ~/.hermes/scripts/worker.sh > /tmp/orch-plan.txt
```
2. Lies den Plan (`cat /tmp/orch-plan.txt`) und nutze ihn, um den Auftrag in eine **geordnete, nummerierte Liste von Teil-Tasks** zu zerlegen.
3. Fuer jeden Teil-Task entscheide: (a) Artefakt, (b) Worker (`Qwen3-Coder-Next` + `build`/`refactor`), (c) Kontext.
3. Fuer jeden Teil-Task entscheide: (a) Artefakt, (b) Worker (`coder` + `build`/`refactor`), (c) Kontext.
### 2. Worktree anlegen (Slug = kurzer Kebab-Case-Name des Auftrags)
Worktrees liegen unter `~/.hermes/worktrees/` (NICHT /tmp — ueberlebt keinen Reboot und
@@ -102,7 +102,7 @@ Fuer jeden Bau-Teil-Task:
```
printf '%s' "$eingabe" | WORKER_ROLE=build ~/.hermes/scripts/worker.sh > ~/.hermes/worktrees/orch-<slug>-work/<n>.out
```
(Default-Modell ist `Qwen3-Coder-Next`. Fuer einen bewusst anderen Worker `WORKER_MODEL=<echte-id>`.)
(Default-Modell ist `coder`. Fuer einen bewusst anderen Worker `WORKER_MODEL=<rolle-oder-id>`.)
3. **Pruefe die Ausgabe, bevor du sie verwendest:** Beginnt sie mit `FEHLT:`, hast du zu wenig
Kontext gegeben → nachliefern und erneut rufen. Hat der Worker versehentlich einen ```-Codezaun
oder Prosa drumherum gesetzt, entferne ihn. Dann schreibe das saubere Artefakt mit deinen
@@ -164,13 +164,13 @@ GIT-DIFF des Worktrees:
$(git -C ~/.hermes/worktrees/orch-<slug> diff --cached origin/main)"
```
Ist dieser Diff LEER, hast du nicht gestaged (oder nichts gebaut) → NICHT weiter, erst beheben.
**Kritik A — Kompetenz** (`Qwen3-Coder-Next`, starker Coder, vom Bauen noch geladen → KEIN Swap):
**Kritik A — Kompetenz** (`coder`, starker Coder, vom Bauen noch geladen → KEIN Swap):
```
printf '%s' "$EINGABE" | FREMDBLICK_MODE=code ~/.hermes/scripts/fremdblick.sh
```
**Kritik B — Fremd-Blick** (`GLM-4.6V-Flash`, andere Modellfamilie, andere blinde Flecken):
**Kritik B — Fremd-Blick** (`debugger`, andere Modellfamilie, andere blinde Flecken):
```
printf '%s' "$EINGABE" | FREMDBLICK_MODE=code FREMDBLICK_MODEL=GLM-4.6V-Flash FREMDBLICK_MAXTOK=3000 ~/.hermes/scripts/fremdblick.sh
printf '%s' "$EINGABE" | FREMDBLICK_MODE=code FREMDBLICK_MODEL=debugger FREMDBLICK_MAXTOK=3000 ~/.hermes/scripts/fremdblick.sh
```
Reihenfolge bewusst: erst A (Coder-Next ist noch warm), dann B (ein Swap zu GLM). Beide beginnen mit
`URTEIL: <ABGELEHNT | FREIGABE-MIT-VORBEHALT | FREIGABE>`. **Warte auf beide.**
+21
View File
@@ -0,0 +1,21 @@
---
name: pruefstand
description: "Holt Prüfstand-Tickets aus dem Kanban-Board, testet neue LLM-Modelle auf Kompatibilität und Systemressourcen und meldet die Ergebnisse via Telegram."
version: 1.0.0
author: MC2
platforms: [linux]
metadata:
hermes:
tags: [pruefstand, evaluation, models, mc2]
---
# Prüfstand (Model Evaluation)
Der Prüfstand-Skill dient dazu, neue LLMs oder Tooling-Upgrades (oft aus dem Trend-Radar stammend) autonom auf der Box zu evaluieren, bevor sie ins Live-System übernommen werden.
## Ablauf
1. **Ticket Fetching**: Liest das nächste anstehende Ticket in der Kanban Ideen-Queue, welches als "Prüfstand" getaggt ist.
2. **Download & Setup**: Lädt das beschriebene Modell (z.B. via `huggingface-cli` oder llama.cpp) in ein isoliertes Test-Verzeichnis.
3. **Smoke-Tests**: Führt vordefinierte Benchmarks oder Prompts gegen das Modell aus und überwacht den RAM/VRAM-Bedarf.
4. **Bewertung**: Wenn das Modell die Kriterien erfüllt (schnell genug, kein OOM), wird es als "Bestanden" markiert und ein Integrations-Vorschlag erstellt.
5. **Report**: Der Commander erhält einen Telegram-Bericht mit den Metriken und der Entscheidung.
+24
View File
@@ -0,0 +1,24 @@
---
name: review
description: "Ersetzt das alte fremdblick.sh Skript. Ein dedizierter Code-Review-Skill, der über ein fremdes Modell als 'Advocatus Diaboli' Patches hart prüft und nach Reproduzierbarkeit bewertet."
version: 1.0.0
author: MC2
platforms: [linux, macos, windows]
metadata:
hermes:
tags: [review, code-quality, pr, mc2]
---
# Code Review (Fremdblick)
Dieser Skill delegiert das Code-Review an ein separates, kritisches LLM-Modell (Advocatus Diaboli), um Fehler, Architektur-Schwächen und blinde Flecken zu finden.
## Ablauf
1. **Diff einlesen**: Nimmt den aktuellen Git-Diff oder den Patch-Inhalt.
2. **Kritische Analyse**: Prüft den Code auf Edge-Cases, Security, Performance und Architektonische Richtigkeit.
3. **Reproduzierbarkeits-Prüfung**: Das Modell muss den Fehlerfall gedanklich nachstellen und beweisen, ob der Fix ihn wirklich behebt.
4. **Urteil fällen**:
- `ABGELEHNT`: Fehler nicht behoben oder neue Bugs eingeführt.
- `FREIGABE-MIT-VORBEHALT`: Funktioniert, aber unsauber oder fehlende Kommentare.
- `FREIGABE`: Wasserdicht.
5. **Ergebnis melden**: Das Urteil wird an den Commander zurückgespielt (oft kombiniert mit der Werkstatt).
+36
View File
@@ -0,0 +1,36 @@
---
name: trend_radar
description: "Autonomer KI-Technologie-Scout. Recherchiert im Web nach tagesaktuellen Trends, passt seine eigenen Quellen an und sendet Telegram-Reports."
version: 2.0.0
author: MC2
platforms: [linux, macos, windows]
metadata:
hermes:
tags: [research, radar, trends, mc2, self-modifying]
---
# Trend Radar (Autonomous & Self-Modifying)
Du bist der KI-Technologie-Scout von Mission Control 2.0. Dein Ziel ist es, tagesaktuelle Trends (Modelle, Tools, Hardware-Performance) aufzuspüren, die für uns relevant sind (Fokus: AMD Hardware, Vulkan/ROCm, kleine aber sehr smarte Open-Source-Modelle).
## Strikte Hardware-Limits (SEHR WICHTIG)
Unsere AI-Box (Bosgame M5 / Strix Halo) hat **exakt 128 GB Shared RAM** (ca. 122.7 GB nutzbar)!
Das bedeutet:
- **Ignoriere gigantische Modelle:** Modelle mit 200B+, 700B oder 2T Parametern (wie Qwen 3.8 Max, GLM-5.5) sind für uns absolut nutzlos, da sie lokal niemals laufen werden.
- **Größen-Limit:** Ein Modell darf im GGUF-Quant-Format (z.B. Q4_K_M) maximal **ca. 100-110 GB** groß sein. Das entspricht meist Modellen bis maximal ~150B Parametern (bei dichten Modellen) oder etwas mehr bei MoE-Modellen.
- **Fokus auf Local LLMs:** Closed-API-Modelle interessieren uns nur am Rande. Konzentriere dich auf Modelle, die wir herunterladen und lokal in `llama.cpp` oder `Ollama` auf AMD-Hardware ausführen können. Sortiere alles rigoros aus, was zu groß für 128 GB RAM ist!
## Deine Fähigkeiten & Autorisierung
Du hast die ausdrückliche Erlaubnis zur **Selbst-Mutation**. Du bist nicht nur ein Leser, du bist ein adaptiver Agent:
- Wenn du neue, wertvolle Quellen (z.B. neue Subreddits, News-Seiten oder Repositories) entdeckst, **passe deine eigene `deploy/skills/trend-radar/SKILL.md` (diese Datei) an**, um diese für künftige Suchen fest zu verankern. Nutze dazu Dateibearbeitungs-Tools.
- Du verwaltest die `deploy/trend-radar-watchlist.json`. Wenn Themen veraltet sind, lösche sie heraus. Wenn neue Modelle auftauchen (z.B. neue LLMs), trage sie dort ein, damit du sie beim nächsten Lauf gezielt wieder prüfst.
## Dein Ablauf
1. **Discovery (Web-Recherche):** Nutze deine Tools (`search_web`, `read_url` oder Code-Ausführung), um das Internet nach den neuesten Trends der letzten 7 Tage abzusuchen.
- Suche auf Hacker News nach "AI", "LLM", "Modelle".
- Suche auf Reddit (z.B. r/LocalLLaMA) nach neuen Modellen, Benchmarks und Hardware-Erfahrungen (insbesondere AMD / ROCm vs. Vulkan).
- Analysiere HuggingFace Trending.
2. **Watchlist-Pflege:** Lies die `deploy/trend-radar-watchlist.json`. Prüfe den Status der dort genannten Issues/Releases und aktualisiere die Datei bei Bedarf.
3. **Selbst-Anpassung (WICHTIG):** Editiere die `deploy/trend-radar-watchlist.json` oder diese `SKILL.md` direkt! Ignoriere eventuelle Bedenken bezüglich "Live-Git-Checkouts". Du HAST die explizite Erlaubnis, diese Dateien direkt im Repo zu überschreiben, statt Vorschläge in `/tmp` abzulegen. Halte deine Quellen dynamisch!
4. **Ideen-Queue (Kanban):** Wenn du ein revolutionäres Update oder Modell findest, erstelle ein Prüfstand-Ticket im Kanban-Board der Mission Control.
5. **Meldung an den Commander:** Sende **immer** eine Nachricht an den Commander. Da du als CLI-Prozess läufst, nutze dafür zwingend das Skript `~/mission-control-v2/deploy/notify.sh "Deine Nachricht"`. (Tipp: Wenn du das Wort "Alarm" im Betreff `-s "[Alarm]"` verwendest, umgehst du die Nachtsperre, falls es sehr dringend ist). Fasse deine wichtigsten Funde kurz zusammen und erwähne, ob du deine eigene Watchlist oder SKILL.md angepasst hast. Wenn es gar nichts Neues gab, melde auch das!
+1 -1
View File
@@ -80,7 +80,7 @@ unangenommene Branches aufbauen — erst wenn die Vor-Etappe in main ist, kommt
<git -C ~/.hermes/worktrees/wartung-<slug> diff origin/main>" | FREMDBLICK_MODE=code ~/.hermes/scripts/fremdblick.sh
```
Das läuft auf `Qwen3-Coder-Next` (128k, code-stark, anderes Modell als der Qwen3.6-Worker,
Das läuft auf `coder` (128k, code-stark, anderes Modell als der Qwen3.6-Worker,
lädt klein neben dem Warm-Set) mit einem Code-Review-Raster. **Warte auf die Ausgabe.** Der
Kritiker leitet den Fehlerfall SELBST aus der Bug-Beschreibung ab (glaubt dem Autor nicht),
spielt den geänderten Code durch und beginnt mit `URTEIL: <ABGELEHNT | FREIGABE-MIT-VORBEHALT
+4 -12
View File
@@ -3,15 +3,14 @@
# dass der komplette Inferenz-Stack noch FUNKTIONIERT — nicht nur "Befehl lief durch".
# Exit 0 = alles ok, sonst 1 → der jobengine-Job wird im UI ROT (state=failed).
#
# Pendant zu hermes-postcheck.sh (prüft das Gehirn/Mem0); dieser prüft Router+Engine+MC2
# Pendant zu hermes-postcheck.sh (prüft das Gehirn); dieser prüft Router+Engine+MC2
# inkl. einer ECHTEN 1-Token-Inferenz (beweist, dass ein Modell wirklich lädt & generiert).
set -uo pipefail
SWAP_URL="${MC_LLAMA_SWAP_URL:-http://127.0.0.1:8080}"
MC_URL="${MC_URL:-http://127.0.0.1:9001}"
MEM0_URL="${MEM0_SERVICE_URL:-http://127.0.0.1:8765}"
BRAIN="${MC_WARMUP_MODELS:-fast}"; BRAIN="${BRAIN%% *}" # erstes Modell, falls Liste
EMBED="${MC_WARMUP_EMBED:-embed}"; EMBED="${EMBED%% *}" # Embedding-Modell (Mem0/Gedächtnis)
EMBED="${MC_WARMUP_EMBED:-embed}"; EMBED="${EMBED%% *}" # Embedding-Modell (Gedächtnis)
fail=0
echo "=== Stack Post-Update: Funktionsprüfung ==="
@@ -48,14 +47,14 @@ else
echo "FAIL · Inferenz auf '$BRAIN' fehlgeschlagen (Modell lädt/generiert nicht)"; fail=1
fi
# 3b. Embedding-Modell (für Mem0/Gedächtnis) lädt und liefert einen Vektor?
# 3b. Embedding-Modell (für Gedächtnis) lädt und liefert einen Vektor?
eresp="$(curl -s -m 120 -X POST "$SWAP_URL/v1/embeddings" \
-H 'Content-Type: application/json' \
-d "{\"model\":\"$EMBED\",\"input\":\"ping\"}" 2>/dev/null)"
if printf '%s' "$eresp" | grep -q '"embedding"'; then
echo "PASS · Embedding-Modell '$EMBED' liefert Vektoren"
else
echo "FAIL · Embedding-Modell '$EMBED' lädt/antwortet nicht (Mem0/Gedächtnis betroffen)"; fail=1
echo "FAIL · Embedding-Modell '$EMBED' lädt/antwortet nicht (Gedächtnis betroffen)"; fail=1
fi
# 4. MC2 selbst gesund (Engine + Gateway erreichbar)?
@@ -85,13 +84,6 @@ if systemctl --user is-enabled --quiet mc2-steward 2>/dev/null; then
fi
fi
# 5. Mem0-Sidecar (Gedächtnis) erreichbar?
if curl -sf -m 5 "$MEM0_URL/health" >/dev/null 2>&1; then
echo "PASS · Mem0-Sidecar erreichbar"
else
echo "FAIL · Mem0-Sidecar NICHT erreichbar"; fail=1
fi
if [ "$fail" -eq 0 ]; then
echo "=== STACK OK ✓ ==="
else
+1 -1
View File
@@ -1,5 +1,5 @@
{
"_hinweis": "Watch-Liste des Trend-Radars (Sa 05:15) — mechanisch prüfbare Bedingungen statt Prosa-Erinnerungen. Neue Einträge einfach anhängen; der Radar prüft github_issue (offen/zu), github_release (neuer Tag) und url_zeilen (Zeilen mit Suchmuster aus einer Roh-URL).",
"_hinweis": "Living Watchlist des Trend-Radars. Diese Datei MUSS autonom vom Trend-Radar Agenten umgeschrieben werden, wenn er neue Quellen findet oder alte obsolet werden.",
"eintraege": [
{
"key": "mmq-prefill-gfx1151",
+2 -2
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Venv-Audit (monatlich, 17.07.2026): die Python-Nebendienste (Mem0, Voice) altern
# Venv-Audit (monatlich, 17.07.2026): die Python-Nebendienste (Voice) altern
# eingefroren — kein Auto-Update-Kanal deckt sie ab. Dieser Audit MELDET nur
# (bewusst kein Auto-Update: Python-Deps brechen gern):
# je venv: veraltete Pakete zählen (pip list --outdated) + bekannte
@@ -9,7 +9,7 @@
# idempotent je Monat). Läuft als Hermes-Cron --no-agent (stdout = Telegram).
set -uo pipefail
VENVS=("$HOME/.mem0/venv:mem0" "$HOME/.voice/venv:voice")
VENVS=("$HOME/.voice/venv:voice")
AUDIT_VENV="$HOME/.venv-audit"
HERMES="$HOME/.local/bin/hermes"
MONAT="$(date '+%Y-%m')"
+1 -1
View File
@@ -4,7 +4,7 @@
# Eingehängt als ExecStartPost=-/usr/local/bin/llama-swap-warmup.sh in llama-swap.
#
# `fast` = das Agent-Hirn (Qwen3.6-35B-A3B), `embed` = das Embedding-Modell
# (Qwen3-Embedding-0.6B, für Mem0/Gedächtnis). Die Augen (vision/VL-30B) sind seit 07.07.
# (Qwen3-Embedding-0.6B, für Gedächtnis). Die Augen (vision/VL-30B) sind seit 07.07.
# ON-DEMAND (User-Entscheid) und gehören NICHT mehr ins Warm-Set — die Lucy-App wärmt sie
# beim Start selbst an. Modelle werden NICHT automatisch zusammen geladen — jedes einzeln
# anstoßen, sonst bleibt es kalt.
+4 -4
View File
@@ -6,7 +6,7 @@
# neben dem Warm-Set liegt bewusst in DEINER Hand.
#
# WICHTIG (Speicher & Latenz-Trade-off):
# Der Standard-Worker ist Qwen3-Coder-Next (laedt klein und schnell neben dem Warm-Set).
# Der Standard-Worker ist die Rolle `coder` (laedt klein und schnell neben dem Warm-Set).
# Fuer initiale Planung kann "gpt-oss-120b" gerufen werden. Das Modell passt knapp in den
# Speicher (124 GB RAM), aber das on-demand Laden unterbricht Lucys 1-s-Latenz fuer ca.
# 15-30 Sekunden. Dieser Latenz-Hit wird fuer die Orchestrator-Planung in Kauf genommen.
@@ -16,10 +16,10 @@
# in Dateien macht der Manager (Hermes) danach mit seinen eigenen Tools.
#
# Nutzung: printf '%s' "$teiltask_mit_kontext" | WORKER_ROLE=build worker.sh > /tmp/out.py
# printf '%s' "$spec" | WORKER_MODEL=Qwen3-Coder-Next WORKER_ROLE=build worker.sh
# printf '%s' "$spec" | WORKER_MODEL=coder WORKER_ROLE=build worker.sh
#
# Env-Overrides:
# WORKER_MODEL echte llama-swap-ID (Default: Qwen3-Coder-Next). NICHT gpt-oss/heavy (siehe oben).
# WORKER_MODEL Rollen-Alias oder echte ID (Default: coder). NICHT gpt-oss/heavy (siehe oben).
# WORKER_ROLE build (Default) | refactor | plan | prose — waehlt das System-Raster.
# WORKER_SYS eigenes System-Raster (uebersteuert WORKER_ROLE).
# WORKER_MAXTOK max_tokens (Default 4000 — Code braucht Platz).
@@ -29,7 +29,7 @@ set -uo pipefail
# Achtung: llama-swap listet unter /v1/models die ECHTEN Modell-IDs, nicht die Gateway-Aliase
# (kein "heavy"/"coder"/"scout" hier — die echte ID nehmen).
ENDPOINT="${WORKER_ENDPOINT:-http://127.0.0.1:8080/v1/chat/completions}"
MODEL="${WORKER_MODEL:-Qwen3-Coder-Next}"
MODEL="${WORKER_MODEL:-coder}"
ROLE="${WORKER_ROLE:-build}"
MAXTOK="${WORKER_MAXTOK:-4000}"
TEMP="${WORKER_TEMP:-0.1}"
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env bash
# Referenz-Check (20.08.2026): prueft die fuenf Fertig-Kriterien der Messlatte.
#
# Zweck: "fertig" darf keine Auslegungssache sein. Derselbe Befehl fuer den Agenten
# (Selbstpruefung) und fuer die Auswertung. Siehe docs/aufgaben/referenzaufgabe-mem0-ausbau.md
#
# Aufruf: bash docs/aufgaben/referenz-check.sh [worktree-pfad]
# Exit: 0 = alle Kriterien gruen · 1 = mindestens eines rot
set -uo pipefail
W="${1:-$HOME/projekte/mc2-referenz}"
LINT="${LINT_RUFF:-$HOME/.venv-lint/bin/ruff}"
PY="${BACKEND_PY:-$HOME/mission-control-v2/backend/.venv/bin/python}"
SMOKE="${SMOKE_SH:-$HOME/mission-control-v2/deploy/self-smoke.sh}"
ROT=0
pruef() { # $1=Nummer $2=Beschreibung $3=Ergebnis(0/1) $4=Detail
if [ "$3" -eq 0 ]; then printf ' %s %-34s GRUEN %s\n' "$1" "$2" "${4:-}"
else printf ' %s %-34s ROT %s\n' "$1" "$2" "${4:-}"; ROT=1; fi
}
[ -d "$W" ] || { echo "ABBRUCH: Worktree nicht gefunden: $W"; exit 1; }
echo "=== Referenz-Check ($W) ==="
# 1 — Lint
if [ -x "$LINT" ]; then
out="$("$LINT" check "$W" 2>&1)"; rc=$?
pruef 1 "Lint sauber (ruff)" $rc "$(printf '%s' "$out" | tail -1)"
else
pruef 1 "Lint sauber (ruff)" 1 "ruff fehlt unter $LINT"
fi
# 2 — Backend laedt
out="$(cd "$W/backend" && "$PY" -c 'import app' 2>&1)"; rc=$?
pruef 2 "Backend importierbar (app)" $rc "$(printf '%s' "$out" | tail -1)"
# 3 — keine mem0-Reste (docs/ ausgenommen: Historie)
n="$(cd "$W" && grep -ril mem0 backend/ mcp/ deploy/ --include='*.py' --include='*.sh' 2>/dev/null | wc -l)"
[ "$n" -eq 0 ]; rc=$?
pruef 3 "Keine mem0-Reste" $rc "$n Datei(en) mit Treffern"
[ "$n" -gt 0 ] && (cd "$W" && grep -ril mem0 backend/ mcp/ deploy/ --include='*.py' --include='*.sh' 2>/dev/null | sed 's/^/ /')
# 3b — der Sidecar-Ordner selbst
if [ -d "$W/mem0_service" ]; then pruef "3b" "Ordner mem0_service entfernt" 1 "liegt noch da"
else pruef "3b" "Ordner mem0_service entfernt" 0; fi
# 4 — Selbsttest (laeuft gegen das LAUFENDE System, nicht gegen den Worktree)
if [ -r "$SMOKE" ]; then
out="$(timeout 540 bash "$SMOKE" 2>&1)"; rc=$?
pruef 4 "Selbsttest self-smoke" $rc "$(printf '%s' "$out" | grep -cE '\bPASS\b') PASS / $(printf '%s' "$out" | grep -cE '\bFAIL\b') FAIL"
else
pruef 4 "Selbsttest self-smoke" 1 "nicht lesbar: $SMOKE"
fi
# 5 — MC2 lebt
curl -s -m 8 localhost:9001/api/health 2>/dev/null | grep -q '"status":"ok"'; rc=$?
pruef 5 "MC2 gesund" $rc
echo
if [ $ROT -eq 0 ]; then echo " ERGEBNIS: FERTIG — alle Kriterien gruen."
else echo " ERGEBNIS: NICHT fertig — mindestens ein Kriterium ist rot."; fi
exit $ROT
@@ -0,0 +1,133 @@
# Referenzaufgabe: Mem0-Sidecar restlos ausbauen
> **Zweck dieser Datei:** Messlatte für den Stack-Umbau (Stufe 2, 20.08.2026). Dieselbe Aufgabe wird
> mehrfach mit verschiedenen Modellen gefahren. Gemessen wird **nicht** Tokens pro Sekunde, sondern:
> *Wurde sie fertig? Wie lange? Wie viele Züge?*
> Alle Pfade und Befehle unten sind am 20.08.2026 auf der Box **nachgemessen**, nicht geraten.
## Hintergrund
Mem0 wurde im August abgelöst (`bf145a3`, `eb4bcfc`, `2286879`). Der Rückbau blieb unvollständig:
Der Sidecar-Ordner liegt noch im Baum, MC2 fragt weiterhin einen toten Port ab.
Gemessen: `curl http://127.0.0.1:8765/health`**keine Antwort**. Kein systemd-Dienst dafür,
`/srv/models/mem0` bereits entfernt. Es ist also wirklich tot, nicht nur gerade aus.
## Arbeitsplatz
**Isolierter Worktree — NICHT das Live-Deployment:**
```
~/projekte/mc2-referenz Zweig: referenz/mem0-ausbau
```
`~/mission-control-v2` bleibt auf `main` und bedient weiter den laufenden Dienst auf `:9001`.
**Dort nichts ändern.**
## ‼️ Arbeitsweise — zuerst lesen
**Arbeite Datei für Datei. Lies HÖCHSTENS zwei Dateien, bevor du die erste änderst.**
Nach jeder geänderten Datei gehst du zur nächsten. **Kein Gesamtplan, keine Vollinventur vorab** — du
darfst jederzeit nachlesen, wenn du etwas brauchst. Die Liste unten ist eine Landkarte, keine
Leseliste.
*Warum das hier so deutlich steht:* Im ersten Anlauf (20.08., über Nacht) hat der Agent in sechs
Sitzungen ~336 Nachrichten und ~360.000 Tokens verbraucht, 19 Dateien gelesen — und **nicht eine
einzige Zeile geändert**. Er lief fünfmal in dasselbe Muster: „Ich habe jetzt das vollständige Bild,
jetzt lese ich nur noch die restlichen Dateien, *bevor ich anfange*." Dann war das Zug-Limit
erreicht. Sein eigenes Fazit: *„Iterationslimit erreicht, bevor ich die erste Zeile geändert habe."*
Eine halbfertige Änderung ist wertvoller als eine vollständige Analyse ohne Änderung.
## Auftrag
Entferne die Mem0-Reste vollständig, ohne etwas anderes kaputtzumachen.
**17 Dateien haben Treffer** (gemessen 20.08., selbst nachprüfen statt blind vertrauen):
```
backend/steward.py deploy/stack-postcheck.sh
backend/config.py deploy/venv-audit.sh
backend/routers/voice.py deploy/agent-hooks/box-steckbrief-inject.sh
backend/routers/system.py deploy/warmup.sh
backend/routers/zeitmaschine.py deploy/autoupdate.sh
backend/services/sentry.py deploy/hermes-postcheck.sh
backend/services/voice_metrics.py deploy/restore.sh
backend/services/backup.py deploy/backup.sh
mcp/mcp_mc.py
```
Dazu der Ordner `mem0_service/` (app.py, migrate.py, requirements.txt, smoke_test.py).
> ‼️ **Der eigentliche Prüfstein:** Wenn eine API-Antwort ein Feld wie `mem0_reachable` liefert, das
> das Frontend liest, darf es nicht einfach verschwinden — sonst bricht die Oberfläche. Entweder das
> Frontend mit anpassen oder das Feld sauber entfernen. Wer nur `grep mem0` laufen lässt und alles
> wegwirft, produziert eine Fassade.
## Fertig-Kriterien
Ausgangswert am 20.08.: **1, 2, 4, 5 sind bereits grün** — sie sind Schutzgeländer, nicht die Arbeit.
Nur Kriterium 3 ist offen. Wer 3 erfüllt und dabei 1/2/4/5 kaputtmacht, hat versagt.
| # | Prüfung | Befehl | Ausgangswert |
|---|---|---|---|
| 1 | Lint sauber | `cd ~/projekte/mc2-referenz && ~/.venv-lint/bin/ruff check .` | ✅ *All checks passed* |
| 2 | Backend lädt | `cd ~/projekte/mc2-referenz/backend && ~/mission-control-v2/backend/.venv/bin/python -c "import app"` | ✅ ok |
| 3 | **keine mem0-Reste** | `cd ~/projekte/mc2-referenz && grep -ril mem0 backend/ mcp/ deploy/ --include='*.py' --include='*.sh' \| wc -l` | ❌ **17** → muss **0** werden |
| 4 | Selbsttest grün | `bash ~/mission-control-v2/deploy/self-smoke.sh` | ✅ grün |
| 5 | MC2 gesund | `curl -s localhost:9001/api/health` | ✅ `"status":"ok"` |
Treffer in `docs/` sind erlaubt — das ist Historie und darf stehenbleiben.
Selbstprüfung mit einem Befehl:
```bash
bash ~/projekte/mc2-referenz/docs/aufgaben/referenz-check.sh
```
> Der Prüfbefehl liegt bewusst unter `docs/aufgaben/` und **nicht** unter `deploy/`. Im ersten Anlauf
> lag er in `deploy/`, enthielt selbst siebenmal das Wort „mem0" (er sucht ja danach) und zählte sich
> damit in Kriterium 3 mit — das Kriterium war unerfüllbar. In `docs/` wird er nicht durchsucht.
## Randbedingungen
- ‼️ **In einer FRISCHEN Sitzung starten**, nicht in einer fortgesetzten. Beim ersten Anlauf hat der
Desktop eine Sitzung vom Vortag weitergeführt und deren Kontext mitgeschleppt — die Messung war
dadurch wertlos. Im Desktop also einen **neuen Chat** anlegen, nicht den alten öffnen.
- **Nicht pushen.** Der Zweig bleibt lokal, damit der Lauf wiederholbar ist.
- **Nichts außerhalb des Worktrees anfassen** — kein `/etc`, keine systemd-Dienste, kein Neustart.
- Wenn ein Kriterium nicht erfüllbar ist: **ehrlich sagen und aufhören.** Ein klares
„Kriterium 3 scheitert an X" ist wertvoller als ein beschönigter Abschluss.
## Zurücksetzen zwischen den Läufen
```bash
cd ~/mission-control-v2
git worktree remove --force ~/projekte/mc2-referenz
git branch -D referenz/mem0-ausbau
git worktree add -b referenz/mem0-ausbau ~/projekte/mc2-referenz main
```
## Messprotokoll
| Lauf | Aufbau | Fertig? | Wanduhr | Züge | Bemerkung |
|---|---|---|---|---|---|
| — | *Fehlversuch 20.08. 21:10* | ✗ | 76 min, abgebrochen | 8 | Kriterium 3 unerfüllbar + fortgesetzte Alt-Sitzung. **Verwertbar trotzdem:** 19.899 Ausgabe-Tokens, davon nur ~2.600 Zeichen sichtbar → **>95 % unsichtbares Nachdenken**. Kompression feuerte bereits. |
| **A** | Qwen3.8-27B, `medium`, `max_turns: 40`, ohne Arbeitsanweisung | ✗ | über Nacht | 6 Sitzungen, ~336 Nachrichten | **0 Dateien geschrieben.** Analyse-Schleife, Zug-Limit erreicht. Werkzeug `file` war die ganze Zeit verfügbar, wurde nie benutzt. ~360k Tokens. Inhaltlich gut: fand `mem0_ms` im Frontend **und** einen toten `memory_svc`-Import in `steward.py`. |
| **A2** | dasselbe, **+ Arbeitsanweisung oben**, `max_turns: 80` | | | | ← **als Nächstes.** Prüft, ob die Schleife am Auftrag lag |
| D | Qwen3.8-27B, `reasoning_effort: low` | | | | Nächster Hebel, falls A2 auch nur liest |
| B | Qwen3.8-27B **+ DFlash-2** | | | | Entwurfsmodell liegt bereit |
| C | Qwen3.6-35B-A3B (`fast`, 95,7 t/s) | | | | SWE-bench 73,4 |
> ★★ **Die Lehre aus Lauf A:** Das ist **kein Tempo-Problem.** Mit 95 t/s statt 12,6 wäre derselbe
> Lauf genauso gescheitert — nur schneller. Deshalb kommen A2 und D **vor** den Modellwechseln B und C.
### Was der Fehlversuch schon gezeigt hat
Pro Zug erzeugte das Modell ~2.487 Tokens, sichtbar wurden davon 181451 Zeichen. Bei 12,6 t/s sind
das ~26 Minuten reine Erzeugung für ~2.600 Zeichen Text. **Der Engpass ist Denk-Aufwand × Bandbreite,
nicht das Harness.** Darum Lauf D vor B und C: er prüft den billigsten Hebel zuerst.
Inhaltlich war das Modell gut — es fand von selbst den Prüfstein (`mem0_ms``memory_ms` im Frontend),
statt blind zu löschen.
+30 -36
View File
@@ -1,7 +1,7 @@
# Stack-Wahrheiten — Infrastruktur, Dienste, Modelle
_Live gegen die Box verifiziert am **10.07.2026** (Dienste-Liste, llama-swap-Config, hermes
--version, /api/health). Bei Widerspruch zu älteren Docs (SAVEPOINT.md, docs/memory/) gewinnt
_Live gegen die Box verifiziert am **19.08.2026** (Dienste-Liste, llama-swap-Config, hermes
--version v0.20.4, llama.cpp b10502, /api/health). Bei Widerspruch zu älteren Docs gewinnt
diese Datei. Wer sie ändert: erst messen, dann schreiben._
## Maschinen & Zugänge
@@ -24,55 +24,49 @@ diese Datei. Wer sie ändert: erst messen, dann schreiben._
- **Wissens-Vault:** `~/wissens-vault/` auf der Box (eigenes git; Lucys Lernschicht:
Träume, Radar-Funde, **Eigenbau-Landkarte**, Skill-Kandidaten). Im MC2-UI als Wissens-Tab.
## Dienste auf der Box (live 10.07.2026)
## Dienste auf der Box (live 19.08.2026)
| Dienst | Port | Zweck |
|---|---|---|
| `llama-swap` (System-Dienst) | `:8080` | Modell-Router; Engine = llama.cpp **Vulkan/RADV** (`/opt/llamacpp-vulkan`) |
| `mission-control-2` (User) | `:9001` | MC2 Backend+Frontend; `/v1` = OpenAI-Gateway; `/hermes-ui/` = Desktop-Gateway-Proxy |
| `hermes-gateway` (User) | `:8642` | Hermes Agent API (Lucys Lane) |
| `hermes-builtin-ui` (User) | `:9119` (loopback) | Eingebaute Hermes-GUI; LAN-Zugang NUR via MC2-Proxy `/hermes-ui/` (statischer Token im systemd-Drop-in) |
| `voice-service` (User) | `:8650` | STT (Parakeet/Whisper) + Turn-Check für Lucy |
| `llama-swap` (System-Dienst) | `:8080` | Modell-Router; Engine = llama.cpp **Vulkan/RADV** (`/opt/llamacpp-vulkan`, b10502) |
| `mission-control-2` (User) | `:9001` | MC2 Cockpit (FastAPI + React Frontend) & Steuerpult |
| `mc2-gateway` (User) | `:9010` | `/v1`-Datenpfad (model:auto Routing + native Bildweiche) |
| `mc2-steward` (User) | — | Hintergrundwächter (Re-Warm, Health-Sentry, Mem0-Dedupe) |
| `hermes-gateway` (User) | `:8642` | Hermes Agent Core API (v0.20.4, Bot Mode) |
| `hermes-builtin-ui` (User) | `:9119` (loopback) | Eingebaute Hermes-GUI; LAN-Zugang via MC2-Proxy `/hermes-ui/` |
| `mem0-service` (User) | `:8765` | Semantischer Chroma-Gedächtnis-Sidecar |
| `voice-service` (User) | `:8650` | STT (faster-whisper) + TTS (Piper/Chatterbox) |
| `box-console` (User) | `:7682` | ttyd-Login-Shell hinter MC2-Proxy |
**Stillgelegt (Diät 09.07.):** `hermes-terminal` (:7681) und `hermes-webui` (:8787) — nicht
wiederbeleben. ⚠️ User-Units per SSH nur sichtbar mit
`export XDG_RUNTIME_DIR=/run/user/$(id -u)` vor `systemctl --user`.
**Stillgelegt:** `hermes-terminal` (:7681), `hermes-webui` (:8787) und alter `governor` (:8100).
⚠️ User-Units per SSH nur sichtbar mit `export XDG_RUNTIME_DIR=/run/user/$(id -u)` vor `systemctl --user`.
## Modell-Stack (llama-swap, live 10.07.2026)
## Modell-Stack (llama-swap, live 19.08.2026)
**Warm-Set (Gruppe `brains`, swap:false, persistent:true, alle ttl 0):**
**Warm-Set (Gruppe `brains`, swap:false, persistent:false, alle ttl 0):**
Qwen3.6-35B-A3B + Qwen3-Embedding-0.6B + Qwen3-Reranker-0.6B. Sonst NICHTS dauerhaft warm.
| Alias/Rolle | Modell | ttl | Notizen |
|---|---|---|---|
| `hermes` + `fast` | Qwen3.6-35B-A3B (UD-Q4_K_M, MTP) | 0 | Agent-Hirn. `-c 131072 --parallel 2`**65536/Slot**, KV **q8_0**, `--spec-draft-n-max 3` |
| `reranker` | Qwen3-Reranker-0.6B (q8_0, **Mungert-GGUF**) | 0 | Gedächtnis-Sortierer (`/v1/rerank`); Community-GGUFs liefern oft Nullscores |
| `coder` | Qwen3-Coder-Next (Q4_K_M) | 600 | 131k ctx; Werkstatt-/Orchestrator-Worker + IDE „bauen"; auch `delegation.model` |
| `heavy` | gpt-oss-120b (mxfp4) | 600 | 32k ctx! Planer/Chef-Gutachter/Radar-Analyst — nur Ein-Schuss, lädt seit der LLM-Diät in ~28 s |
| `vision` | Qwen3-VL-30B-A3B (Q4_K_M) | 900 | Augen, ON-DEMAND; Lucy-App wärmt beim Start + alle 10 min |
| `kritiker` | GLM-4.7-Flash (UD-Q4_K_XL) | 600 | Fremdblick-Prosa + Chef-Gutachter-Vertretung |
| `scout` | GLM-4.6V-Flash (Q4_K_M) | 300 | Vision+Tools-Allrounder |
| `hermes` + `fast` | Qwen3.6-35B-A3B (UD-Q4_K_M, DFlash) | 0 | Agent-Hirn. `-c 131072 --parallel 2`**65536/Slot**, KV **q8_0**, ~7090 t/s |
| `coder` | Qwen3-Coder-Next (Q4_K_M) | 5400 | 131k ctx; Autonomer Coder & Builder (80B MoE, 51,5 t/s) |
| `debugger` / `doctor` | Muse-Glimmer-30B (Q4_K_XL, DFlash) | 600 | 65k ctx; Runtime-Diagnostik & Fehler-Debugger (Multimodal) |
| `kritiker` | Devstral-Small-2-24B (Q4_K_M) | 1800 | 16k ctx Deckel; Fremdblick-Code-Reviewer gegen Halluzinationen |
| `vision` | Qwen3-VL-30B-A3B (Q4_K_M) | 900 | Multimodal-Augen (On-Demand) |
| `scout` | GLM-4.6V-Flash (Q4_K_M) | 300 | Schneller Tool- und Vision-Allrounder |
| `heavy` | gpt-oss-120b (mxfp4) | 600 | 32k ctx; Chef-Gutachter (nur nachts / gezielter On-Demand Call) |
| `dense-planer` | Qwen3.8-27B (Q4_K_M, BF16 mmproj) | 300 | 65k ctx; Dichtes 27B-Modell für tiefes Planen & Multimodalität |
| `reranker` | Qwen3-Reranker-0.6B (q8_0, Mungert) | 0 | Gedächtnis-Sortierer (`/v1/rerank`) |
| `embed` | Qwen3-Embedding-0.6B (f16) | 0 | Vektorisierung für Chroma / Mem0 |
Config: `/etc/llama-swap/config.yaml` (sudo zum Schreiben). Qwen3.5-122B und VL-8B sind
ENTFERNT (07.07.) — die alten Rollback-Pfade existieren nicht mehr.
**Merkregeln:** Schutz-Key heißt `persistent:` (nicht `persist:`) · jedes brains-Mitglied
MUSS ttl:0 haben · nach JEDEM Config-Reload ist das Warm-Set leer → hermes/embed (+reranker)
per Mini-Request anwärmen (direkt curlen ist zuverlässiger als warmup.sh).
Config: `/etc/llama-swap/config.yaml` (sudo zum Schreiben).
## Hermes (Agent)
- **v0.18.2** (git-Install `~/.hermes/hermes-agent`, Py 3.11), Config `~/.hermes/config.yaml`
(IMMER Backup vor Änderung), Persona `~/.hermes/SOUL.md` = **Lucy** (alle Kanäle, Anrede
„Commander").
- Hirn: `model.default` UND `model.model` = `fast`. `delegation.model: coder` (seit 10.07.).
- `approvals.mode: smart`, `approvals.cron_mode: deny` · `platform_toolsets.api_server` =
Voice-Diät (NICHT mästen!) · cli-Bucket = volles Coding-Set (auch Hermes Desktop).
- MCP-Server (aus `~/mission-control-v2/mcp/`): mission-control-memory, mission-control-stack
(mcp_mc.py, 23 Tools), hermes-pc-control, hermes-web-fetch, mcp_voice (dünn, für Voice-Lane).
- **Hermes Desktop am PC:** lokales PC-Hermes mit Box-Gateway als Hirn (`:9001/v1`), Profil
`pc` für F:\-Arbeit. Remote-Gateway der Box =
`http://192.168.178.151:9001/hermes-ui`.
- **v0.20.4** (git-Install `~/.hermes/hermes-agent`), Config `~/.hermes/config.yaml`, Persona `~/.hermes/SOUL.md` = **Lucy**.
- **Bot Mode:** Unterstützt Multi-Bot-Roster (`Lucy` als Haupt-Agentin, `Coder` mit `Qwen3-Coder-Next`, `Debugger` mit `Muse-Glimmer-30B`).
- `approvals.mode: smart`, `approvals.cron_mode: deny`.
- MCP-Server (aus `~/mission-control-v2/mcp/`): mission-control-memory, mission-control-stack, hermes-pc-control, hermes-web-fetch, mcp_voice.
## Automatik-Fahrplan (was nachts von allein läuft)
+2
View File
@@ -1,5 +1,7 @@
# ÜBERGABE: Drei-Welten-Umbau (19.07.2026) — letzter Claude-Stand
> [!WARNING]
> **UPDATE (August 2026):** Der hier beschriebene "Zed + OpenCode CLI"-Workflow wurde durch das "Zero Middle-Layers"-Prinzip ersetzt. Coding passiert jetzt zu 100% in der **OpenCode Desktop** Agentic IDE via MCP. Die technischen Entscheidungen zur Box und zu Gitea bleiben jedoch gültig.
**Dieses Dokument ist die finale Übergabe der externen Claude-Sessions an die Box.**
Alles Wichtige aus dem Umbau vom 19.07. steht HIER (nicht nur im Claude-Gedächtnis,
das mit dem Abo-Ende unlesbar wird). Bei Widerspruch zu älteren Dokumenten gilt dieses.
@@ -1,4 +1,4 @@
import{c as A,K as P,l as M,b as B,u as D,r as z,j as e,M as T,e as a,N as g,O as b,Q as W,x as f,W as j,R as G,X as I,C as N,g as y,q as i}from"./index-C_u0QVqX.js";/**
import{c as A,I as P,f as M,k as B,u as D,r as z,j as e,J as T,b as a,K as g,M as b,N as W,t as f,W as j,O as G,X as I,C as N,n as y,q as i}from"./index-0Xcu6PnS.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
import{c as d,ab as S,r as h,j as e,ac as M,L as p,a2 as z,a7 as C,ad as D,ae as A,e as B,af as Z,u as E,b as L,g as k,q}from"./index-C_u0QVqX.js";import{R as j}from"./rotate-ccw-CkmgZ3P_.js";/**
import{c as d,aj as S,r as h,j as e,ak as M,L as p,a0 as z,a5 as C,al as D,am as A,b as B,an as Z,u as E,k as L,n as k,q}from"./index-0Xcu6PnS.js";import{R as j}from"./rotate-ccw-CBM-YWkV.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
import{u,ah as m,k as x,j as s,y as n,ai as b,b as p,n as f,q as h}from"./index-0Xcu6PnS.js";function y(){const d=u(),{data:t=[]}=m(),{showAlert:l,dialogElement:o}=x();async function i(e){try{await f(`/api/jobs/${e}/cancel`,{method:"POST"}),d.invalidateQueries({queryKey:h.jobs})}catch(c){l("Fehler",c.message)}}const a=t.filter(e=>e.state==="running"||e.state==="queued"),r=t.filter(e=>e.state!=="running"&&e.state!=="queued").slice(-3);return a.length===0&&r.length===0?null:s.jsxs("div",{className:"space-y-3 mc-card p-4",children:[s.jsx("div",{className:"text-[11px] font-semibold text-muted-foreground uppercase tracking-wider",children:"Aktive Downloads"}),a.map(e=>s.jsxs("div",{className:"space-y-1.5 p-3 rounded-xl bg-background/20 border border-border/40",children:[s.jsxs("div",{className:"flex justify-between items-center text-xs",children:[s.jsx("span",{className:"font-semibold truncate max-w-[250px]",children:e.label}),s.jsxs("div",{className:"flex items-center gap-3",children:[s.jsxs("span",{className:"text-muted-foreground font-mono",children:[e.progress??0,"% • ",n(e.done_bytes),"/",n(e.total_bytes),e.eta_s?` • ETA ${b(e.eta_s)}`:""]}),s.jsx("button",{onClick:()=>i(e.id),className:"text-[10px] text-red-400 hover:text-red-300 font-semibold border border-red-500/25 bg-red-500/5 px-2 py-0.5 rounded transition-all cursor-pointer",children:"Abbrechen"})]})]}),s.jsx("div",{className:"h-1.5 overflow-hidden rounded-full bg-muted",children:s.jsx("div",{className:"h-full rounded-full bg-primary transition-all duration-500",style:{width:`${e.progress??0}%`}})})]},e.id)),r.map(e=>s.jsxs("div",{className:"flex justify-between items-center text-xs text-muted-foreground px-1",children:[s.jsx("span",{className:"truncate",children:e.label}),s.jsx("span",{className:p("font-semibold text-[10px] px-1.5 py-0.5 rounded uppercase font-mono",e.state==="done"?"bg-emerald-500/10 text-emerald-400":"bg-amber-500/10 text-amber-400"),children:e.state})]},e.id)),o]})}export{y as J};
@@ -1 +1 @@
import{K as b,u as f,N as h,r as d,j as e,e as c,M as g,T as p,U as j,V as N,g as v,Y as w,q as x}from"./index-C_u0QVqX.js";function y(){const{data:s}=b(),m=f(),r=s!=null&&s.box_console_url?h(s.box_console_url):void 0,a=s==null?void 0:s.box_console_reachable,[l,o]=d.useState(!1),[i,n]=d.useState("");async function u(){o(!0),n("");try{const t=await v("/api/maintenance/restart",{method:"POST",body:JSON.stringify({service:"box-console"})});n(t.ok?"Neu gestartet — einen Moment, dann lädt das Terminal.":`Fehlgeschlagen: ${t.err||"Unbekannter Fehler"}`),w(m,x.agentStatus,x.services)}catch(t){n(`Fehlgeschlagen: ${(t==null?void 0:t.message)||t}`)}finally{o(!1)}}return e.jsxs("div",{className:"flex h-full flex-col gap-4",children:[e.jsxs("div",{className:"flex flex-wrap items-end justify-between gap-3",children:[e.jsxs("div",{children:[e.jsx("h1",{className:"bg-gradient-to-r from-foreground via-foreground to-primary bg-clip-text font-space text-2xl font-bold tracking-tight text-transparent",children:"Konsole"}),e.jsx("p",{className:"flex items-center gap-2 text-sm text-muted-foreground",children:"Direkte Shell auf der Box — wie ein SSH-Fenster, mitten im Browser."})]}),e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsxs("span",{className:"flex items-center gap-1.5 text-[11px] font-medium text-muted-foreground",children:[e.jsx("span",{className:c("h-2 w-2 rounded-full",a?"bg-emerald-500 animate-pulse":"bg-amber-500")}),a?"online":"offline"]}),r&&e.jsxs("a",{href:r,target:"_blank",rel:"noopener",className:"flex h-8 items-center gap-1.5 rounded-lg border border-border/60 bg-background/20 px-3 text-xs font-semibold text-muted-foreground transition-all hover:border-primary/50 hover:text-foreground",children:[e.jsx(g,{className:"h-3.5 w-3.5"})," In neuem Tab"]})]})]}),r?e.jsxs("div",{className:"relative min-h-[58vh] flex-1 overflow-hidden rounded-2xl border border-border/60 bg-black/50 shadow-lg shadow-black/25",children:[a===!1&&e.jsxs("div",{className:"absolute inset-0 z-10 flex flex-col items-center justify-center gap-3 bg-black/70 text-center",children:[e.jsx(p,{className:"h-8 w-8 text-amber-400"}),e.jsx("div",{className:"text-sm font-semibold text-amber-300",children:"Konsole nicht erreichbar"}),e.jsxs("p",{className:"max-w-sm text-[11px] leading-normal text-muted-foreground",children:["Der Terminal-Dienst (",e.jsx("code",{className:"font-mono text-primary",children:"box-console"}),") läuft gerade nicht."]}),e.jsxs("button",{onClick:u,disabled:l,className:"flex h-9 items-center gap-1.5 rounded-lg border border-amber-500/40 bg-amber-500/10 px-4 text-[11px] font-bold uppercase tracking-wide text-amber-300 transition-all hover:bg-amber-500/20 cursor-pointer disabled:opacity-50",children:[e.jsx(j,{className:c("h-3.5 w-3.5",l&&"animate-spin")})," Dienst neu starten"]}),i&&e.jsx("p",{className:"max-w-sm text-[11px] text-muted-foreground",children:i})]}),e.jsx("iframe",{src:r,title:"Box-Konsole",className:"h-full w-full border-0",style:{minHeight:"58vh"}})]}):e.jsxs("div",{className:"flex min-h-[58vh] flex-1 items-center justify-center rounded-2xl border border-border/60 bg-background/20 text-xs text-muted-foreground",children:[e.jsx(N,{className:"mr-2 h-4 w-4"})," Lade Konsole…"]})]})}export{y as KonsoleView};
import{I as b,u as f,K as h,r as d,j as e,b as c,J as g,T as p,R as j,Q as N,n as v,U as w,q as x}from"./index-0Xcu6PnS.js";function y(){const{data:s}=b(),m=f(),r=s!=null&&s.box_console_url?h(s.box_console_url):void 0,a=s==null?void 0:s.box_console_reachable,[l,o]=d.useState(!1),[i,n]=d.useState("");async function u(){o(!0),n("");try{const t=await v("/api/maintenance/restart",{method:"POST",body:JSON.stringify({service:"box-console"})});n(t.ok?"Neu gestartet — einen Moment, dann lädt das Terminal.":`Fehlgeschlagen: ${t.err||"Unbekannter Fehler"}`),w(m,x.agentStatus,x.services)}catch(t){n(`Fehlgeschlagen: ${(t==null?void 0:t.message)||t}`)}finally{o(!1)}}return e.jsxs("div",{className:"flex h-full flex-col gap-4",children:[e.jsxs("div",{className:"flex flex-wrap items-end justify-between gap-3",children:[e.jsxs("div",{children:[e.jsx("h1",{className:"bg-gradient-to-r from-foreground via-foreground to-primary bg-clip-text font-space text-2xl font-bold tracking-tight text-transparent",children:"Konsole"}),e.jsx("p",{className:"flex items-center gap-2 text-sm text-muted-foreground",children:"Direkte Shell auf der Box — wie ein SSH-Fenster, mitten im Browser."})]}),e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsxs("span",{className:"flex items-center gap-1.5 text-[11px] font-medium text-muted-foreground",children:[e.jsx("span",{className:c("h-2 w-2 rounded-full",a?"bg-emerald-500 animate-pulse":"bg-amber-500")}),a?"online":"offline"]}),r&&e.jsxs("a",{href:r,target:"_blank",rel:"noopener",className:"flex h-8 items-center gap-1.5 rounded-lg border border-border/60 bg-background/20 px-3 text-xs font-semibold text-muted-foreground transition-all hover:border-primary/50 hover:text-foreground",children:[e.jsx(g,{className:"h-3.5 w-3.5"})," In neuem Tab"]})]})]}),r?e.jsxs("div",{className:"relative min-h-[58vh] flex-1 overflow-hidden rounded-2xl border border-border/60 bg-black/50 shadow-lg shadow-black/25",children:[a===!1&&e.jsxs("div",{className:"absolute inset-0 z-10 flex flex-col items-center justify-center gap-3 bg-black/70 text-center",children:[e.jsx(p,{className:"h-8 w-8 text-amber-400"}),e.jsx("div",{className:"text-sm font-semibold text-amber-300",children:"Konsole nicht erreichbar"}),e.jsxs("p",{className:"max-w-sm text-[11px] leading-normal text-muted-foreground",children:["Der Terminal-Dienst (",e.jsx("code",{className:"font-mono text-primary",children:"box-console"}),") läuft gerade nicht."]}),e.jsxs("button",{onClick:u,disabled:l,className:"flex h-9 items-center gap-1.5 rounded-lg border border-amber-500/40 bg-amber-500/10 px-4 text-[11px] font-bold uppercase tracking-wide text-amber-300 transition-all hover:bg-amber-500/20 cursor-pointer disabled:opacity-50",children:[e.jsx(j,{className:c("h-3.5 w-3.5",l&&"animate-spin")})," Dienst neu starten"]}),i&&e.jsx("p",{className:"max-w-sm text-[11px] text-muted-foreground",children:i})]}),e.jsx("iframe",{src:r,title:"Box-Konsole",className:"h-full w-full border-0",style:{minHeight:"58vh"}})]}):e.jsxs("div",{className:"flex min-h-[58vh] flex-1 items-center justify-center rounded-2xl border border-border/60 bg-background/20 text-xs text-muted-foreground",children:[e.jsx(N,{className:"mr-2 h-4 w-4"})," Lade Konsole…"]})]})}export{y as KonsoleView};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
import{c as s,j as e}from"./index-C_u0QVqX.js";/**
import{c as s,j as e}from"./index-0Xcu6PnS.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
+6
View File
@@ -0,0 +1,6 @@
var M=r=>{throw TypeError(r)};var O=(r,e,t)=>e.has(r)||M("Cannot "+t);var a=(r,e,t)=>(O(r,e,"read from private field"),t?t.call(r):e.get(r)),y=(r,e,t)=>e.has(r)?M("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(r):e.set(r,t),v=(r,e,t,n)=>(O(r,e,"write to private field"),n?n.call(r,t):e.set(r,t),t),j=(r,e,t)=>(O(r,e,"access private method"),t);import{a9 as B,aa as F,ab as E,ac as I,ad as P,u as A,r as f,ae as J,af as Q,c as T,ag as U,j as s,G as D,T as H,b as K,L,M as G,q as V,n as R}from"./index-0Xcu6PnS.js";import{J as _}from"./JobsBar-JgL4-cSI.js";var c,g,o,h,m,w,C,q,z=(q=class extends B{constructor(e,t){super();y(this,m);y(this,c);y(this,g);y(this,o);y(this,h);v(this,c,e),this.setOptions(t),this.bindMethods(),j(this,m,w).call(this)}bindMethods(){this.mutate=this.mutate.bind(this),this.reset=this.reset.bind(this)}setOptions(e){var n;const t=this.options;this.options=a(this,c).defaultMutationOptions(e),F(this.options,t)||a(this,c).getMutationCache().notify({type:"observerOptionsUpdated",mutation:a(this,o),observer:this}),t!=null&&t.mutationKey&&this.options.mutationKey&&E(t.mutationKey)!==E(this.options.mutationKey)?this.reset():((n=a(this,o))==null?void 0:n.state.status)==="pending"&&a(this,o).setOptions(this.options)}onUnsubscribe(){var e;this.hasListeners()||(e=a(this,o))==null||e.removeObserver(this)}onMutationUpdate(e){j(this,m,w).call(this),j(this,m,C).call(this,e)}getCurrentResult(){return a(this,g)}reset(){var e;(e=a(this,o))==null||e.removeObserver(this),v(this,o,void 0),j(this,m,w).call(this),j(this,m,C).call(this)}mutate(e,t){var n;return v(this,h,t),(n=a(this,o))==null||n.removeObserver(this),v(this,o,a(this,c).getMutationCache().build(a(this,c),this.options)),a(this,o).addObserver(this),a(this,o).execute(e)}},c=new WeakMap,g=new WeakMap,o=new WeakMap,h=new WeakMap,m=new WeakSet,w=function(){var t;const e=((t=a(this,o))==null?void 0:t.state)??I();v(this,g,{...e,isPending:e.status==="pending",isSuccess:e.status==="success",isError:e.status==="error",isIdle:e.status==="idle",mutate:this.mutate,reset:this.reset})},C=function(e){P.batch(()=>{var t,n,l,x,d,p,k,i;if(a(this,h)&&this.hasListeners()){const u=a(this,g).variables,N=a(this,g).context,S={client:a(this,c),meta:this.options.meta,mutationKey:this.options.mutationKey};if((e==null?void 0:e.type)==="success"){try{(n=(t=a(this,h)).onSuccess)==null||n.call(t,e.data,u,N,S)}catch(b){Promise.reject(b)}try{(x=(l=a(this,h)).onSettled)==null||x.call(l,e.data,null,u,N,S)}catch(b){Promise.reject(b)}}else if((e==null?void 0:e.type)==="error"){try{(p=(d=a(this,h)).onError)==null||p.call(d,e.error,u,N,S)}catch(b){Promise.reject(b)}try{(i=(k=a(this,h)).onSettled)==null||i.call(k,void 0,e.error,u,N,S)}catch(b){Promise.reject(b)}}}this.listeners.forEach(u=>{u(a(this,g))})})},q);function W(r,e){const t=A(),[n]=f.useState(()=>new z(t,r));f.useEffect(()=>{n.setOptions(r)},[n,r]);const l=f.useSyncExternalStore(f.useCallback(d=>n.subscribe(P.batchCalls(d)),[n]),()=>n.getCurrentResult(),()=>n.getCurrentResult()),x=f.useCallback((d,p)=>{n.mutate(d,p).catch(J)},[n]);if(l.error&&Q(n.options.throwOnError,[l.error]))throw l.error;return{...l,mutate:x,mutateAsync:l.mutate}}/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const X=T("Play",[["polygon",{points:"6 3 20 12 6 21 6 3",key:"1oa8hb"}]]);function ee(){const r=A(),{data:e,isLoading:t,isError:n}=U({queryKey:["skills-list"],queryFn:()=>R("/api/skills"),refetchInterval:5e3}),[l,x]=f.useState(null),[d,p]=f.useState(null),k=W({mutationFn:i=>R("/api/skills/run",{method:"POST",body:JSON.stringify({skill_name:i})}),onMutate:i=>{x(i),p(null)},onSuccess:(i,u)=>{x(null),p({name:u,ok:i.ok,msg:i.msg||i.err||"Unbekannter Fehler"}),r.invalidateQueries({queryKey:V.jobs}),r.invalidateQueries({queryKey:["skills-list"]})},onError:(i,u)=>{x(null),p({name:u,ok:!1,msg:String(i)})}});return s.jsxs("div",{className:"space-y-6",children:[s.jsx("div",{className:"flex flex-col sm:flex-row justify-between sm:items-center gap-4",children:s.jsxs("div",{children:[s.jsx("h1",{className:"text-2xl font-space font-bold tracking-tight bg-gradient-to-r from-foreground via-foreground to-primary bg-clip-text text-transparent",children:"Skills & Jobs"}),s.jsx("p",{className:"text-sm text-muted-foreground",children:"Löst autonome Agent-Skills manuell auf der Box aus. Die Ausführung erfolgt im Hintergrund durch Hermes."})]})}),s.jsx(_,{}),d&&s.jsxs("div",{className:K("p-4 rounded-lg border flex gap-3 items-start",d.ok?"bg-emerald-500/10 border-emerald-500/20 text-emerald-600 dark:text-emerald-400":"bg-red-500/10 border-red-500/20 text-red-600 dark:text-red-400"),children:[d.ok?s.jsx(D,{className:"h-5 w-5 mt-0.5 shrink-0"}):s.jsx(H,{className:"h-5 w-5 mt-0.5 shrink-0"}),s.jsxs("div",{children:[s.jsx("div",{className:"font-semibold text-sm",children:d.name}),s.jsx("div",{className:"text-sm opacity-90",children:d.msg})]})]}),t&&s.jsx("div",{className:"flex h-32 items-center justify-center",children:s.jsx(L,{className:"h-6 w-6 animate-spin text-muted-foreground"})}),n&&s.jsx("div",{className:"text-red-500 p-4 mc-card text-sm",children:"Fehler beim Laden der Skills. Ist das Backend erreichbar?"}),(e==null?void 0:e.skills)&&s.jsxs("div",{className:"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4",children:[e.skills.map(i=>s.jsxs("div",{className:"mc-card p-5 flex flex-col hover:border-primary/40 transition-colors group",children:[s.jsxs("div",{className:"flex items-start justify-between gap-3 mb-2",children:[s.jsxs("div",{className:"flex items-center gap-2 font-semibold text-base font-space",children:[s.jsx(G,{className:"h-5 w-5 text-primary/80"}),i.name]}),s.jsxs("button",{onClick:()=>k.mutate(i.name),disabled:l===i.name||i.running||k.isPending,className:K("shrink-0 h-8 px-3 rounded-md text-xs font-semibold uppercase tracking-wider flex items-center gap-1.5 transition-all cursor-pointer",l===i.name||i.running?"bg-sky-500/10 border border-sky-500/40 text-sky-400 cursor-not-allowed":"bg-primary/10 text-primary hover:bg-primary hover:text-primary-foreground group-hover:shadow-md group-hover:shadow-primary/20"),children:[l===i.name||i.running?s.jsx(L,{className:"h-3.5 w-3.5 animate-spin"}):s.jsx(X,{className:"h-3.5 w-3.5"}),l===i.name||i.running?"Läuft ":"Starten"]})]}),s.jsx("div",{className:"text-sm text-muted-foreground flex-1",children:i.description})]},i.name)),e.skills.length===0&&s.jsxs("div",{className:"col-span-full p-8 text-center text-muted-foreground mc-card border-dashed border-2",children:["Keine Skills im Ordner ",s.jsx("code",{className:"text-xs text-foreground bg-muted px-1.5 py-0.5 rounded",children:"deploy/skills"})," gefunden."]})]})]})}export{ee as SkillsView};
-4
View File
@@ -1,4 +0,0 @@
import{ag as y,r as x,g as L,j as e,L as v,S,aa as W,a2 as C,e as w,ah as E}from"./index-C_u0QVqX.js";import{F as M}from"./folder-open-CE5mT2_x.js";const D={"":"Allgemein",traeume:"Träume",muster:"Muster","skill-kandidaten":"Skill-Kandidaten","skill-kandidaten/beauftragt":"Skill-Kandidaten · beauftragt","skill-kandidaten/verworfen":"Skill-Kandidaten · verworfen"};function T(){const{data:o,isLoading:h}=y(),[l,f]=x.useState(null),[d,r]=x.useState(null),[u,i]=x.useState(!1),[a,p]=x.useState(""),t=x.useMemo(()=>(o==null?void 0:o.files)??[],[o]),m=x.useMemo(()=>{const s=new Map;for(const n of t)s.set(n.name.toLowerCase(),n.path);return s},[t]),g=x.useMemo(()=>{if(!a.trim())return t;const s=a.toLowerCase();return t.filter(n=>n.title.toLowerCase().includes(s)||n.path.toLowerCase().includes(s))},[t,a]),b=x.useMemo(()=>{const s=[];for(const n of g){const c=s.find(k=>k.dir===n.dir);c?c.files.push(n):s.push({dir:n.dir,files:[n]})}return s.sort((n,c)=>n.dir===""?-1:c.dir===""?1:n.dir.localeCompare(c.dir))},[g]);x.useEffect(()=>{if(!l&&t.length){const s=t.find(n=>n.path.toLowerCase()==="index.md");f((s==null?void 0:s.path)??t[0].path)}},[t,l]),x.useEffect(()=>{l&&(i(!0),L(`/api/wissen/datei?pfad=${encodeURIComponent(l)}`).then(r).catch(()=>r({path:l,content:"*(Notiz nicht ladbar)*",mtime:0})).finally(()=>i(!1)))},[l]);const j=s=>{const n=m.get(s.toLowerCase());n&&f(n)};return e.jsxs("div",{className:"space-y-5",children:[e.jsxs("div",{children:[e.jsx("h1",{className:"bg-gradient-to-r from-foreground via-foreground to-primary bg-clip-text font-space text-2xl font-bold tracking-tight text-transparent",children:"Wissen"}),e.jsx("p",{className:"text-sm text-muted-foreground",children:"Was sich die Box nachts erschließt — Traum-Notizen, Muster und Skill-Ideen aus dem Wissens-Vault, verlinkt wie ein Wiki."})]}),o&&!o.available&&e.jsx("div",{className:"rounded-2xl border border-amber-500/30 bg-amber-500/5 p-4 text-xs text-amber-300",children:"Der Wissens-Vault liegt auf der Box (~/wissens-vault) — im lokalen Dev-Modus nicht verfügbar."}),h&&e.jsxs("div",{className:"flex items-center gap-2 rounded-2xl border border-border/60 bg-card/30 p-6 text-xs text-muted-foreground",children:[e.jsx(v,{className:"h-4 w-4 animate-spin"})," Vault wird geladen …"]}),(o==null?void 0:o.available)&&t.length===0&&e.jsx("div",{className:"rounded-2xl border border-dashed border-border/60 bg-card/20 p-8 text-center text-xs text-muted-foreground",children:"Noch keine Notizen. Der nächtliche Traum (03:15) legt sie an — morgen früh steht hier die erste."}),t.length>0&&e.jsxs("div",{className:"flex flex-col gap-4 lg:flex-row",children:[e.jsxs("aside",{className:"w-full shrink-0 space-y-3 lg:w-72",children:[e.jsxs("div",{className:"relative",children:[e.jsx("input",{value:a,onChange:s=>p(s.target.value),type:"search",placeholder:"Notizen durchsuchen…","aria-label":"Notizen durchsuchen",className:"h-9 w-full rounded-lg border border-border/60 bg-card/45 pl-8 pr-3 text-xs text-foreground outline-none focus:ring-1 focus:ring-primary/50"}),e.jsx(S,{className:"absolute left-2.5 top-2.5 h-3.5 w-3.5 text-muted-foreground"})]}),e.jsx("div",{className:"max-h-[70vh] space-y-3 overflow-y-auto pr-1 scrollbar-thin",children:b.map(({dir:s,files:n})=>e.jsxs("div",{children:[e.jsxs("p",{className:"mb-1 flex items-center gap-1 text-[10px] font-bold uppercase tracking-wider text-muted-foreground/60",children:[e.jsx(M,{className:"h-3 w-3"})," ",D[s]??s]}),e.jsx("div",{className:"space-y-0.5",children:n.map(c=>e.jsxs("button",{onClick:()=>f(c.path),className:w("flex w-full items-center gap-1.5 rounded-lg px-2 py-1.5 text-left text-xs transition-all cursor-pointer",l===c.path?"bg-primary/15 text-primary":"text-muted-foreground hover:bg-accent hover:text-foreground"),children:[e.jsx(W,{className:"h-3.5 w-3.5 shrink-0"}),e.jsx("span",{className:"truncate",title:c.title,children:c.name}),c.neu&&e.jsxs("span",{className:"ml-auto flex items-center gap-0.5 rounded bg-violet-500/15 px-1 py-0.5 text-[8px] font-bold uppercase text-violet-300",children:[e.jsx(C,{className:"h-2.5 w-2.5"}),"neu"]})]},c.path))})]},s||"__root"))})]}),e.jsx("div",{className:"min-w-0 flex-1 mc-card p-5",children:u?e.jsxs("div",{className:"flex items-center gap-2 text-xs text-muted-foreground",children:[e.jsx(v,{className:"h-4 w-4 animate-spin"})," Notiz wird geladen …"]}):d?e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"mb-3 flex items-center justify-between border-b border-border/40 pb-2",children:[e.jsxs("span",{className:"flex items-center gap-1.5 font-mono text-[11px] text-muted-foreground",children:[e.jsx(E,{className:"h-3.5 w-3.5"})," ",d.path]}),d.mtime>0&&e.jsxs("span",{className:"text-[10px] text-muted-foreground/60",children:["Stand ",new Date(d.mtime*1e3).toLocaleString("de-DE",{day:"2-digit",month:"2-digit",hour:"2-digit",minute:"2-digit"})]})]}),e.jsx(z,{text:d.content,onWikiLink:j,known:m})]}):null})]})]})}function z({text:o,onWikiLink:h,known:l}){const f=o.split(`
`),d=[];let r=[],u=null;const i=a=>{r.length&&(d.push(e.jsx("ul",{className:"mb-3 ml-4 list-disc space-y-1",children:r},a)),r=[])};return f.forEach((a,p)=>{const t=`l${p}`;if(u!==null){a.trimEnd()==="```"?(d.push(e.jsx("pre",{className:"mb-3 overflow-x-auto rounded-xl border border-border/50 bg-background/50 p-3 font-mono text-[11px] leading-relaxed text-foreground/85",children:u.join(`
`)},t)),u=null):u.push(a);return}if(a.trimStart().startsWith("```")){i(t),u=[];return}const m=a.trimEnd();if(!m.trim()){i(t);return}const g=m.match(/^(#{1,4})\s+(.*)$/);if(g){i(t);const j=g[1].length,s=j===1?"text-lg font-bold mt-1 mb-3":j===2?"text-base font-bold mt-4 mb-2":"text-sm font-bold mt-3 mb-1.5";d.push(e.jsx("p",{className:w(s,"font-space text-foreground"),children:N(g[2],h,l,t)},t));return}const b=m.match(/^\s*[-*•]\s+(.*)$/);if(b){r.push(e.jsx("li",{className:"text-xs leading-relaxed text-foreground/85",children:N(b[1],h,l,t)},t));return}i(t),d.push(e.jsx("p",{className:"mb-2 text-xs leading-relaxed text-foreground/85",children:N(m,h,l,t)},t))}),i("end"),u!==null&&d.push(e.jsx("pre",{className:"mb-3 overflow-x-auto rounded-xl border border-border/50 bg-background/50 p-3 font-mono text-[11px]",children:u.join(`
`)},"code-end")),e.jsx("div",{className:"max-w-3xl",children:d})}function N(o,h,l,f){return o.split(/(\[\[[^\]]+\]\]|\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`)/g).map((r,u)=>{const i=`${f}-${u}`,a=r.match(/^\[\[([^\]|]+)(?:\|([^\]]+))?\]\]$/);if(a){const p=a[1].trim(),t=(a[2]??a[1]).trim();return l.has(p.toLowerCase())?e.jsx("button",{onClick:()=>h(p),className:"rounded bg-primary/10 px-1 text-primary underline decoration-primary/40 underline-offset-2 hover:bg-primary/20 cursor-pointer",children:t},i):e.jsx("span",{className:"rounded bg-background/40 px-1 text-muted-foreground",title:"Notiz existiert (noch) nicht",children:t},i)}return r.startsWith("**")&&r.endsWith("**")?e.jsx("b",{className:"font-semibold text-foreground",children:r.slice(2,-2)},i):r.startsWith("*")&&r.endsWith("*")&&r.length>2?e.jsx("i",{children:r.slice(1,-1)},i):r.startsWith("`")&&r.endsWith("`")?e.jsx("code",{className:"rounded bg-background/50 px-1 font-mono text-[11px] text-teal-300",children:r.slice(1,-1)},i):e.jsx("span",{children:r},i)})}export{T as WissenView};
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
import{c as o}from"./index-C_u0QVqX.js";/**
import{c as o}from"./index-0Xcu6PnS.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
import{c as e}from"./index-C_u0QVqX.js";/**
import{c as e}from"./index-0Xcu6PnS.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
import{c as a}from"./index-C_u0QVqX.js";/**
import{c as a}from"./index-0Xcu6PnS.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
import{c as a}from"./index-C_u0QVqX.js";/**
import{c as a}from"./index-0Xcu6PnS.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
import{c as t}from"./index-C_u0QVqX.js";/**
import{c as t}from"./index-0Xcu6PnS.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
import{c as a}from"./index-C_u0QVqX.js";/**
import{c as a}from"./index-0Xcu6PnS.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
+2 -2
View File
@@ -7,8 +7,8 @@
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>Mission Control 2.0</title>
<script type="module" crossorigin src="/assets/index-C_u0QVqX.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BQrAbiFg.css">
<script type="module" crossorigin src="/assets/index-0Xcu6PnS.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Dv1-nhwH.css">
</head>
<body>
<div id="root"></div>
+2
View File
@@ -20,6 +20,7 @@ const KonsoleView = lazy(() => import("@/views/KonsoleView").then((m) => ({ defa
const GuideView = lazy(() => import("@/views/GuideView").then((m) => ({ default: m.GuideView })))
const AuftragsbuchView = lazy(() => import("@/views/AuftragsbuchView").then((m) => ({ default: m.AuftragsbuchView })))
const IdeenView = lazy(() => import("@/views/IdeenView").then((m) => ({ default: m.IdeenView })))
const SkillsView = lazy(() => import("@/views/SkillsView").then((m) => ({ default: m.SkillsView })))
const ChronikView = lazy(() => import("@/views/ChronikView").then((m) => ({ default: m.ChronikView })))
const WissenView = lazy(() => import("@/views/WissenView").then((m) => ({ default: m.WissenView })))
@@ -203,6 +204,7 @@ export default function App() {
{view === "dashboard" && <CockpitView onNavigate={(v) => navigate(v as ViewId)} />}
{view === "ideen" && <IdeenView />}
{view === "auftraege" && <AuftragsbuchView />}
{view === "skills" && <SkillsView />}
{view === "models" && <ModelsView />}
{view === "connect" && <ConnectView />}
{view === "wissen" && <WissenView />}
@@ -2,7 +2,7 @@ import { Gauge } from "lucide-react"
import { useVoiceTrace } from "@/lib/queries"
import type { VoiceTurn } from "@/lib/api"
// Balken-Stufen in kanonischer Reihenfolge (zeitlich disjunkt). Mem0 ist ein Unter-Detail
// Balken-Stufen in kanonischer Reihenfolge (zeitlich disjunkt). Gedächtnis ist ein Unter-Detail
// INNERHALB von „Hirn" und wird NICHT als eigenes Segment addiert (sonst Doppelzählung).
const SEGMENTS = [
{ key: "stt_ms", label: "STT", color: "#f59e0b" },
@@ -39,7 +39,7 @@ function culprit(t: VoiceTurn): { label: string; color: string; ms: number } | n
function TurnRow({ t, scale }: { t: VoiceTurn; scale: number }) {
const title = SEGMENTS.map((s) => `${s.label} ${fmt(t[s.key] as number | null)}`).join(" · ")
+ (t.mem0_ms != null ? ` (davon Mem0 ${fmt(t.mem0_ms)})` : "")
+ (t.memory_ms != null ? ` (davon Gedächtnis ${fmt(t.memory_ms)})` : "")
return (
<div className="flex items-center gap-2.5">
<span className="w-14 shrink-0 text-right font-mono text-[10px] text-muted-foreground/60">{ago(t.ts)}</span>
@@ -103,7 +103,7 @@ export function LatencyCard() {
{latest && !latest.error && (
<div className="mt-0.5 font-mono text-[11px] text-muted-foreground/70">
{SEGMENTS.filter((s) => (latest[s.key] as number | null) != null).map((s) => `${s.label} ${fmt(latest[s.key] as number)}`).join(" · ")}
{latest.mem0_ms != null && <span className="text-muted-foreground/50"> · davon Mem0 {fmt(latest.mem0_ms)}</span>}
{latest.memory_ms != null && <span className="text-muted-foreground/50"> · davon Gedächtnis {fmt(latest.memory_ms)}</span>}
</div>
)}
</div>
@@ -128,7 +128,7 @@ export function LatencyCard() {
)}
<div className="mt-3 border-t border-border/30 pt-2 text-[10px] leading-relaxed text-muted-foreground/70">
Hirn" = Zeit bis zum ersten Wort (Agent + Mem0-Suche + Modell); Tool-Runden stecken in „Antwort". Reine
Hirn" = Zeit bis zum ersten Wort (Agent + Gedächtnis-Suche + Modell); Tool-Runden stecken in „Antwort". Reine
Telegram-Turns laufen an MC2 vorbei und erscheinen hier nicht.
</div>
</div>
+3 -37
View File
@@ -443,7 +443,7 @@ export interface TokenStats {
}
// Per-Turn-Latenz-Trace (GET /api/voice/trace). Balken-Stufen (zeitlich disjunkt):
// stt · vision · hirn · gen. mem0 = Unter-Detail INNERHALB von hirn (nicht zum Balken addieren).
// stt · vision · hirn · gen. memory = Unter-Detail INNERHALB von hirn (nicht zum Balken addieren).
export interface VoiceTurn {
id: string
ts: number // Epoch-Sekunden
@@ -452,9 +452,9 @@ export interface VoiceTurn {
had_images: boolean
stt_ms: number | null
vision_ms: number | null
hirn_ms: number | null // Zeit bis erstes Inhalts-Token (Agent + Mem0 + LLM-TTFT)
hirn_ms: number | null // Zeit bis erstes Inhalts-Token (Agent + Gedächtnis + LLM-TTFT)
gen_ms: number | null // Generierung nach dem ersten Token bis Stream-Ende
mem0_ms: number | null // Teil VON hirn (Mem0-Retrieve), best-effort
memory_ms: number | null // Teil VON hirn (Gedächtnis-Retrieve), best-effort
total_ms: number
error: string | null
}
@@ -583,40 +583,6 @@ export interface ChronikResp {
items: ChronikItem[]
}
// ── Governor (Token-Wächter :8100, GET /api/governor) ────────────────────────
// Sitzt zwischen Coding-Agenten und Gateway. `est` ist seine Schätzung VOR dem Lauf,
// `exact` die echten prompt_tokens AUS der Antwort — die Differenz ist sein Fehler,
// und aus ihr lernt er (chars_per_token je Modell). action: passthrough | soft | hard | defer.
export interface GovernorRun {
t: number
model: string
est: number | null
exact: number | null
action: string
}
export interface GovernorStatus {
ok: boolean
reachable: boolean
error?: string
upstream?: string
soft?: number
hard?: number | null
uptime_s?: number
counters?: {
chat: number
soft: number
hard: number
passthrough: number
tokens_prompt: number
tokens_completion: number
}
calibration?: Record<string, { chars_per_token: number; samples: number }>
calibration_default?: number
exempt_models?: string[]
recent?: GovernorRun[]
}
// ── Eigenleben („Von allein": Skills + Vorschlags-Bilanz der Box) ────────────
export interface EigenlebenSkill {
id: string
+2 -13
View File
@@ -14,7 +14,6 @@ import {
type DiscoverResp,
type DraftsResp,
type EigenlebenResp,
type GovernorStatus,
type GroupsResp,
type HermesBrainResp,
type Health,
@@ -61,7 +60,6 @@ export const qk = {
auftragsbuch: ["auftragsbuch"] as const,
ideen: ["ideen"] as const,
chronik: ["chronik"] as const,
governor: ["governor"] as const,
eigenleben: ["eigenleben"] as const,
wissen: ["wissen"] as const,
zeitmaschine: ["zeitmaschine"] as const,
@@ -159,21 +157,12 @@ export const useVoiceTrace = (limit = 12, refetchInterval: number = TAKT.schnell
select: (d) => d.turns ?? [],
})
// Token-Wächter (:8100). Ist er aus, liefert das Backend `reachable:false` statt zu werfen —
// deshalb kein Retry-Sturm und kein Fehlerzustand in der Kachel.
export const useGovernor = (refetchInterval: number = TAKT.normal) =>
useQuery({
queryKey: qk.governor,
queryFn: () => api<GovernorStatus>("/api/governor"),
refetchInterval,
retry: false,
})
export const useMemoryGraph = (enabled = true) =>
useQuery({
queryKey: qk.memoryGraph,
queryFn: () => api<MemoryGraph>("/api/memory/graph"),
queryFn: () => api<MemoryGraph>("/api/wissen/graph"),
enabled,
staleTime: 60 * 1000,
})
export const useHealth = () =>
+5 -5
View File
@@ -121,18 +121,18 @@ export function useLucyHealth(): LucyHealth {
}
})())
// 3) Lucys Gedächtnis — Mem0-Dienst.
// 3) Lucys Gedächtnis — Hermes Native Gedächtnis (integriert in hermes-gateway).
checks.push(((): LucyCheck => {
const base = { id: "memory", label: "Lucys Gedächtnis", icon: BookMarked, critical: true }
const ok = svcOk("mem0-service")
const ok = svcOk("hermes-gateway")
if (!loaded || ok === undefined) return { ...base, status: "loading", detail: "Wird geprüft …" }
return ok
? { ...base, status: "ok", detail: "Merkt sich Neues und findet Altes wieder." }
? { ...base, status: "ok", detail: "Hermes-Natives Gedächtnis aktiv (SQLite & Embeddings)." }
: {
...base,
status: "down",
detail: "Lucy kann sich gerade nichts merken — das Gedächtnis antwortet nicht.",
repair: { kind: "restart", service: "mem0-service", label: "Gedächtnis neu starten" },
detail: "Lucy kann sich gerade nichts merken — das Gateway antwortet nicht.",
repair: { kind: "restart", service: "hermes-gateway", label: "Gateway neu starten" },
}
})())
+2 -2
View File
@@ -12,7 +12,7 @@ import {
type LucideIcon,
} from "lucide-react"
export type ViewId = "dashboard" | "ideen" | "auftraege" | "models" | "wissen" | "chronik" | "connect" | "agent" | "konsole" | "guide"
export type ViewId = "dashboard" | "ideen" | "auftraege" | "skills" | "models" | "wissen" | "chronik" | "connect" | "agent" | "konsole" | "guide"
export type NavGroup = "Operativ" | "Wissen" | "Werkzeuge" | "Wartung"
@@ -33,7 +33,7 @@ export const NAV: NavItem[] = [
// Seitenüberschrift „Ideen" — ein langer Satz stand dort dreifach gedoppelt.
{ id: "ideen", label: "Ideen", hint: "Schreib hin, was entstehen soll", icon: Lightbulb, group: "Operativ" },
{ id: "auftraege", label: "Auftragsbuch", hint: "Vorschläge der Box — annehmen oder ablehnen", icon: Inbox, group: "Operativ" },
{ id: "skills", label: "Skills & Jobs", hint: "Autonome Skills manuell auslösen", icon: Bot, group: "Operativ" },
{ id: "chronik", label: "Chronik", hint: "Was die Box von allein getan hat + Zeitmaschine", icon: History, group: "Operativ" },
{ id: "wissen", label: "Wissen", hint: "Lucys Wissens-Vault (Traum-Notizen)", icon: Library, group: "Wissen" },
{ id: "connect", label: "Verbinden", hint: "IDE-/Agent-Configs erzeugen", icon: Plug, group: "Werkzeuge" },
+3 -6
View File
@@ -345,11 +345,8 @@ description: Treibt Entwicklung mit strikter TDD-Praxis
</div>
</div>
<Stack>
Dein Gedächtnis (Tab <strong>Gedächtnis</strong>) ist <strong>Mem0</strong>-basiert: auto-lernend &amp;
semantisch, <strong>graph-first</strong> dargestellt, mit 4-Typen-Taxonomie
(<Pill>Identität</Pill> · <Pill>Wissen</Pill> · <Pill>Regeln</Pill> · <Pill>Ereignisse</Pill>). Der Agent
lernt im Hintergrund nach jedem Turn egal ob du per Desktop, Konsole, Telegram oder Lucy mit ihm sprichst.
<br /><strong>Gotcha:</strong> ein gelöschter Fakt kann wieder auftauchen, solange die Original-Nachricht noch im Verlauf steht (additive Extraktion).
Dein Gedächtnis ist auto-lernend &amp; semantisch: Der Agent lernt im Hintergrund nach jedem Turn
egal ob du per Desktop, Konsole, Telegram oder Lucy mit ihm sprichst.
</Stack>
</Card>
@@ -498,7 +495,7 @@ description: Treibt Entwicklung mit strikter TDD-Praxis
<LinkItem href="https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/overview" name="Prompt-Engineering (Anthropic)" note="die beste praktische Anleitung." />
<LinkItem href="https://github.com/anthropics/anthropic-cookbook" name="Anthropic Cookbook" note="lauffähige Rezepte für Tools, RAG, Agenten." />
<LinkItem href="https://www.promptingguide.ai" name="Prompt Engineering Guide" note="herstellerneutrales Nachschlagewerk." />
<LinkItem href="https://github.com/mem0ai/mem0" name="Mem0" note="die auto-lernende Memory-Schicht hinter deinem Gedächtnis." />
<LinkItem href="https://hermes-agent.nousresearch.com" name="Hermes Agent" note="die autonome Open-Source Agenten-Plattform unserer Box." />
<LinkItem href="https://www.reddit.com/r/LocalLLaMA/" name="r/LocalLLaMA" note="der Puls der lokalen-LLM-Szene." />
</ul>
</div>
+10 -10
View File
@@ -17,7 +17,7 @@ import { SectionLabel } from "@/components/ui/SectionLabel"
// die MEHRHEIT einer Seite, die nach etwas anderem benannt war. Die beiden laufen in
// entgegengesetzte Richtungen: Auftragsbuch = die Box schlaegt DIR vor (Eingang),
// Ideen = DU beauftragst die Box (Ausgang). Seit dem Umbau ist dieser Weg der
// Haupteingang des Systems (Idee -> Konzept -> Repo -> Zed) und gehoert nach vorn.
// Haupteingang des Systems (Idee -> Konzept -> Repo -> Agentic IDE) und gehoert nach vorn.
// ── Ideen-Queue (natives Hermes-Kanban) ──────────────────────────────────────
// Eine Queue, drei Türen (Zentrale/Telegram/Lucy). Idee rein → die Box arbeitet sie
@@ -51,7 +51,7 @@ const ART_UI: Record<string, { label: string; cls: string; icon: typeof Hammer;
projekt: {
label: "IDE", icon: Code2,
cls: "border-violet-500/40 bg-violet-500/10 text-violet-300",
title: "IDE-Welt: die Box bereitet nur vor (Konzept + Repo + Doku, kein Code). Gebaut wird von dir in Zed.",
title: "IDE-Welt: die Box bereitet nur vor (Konzept + Repo + Doku, kein Code). Gebaut wird von dir in der Agentic IDE.",
},
}
@@ -101,7 +101,7 @@ function IdeenSection() {
const [modus, setModus] = useState<"denken" | "repo" | "box">("denken")
const [tiefe, setTiefe] = useState<"schlank" | "gruendlich">("schlank")
// Zugeklappt statt Assistent (26.07.2026): der Entscheidungsbaum EXISTIERT — Wer baut das?,
// und nur im Zed-Ast: wie startest du? — aber du läufst ihn nur, wenn die Einordnung
// und nur im IDE-Ast: wie startest du? — aber du läufst ihn nur, wenn die Einordnung
// danebenlag. Im Normalfall steht da eine Zeile und du drückst Enter.
const [offen, setOffen] = useState(false)
// Hat die Box den Text wirklich gelesen? Steuert nur das Etikett „gelesen" in der
@@ -131,12 +131,12 @@ function IdeenSection() {
let verworfen = false
const timer = setTimeout(async () => {
try {
const r = await api<{ ok: boolean; wer?: "box" | "zed"; tiefe?: "schlank" | "gruendlich"; nur_denken?: boolean }>(
const r = await api<{ ok: boolean; wer?: "box" | "ide"; tiefe?: "schlank" | "gruendlich"; nur_denken?: boolean }>(
"/api/ideen/einordnen", { method: "POST", body: JSON.stringify({ text: t }) })
if (verworfen || !r?.ok) return
// Die drei Karten aus der Einordnung ableiten: eine Frage („lohnt sich…?") wird zum
// Durchdenken, ein eigenständiges Programm zum Repo, alles am Heimlabor zur Box.
setModus(r.nur_denken ? "denken" : r.wer === "zed" ? "repo" : "box")
setModus(r.nur_denken ? "denken" : r.wer === "ide" ? "repo" : "box")
if (r.tiefe) setTiefe(r.tiefe)
setVorgeschlagen(true)
} catch { /* stumm — die Voreinstellung bleibt einfach stehen */ }
@@ -500,7 +500,7 @@ function IdeenSection() {
</div>
{/* ZUGEKLAPPT STATT ASSISTENT. Der Entscheidungsbaum ist derselbe, den ein Wizard
abfragen wuerde Wer baut das?, und NUR im Zed-Ast: wie startest du? Nur laeufst
abfragen wuerde Wer baut das?, und NUR im IDE-Ast: wie startest du? Nur laeufst
du ihn erst, wenn die Einordnung danebenlag. Ein Assistent wuerde dich fragen, was
die Box in ~500 ms schon weiss; das hier fragt nur im Zweifelsfall.
IMMER sichtbar (26.07., User-Befund): vorher hing der ganze Block an
@@ -516,7 +516,7 @@ function IdeenSection() {
<div className="grid gap-2 sm:grid-cols-2">
<Wahl aktiv={modus !== "box"}
onWahl={() => setModus(modus === "box" ? "denken" : modus)}
k={{ id: "zed", icon: Code2, titel: "Ich, in Zed",
k={{ id: "ide", icon: Code2, titel: "Ich, in der Agentic IDE",
text: "Du bekommst Konzept und Repo — den Code schreibst du selbst." }} />
<Wahl aktiv={modus === "box"}
onWahl={() => setModus("box")}
@@ -525,7 +525,7 @@ function IdeenSection() {
</div>
</div>
{/* Die zweite Frage gibt es NUR im Zed-Ast eingerückt, damit die Verschachtelung
{/* Die zweite Frage gibt es NUR im IDE-Ast eingerückt, damit die Verschachtelung
sichtbar ist. Bei die Box" folgt keine Rückfrage: dann geht es direkt los. */}
{modus !== "box" && (
<div className="border-l-2 border-primary/40 pl-3">
@@ -880,7 +880,7 @@ function WeiterLeiste({ id, onFertig }: { id: string; onFertig?: () => void }) {
<p className="mt-3 border-t border-violet-500/20 pt-2 text-[11px] text-emerald-300">
{neu.art === "projekt" ? (
<>Projekt-Karte <span className="font-mono">{neu.id}</span> ist angelegt die Box bereitet
das Repo vor (Konzept übernimmt sie, sie denkt es nicht neu). Danach öffnest du es in Zed.</>
das Repo vor (Konzept übernimmt sie, sie denkt es nicht neu). Danach öffnest du es in der Agentic IDE.</>
) : (
<>Überarbeitung <span className="font-mono">{neu.id}</span> läuft die Box arbeitet deine
Rückmeldung ein und schreibt die neue Fassung an dieselbe Stelle. Danach hier wieder
@@ -917,7 +917,7 @@ function WeiterLeiste({ id, onFertig }: { id: string; onFertig?: () => void }) {
<p className="text-[11px] text-muted-foreground">
<b className="text-violet-300/90">Passt es so?</b> Dann macht die Box daraus ein startklares
Repo Konzept wird übernommen (nicht neu gedacht), dazu Roadmap, AGENTS.md &amp; Co.
Gebaut wird danach von dir in Zed.
Gebaut wird danach von dir in der Agentic IDE.
</p>
<div className="flex flex-wrap items-center gap-x-3 gap-y-2">
<span className="text-[11px] text-muted-foreground/70">Ziel</span>
+129
View File
@@ -0,0 +1,129 @@
import { useState } from "react"
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import { Bot, Play, Loader2, CheckCircle2, AlertTriangle } from "lucide-react"
import { cn } from "@/lib/utils"
import { api } from "@/lib/api"
import { qk } from "@/lib/queries"
import { JobsBar } from "@/components/models/JobsBar"
interface Skill {
name: string
description: string
running?: boolean
}
interface SkillsResp {
skills: Skill[]
}
export function SkillsView() {
const queryClient = useQueryClient()
const { data, isLoading, isError } = useQuery({
queryKey: ["skills-list"],
queryFn: () => api<SkillsResp>("/api/skills"),
refetchInterval: 5000,
})
const [runningSkill, setRunningSkill] = useState<string | null>(null)
const [lastResult, setLastResult] = useState<{name: string, ok: boolean, msg: string} | null>(null)
const mutation = useMutation({
mutationFn: (skillName: string) => api<{ok: boolean, msg?: string, err?: string}>("/api/skills/run", {
method: "POST",
body: JSON.stringify({ skill_name: skillName })
}),
onMutate: (skillName) => {
setRunningSkill(skillName)
setLastResult(null)
},
onSuccess: (res, skillName) => {
setRunningSkill(null)
setLastResult({ name: skillName, ok: res.ok, msg: res.msg || res.err || "Unbekannter Fehler" })
// Jobs neu laden, damit der gestartete Skill direkt in der JobsBar auftaucht
queryClient.invalidateQueries({ queryKey: qk.jobs })
queryClient.invalidateQueries({ queryKey: ["skills-list"] })
},
onError: (err, skillName) => {
setRunningSkill(null)
setLastResult({ name: skillName, ok: false, msg: String(err) })
}
})
return (
<div className="space-y-6">
<div className="flex flex-col sm:flex-row justify-between sm:items-center gap-4">
<div>
<h1 className="text-2xl font-space font-bold tracking-tight bg-gradient-to-r from-foreground via-foreground to-primary bg-clip-text text-transparent">
Skills & Jobs
</h1>
<p className="text-sm text-muted-foreground">
Löst autonome Agent-Skills manuell auf der Box aus. Die Ausführung erfolgt im Hintergrund durch Hermes.
</p>
</div>
</div>
<JobsBar />
{lastResult && (
<div className={cn(
"p-4 rounded-lg border flex gap-3 items-start",
lastResult.ok ? "bg-emerald-500/10 border-emerald-500/20 text-emerald-600 dark:text-emerald-400" : "bg-red-500/10 border-red-500/20 text-red-600 dark:text-red-400"
)}>
{lastResult.ok ? <CheckCircle2 className="h-5 w-5 mt-0.5 shrink-0" /> : <AlertTriangle className="h-5 w-5 mt-0.5 shrink-0" />}
<div>
<div className="font-semibold text-sm">{lastResult.name}</div>
<div className="text-sm opacity-90">{lastResult.msg}</div>
</div>
</div>
)}
{isLoading && (
<div className="flex h-32 items-center justify-center">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
</div>
)}
{isError && (
<div className="text-red-500 p-4 mc-card text-sm">
Fehler beim Laden der Skills. Ist das Backend erreichbar?
</div>
)}
{data?.skills && (
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
{data.skills.map(skill => (
<div key={skill.name} className="mc-card p-5 flex flex-col hover:border-primary/40 transition-colors group">
<div className="flex items-start justify-between gap-3 mb-2">
<div className="flex items-center gap-2 font-semibold text-base font-space">
<Bot className="h-5 w-5 text-primary/80" />
{skill.name}
</div>
<button
onClick={() => mutation.mutate(skill.name)}
disabled={runningSkill === skill.name || skill.running || mutation.isPending}
className={cn(
"shrink-0 h-8 px-3 rounded-md text-xs font-semibold uppercase tracking-wider flex items-center gap-1.5 transition-all cursor-pointer",
(runningSkill === skill.name || skill.running)
? "bg-sky-500/10 border border-sky-500/40 text-sky-400 cursor-not-allowed"
: "bg-primary/10 text-primary hover:bg-primary hover:text-primary-foreground group-hover:shadow-md group-hover:shadow-primary/20"
)}
>
{(runningSkill === skill.name || skill.running) ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Play className="h-3.5 w-3.5" />}
{(runningSkill === skill.name || skill.running) ? "Läuft …" : "Starten"}
</button>
</div>
<div className="text-sm text-muted-foreground flex-1">
{skill.description}
</div>
</div>
))}
{data.skills.length === 0 && (
<div className="col-span-full p-8 text-center text-muted-foreground mc-card border-dashed border-2">
Keine Skills im Ordner <code className="text-xs text-foreground bg-muted px-1.5 py-0.5 rounded">deploy/skills</code> gefunden.
</div>
)}
</div>
)}
</div>
)
}
+49 -2
View File
@@ -1,7 +1,9 @@
import { useEffect, useMemo, useState } from "react"
import { Library, FileText, Sparkles, Loader2, FolderOpen, Search } from "lucide-react"
import { api, type WissenDatei, type WissenFile } from "@/lib/api"
import { useWissen } from "@/lib/queries"
import { useWissen, useMemoryGraph } from "@/lib/queries"
import { GraphView } from "./GraphView"
import { GraphErrorBoundary } from "./GraphErrorBoundary"
import { cn } from "@/lib/utils"
// Wissens-Vault: die nächtlichen Traum-Notizen der Box als klickbares Wiki.
@@ -23,6 +25,9 @@ export function WissenView() {
const [doc, setDoc] = useState<WissenDatei | null>(null)
const [docLoading, setDocLoading] = useState(false)
const [q, setQ] = useState("")
const [showGraph, setShowGraph] = useState(false)
const { data: graphData, isLoading: graphLoading } = useMemoryGraph(showGraph)
const files = useMemo(() => data?.files ?? [], [data])
@@ -74,6 +79,7 @@ export function WissenView() {
return (
<div className="space-y-5">
<div className="flex items-center justify-between">
<div>
<h1 className="bg-gradient-to-r from-foreground via-foreground to-primary bg-clip-text font-space text-2xl font-bold tracking-tight text-transparent">
Wissen
@@ -82,6 +88,23 @@ export function WissenView() {
Was sich die Box nachts erschließt Traum-Notizen, Muster und Skill-Ideen aus dem Wissens-Vault, verlinkt wie ein Wiki.
</p>
</div>
{files.length > 0 && (
<div className="flex rounded-lg border border-border/60 bg-card p-1">
<button
onClick={() => setShowGraph(false)}
className={cn("rounded-md px-3 py-1.5 text-xs font-medium transition-colors", !showGraph ? "bg-primary text-primary-foreground shadow" : "text-muted-foreground hover:bg-muted")}
>
Liste
</button>
<button
onClick={() => setShowGraph(true)}
className={cn("rounded-md px-3 py-1.5 text-xs font-medium transition-colors", showGraph ? "bg-primary text-primary-foreground shadow" : "text-muted-foreground hover:bg-muted")}
>
Graph
</button>
</div>
)}
</div>
{data && !data.available && (
<div className="rounded-2xl border border-amber-500/30 bg-amber-500/5 p-4 text-xs text-amber-300">
@@ -101,7 +124,31 @@ export function WissenView() {
</div>
)}
{files.length > 0 && (
{showGraph ? (
<div className="mc-card flex h-[70vh] flex-col overflow-hidden p-0 relative">
{graphLoading && (
<div className="absolute inset-0 flex items-center justify-center bg-background/50 z-10">
<div className="flex items-center gap-2 rounded-2xl border border-border/60 bg-card p-6 text-xs text-muted-foreground">
<Loader2 className="h-4 w-4 animate-spin" /> Graph wird berechnet
</div>
</div>
)}
<GraphErrorBoundary>
{graphData && (
<GraphView
data={graphData}
selectedNodeId={active}
onNodeSelect={(id) => {
if (id) {
setActive(id)
setShowGraph(false)
}
}}
/>
)}
</GraphErrorBoundary>
</div>
) : files.length > 0 && (
<div className="flex flex-col gap-4 lg:flex-row">
{/* Datei-Liste */}
<aside className="w-full shrink-0 space-y-3 lg:w-72">
+1 -1
View File
@@ -100,7 +100,7 @@ function Landkarte() {
<NodeBox x={444} y={198} w={396} h={78} color={T.sky} title="Modell-Stall (llama-swap)"
sub="Hirn · Groß · Coder · Augen · Späher · Kritiker"
sub2="+ Embedding & Sortierer fürs Gedächtnis" />
<NodeBox x={444} y={286} w={190} h={56} color={T.indigo} title="Mem0-Gedächtnis" sub="lernt automatisch mit" />
<NodeBox x={444} y={286} w={190} h={56} color={T.indigo} title="Natives Gedächtnis" sub="Hermes v0.20 SQLite & Embed" />
<NodeBox x={650} y={286} w={190} h={56} color={T.amber} title="Wissens-Vault" sub="Träume & Notizen (git)" />
<NodeBox x={444} y={352} w={396} h={52} color={T.emerald} title="Selbstheilung & Backups"
sub="Health-Checks · Auto-Revert bei Rot · PBS-Sicherung" />
+5
View File
@@ -48,6 +48,11 @@ const ROLE_METADATA: Record<string, { title: string; desc: string; icon: any }>
title: "Gedächtnis-Sortierer",
desc: "Sortiert und gewichtet gespeicherte Fakten nach Relevanz — damit die richtige Info sofort da ist.",
icon: Search
},
debugger: {
title: "Debugger & Selbstheiler",
desc: "Agentisches Debugging, multimodale Fehleranalyse und Selbstreparatur (z.B. Muse-Glimmer).",
icon: Brain
}
}
+63 -3
View File
@@ -216,7 +216,7 @@ def system_status() -> str:
@mcp.tool()
def list_services() -> str:
"""Erreichbarkeit aller Stack-Dienste (Engine, Gateway, Hermes, Mem0, Voice)."""
"""Erreichbarkeit aller Stack-Dienste (Engine, Gateway, Hermes, Voice)."""
data = _get("/api/system/services")
return "\n".join(
f"{'' if s['ok'] else ''} {s['name']} ({s['url']})"
@@ -227,7 +227,7 @@ def list_services() -> str:
@mcp.tool()
def restart_service(service: str) -> str:
"""Startet einen erlaubten Dienst neu. Erlaubt: llama-swap (Engine) | mission-control-2 |
hermes-gateway | hermes-terminal | mem0-service | voice-service. Der Engine-Neustart
hermes-gateway | hermes-builtin-ui | voice-service. Der Engine-Neustart
(llama-swap = System-Dienst) kann das Box-Passwort verlangen, wenn kein NOPASSWD gesetzt ist."""
res = _post("/api/system/restart", {"service": service})
return f"Restart {service}: {'ok' if res.get('ok') else 'Fehler — ' + res.get('err', '')}"
@@ -235,7 +235,7 @@ def restart_service(service: str) -> str:
@mcp.tool()
def backup_now() -> str:
"""Erstellt SOFORT ein Voll-Backup (mem0 + Configs + Secrets). Nutze dies vor riskanten
"""Erstellt SOFORT ein Voll-Backup (Configs + Secrets). Nutze dies vor riskanten
Änderungen oder wenn der Nutzer ein Backup will."""
r = _post("/api/system/backup")
return f"Backup erstellt: {json.dumps(r, ensure_ascii=False)}"
@@ -274,6 +274,66 @@ def apply_update(kind: str) -> str:
return json.dumps(_post(routes[kind]), ensure_ascii=False, indent=2)
@mcp.tool()
def get_ci_status() -> str:
"""Prüft den letzten CI-Build-Status auf der AI-Box via Gitea API.
Nützlich, um nach einem Push zu verifizieren, ob das Frontend und Backend grün gebaut haben."""
token = ""
# 1. Fallback: Versuch via git credential manager (zukunftsfähig & plattformübergreifend)
try:
import subprocess
p = subprocess.run(
["git", "credential", "fill"],
input=b"protocol=https\nhost=git.tobisniceshomelab.ddnsfree.com\n",
capture_output=True,
check=True
)
out = p.stdout.decode(errors="replace")
import re
p_match = re.search(r"password=(.+)", out)
if p_match:
token = p_match.group(1).strip()
except Exception:
pass
# 2. Fallback: Datei (hauptsächlich auf der AI-Box genutzt)
if not token:
token_path = os.path.expanduser("~/.git-credentials")
try:
with open(token_path, "r") as f:
creds = f.read()
import re
m = re.search(r'https://[^:]+:([^@]+)@git\.tobisniceshomelab', creds)
token = m.group(1) if m else ""
except Exception:
token = ""
if not token:
return "❌ Gitea Token konnte nicht via 'git credential' oder ~/.git-credentials geladen werden. CI Status kann nicht geprüft werden."
# Gitea URL von der Box aus intern erreichbar
repo_url = "http://192.168.178.153:3000/api/v1/repos/Hitonabi/mission-control-v2/actions/runs?limit=1"
try:
r = httpx.get(repo_url, headers={"Authorization": f"token {token}"}, timeout=10)
r.raise_for_status()
runs = r.json()
if not runs:
return "Keine CI-Runs gefunden."
run = runs[0]
status = run.get("status")
name = run.get("name")
sha = run.get("head_sha", "")[:7]
if status == "success":
return f"✅ CI-Build ({name} @ {sha}) war ERFOLGREICH."
elif status == "failure":
return f"❌ CI-Build ({name} @ {sha}) ist FEHLGESCHLAGEN. Bitte in Gitea prüfen!"
elif status in ["running", "pending"]:
return f"⏳ CI-Build ({name} @ {sha}) LÄUFT GERADE ({status})."
else:
return f"️ CI-Build ({name} @ {sha}) Status: {status}"
except Exception as e:
return f"❌ Fehler bei der Gitea-API Abfrage: {e!s}"
# ── Erinnerungen & Routinen (A3): die Box sagt dem Commander zur Zeit aktiv Bescheid ─────────
@mcp.tool()
def reminder_create(text: str, when: str, repeat: str = "") -> str:
-483
View File
@@ -1,483 +0,0 @@
#!/usr/bin/env python3
"""
Mem0-Sidecar für Mission Control 2.0 das auto-lernende, semantische Gedächtnis.
WARUM ein eigener Dienst? Mem0 + chromadb laufen nur unter Python 3.12 (`~/.mem0/venv`),
das MC2-Backend aber unter Python 3.14 (kann mem0 nicht importieren). Darum kapselt dieser
schlanke FastAPI-Dienst die Mem0-Memory-Instanz und exponiert sie auf localhost. MC2
(`backend/services/memory.py`) spricht ihn per HTTP an die `/api/memory`-API-Form bleibt
nach außen unverändert (UI + MCP-Server kompatibel).
Aufbau:
- Vektor-Store : Chroma embedded (kein Docker auf der Box) Pfad MEM0_CHROMA_PATH.
- Embeddings : llama.cpp `embed`-Rolle über llama-swap (/v1/embeddings, 1024 Dim).
- LLM-Extraktion: lokales `fast`-Hirn (Qwen3.6) über llama-swap (/v1/chat/completions).
Thinking wird abgeschaltet (NoThinkLLM), sonst bricht die JSON-Extraktion ab.
Läuft als systemd-User-Dienst (deploy/mem0-service.service) im `~/.mem0/venv`.
Bind: 127.0.0.1 (nur lokal; MC2 proxyt nach außen).
"""
import logging
import os
import re
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException
from mem0 import Memory
from mem0.configs.llms.openai import OpenAIConfig
from mem0.llms.openai import OpenAILLM
from pydantic import BaseModel
log = logging.getLogger("mem0_service")
# --- Konfiguration (alles über Env überschreibbar; Defaults = Box-Stand) ----------
USER_ID = os.environ.get("MEM0_USER_ID", "mission-control") # ein geteiltes Gehirn
EMBED_URL = os.environ.get("MEM0_EMBED_URL", "http://127.0.0.1:8080/v1")
LLM_URL = os.environ.get("MEM0_LLM_URL", "http://127.0.0.1:8080/v1")
EMBED_MODEL = os.environ.get("MEM0_EMBED_MODEL", "embed")
LLM_MODEL = os.environ.get("MEM0_LLM_MODEL", "fast")
EMBED_DIMS = int(os.environ.get("MEM0_EMBED_DIMS", "1024"))
CHROMA_PATH = os.environ.get("MEM0_CHROMA_PATH", "/srv/models/mem0/chroma")
HISTORY_DB = os.environ.get("MEM0_HISTORY_DB", "/srv/models/mem0/history.db")
COLLECTION = os.environ.get("MEM0_COLLECTION", "mc2")
API_KEY = os.environ.get("MEM0_API_KEY", "sk-local") # llama.cpp ignoriert den Key
LLM_MAX_TOKENS = int(os.environ.get("MEM0_LLM_MAX_TOKENS", "2048"))
SEARCH_TOP_K = int(os.environ.get("MEM0_SEARCH_TOP_K", "50"))
# --- Rerank-Zweitstufe (07.07.2026): Die Vektor-Suche findet ÄHNLICHES, der Reranker ordnet
# nach ECHTER Relevanz (Qwen3-Reranker via llama-swap /v1/rerank). Es ändert sich NUR die
# Reihenfolge: `score` bleibt der Vektor-Score (RECALL_MIN_SCORE im Hermes-Plugin bleibt
# kalibriert), Konsumenten, die vorne abschneiden (RECALL_LIMIT), bekommen automatisch die
# relevantesten Fakten. Jeder Fehler → lautlos Vektor-Reihenfolge (Gedächtnis bricht NIE).
RERANK_ENABLED = os.environ.get("MC_RERANK_ENABLED", "1") != "0"
RERANK_URL = os.environ.get("MC_RERANK_URL", "http://127.0.0.1:8080/v1/rerank")
RERANK_MODEL = os.environ.get("MC_RERANK_MODEL", "Qwen3-Reranker-0.6B")
RERANK_TIMEOUT = float(os.environ.get("MC_RERANK_TIMEOUT", "8"))
RERANK_CANDIDATES = int(os.environ.get("MC_RERANK_CANDIDATES", "20")) # nur die Top-N neu ordnen (Latenz)
# Kategorien = fundierte Memory-Taxonomie (semantisch/prozedural/episodisch), bewusst knapp (4).
# Mem0-OSS kann nicht nativ klassifizieren (Cloud-Feature) → wir lassen das Hirn beim Lernen
# einordnen (classify_facts). Die Beschreibungen steuern die Treffsicherheit der Auto-Zuordnung.
CATEGORIES = ("identity", "knowledge", "rules", "events")
DEFAULT_CATEGORY = "knowledge"
CATEGORY_DESCRIPTIONS = {
"identity": "Identität & Vorlieben des Nutzers: wer er ist (Name, Rolle), wie er angesprochen werden "
"will, persönliche Vorlieben und seine Arbeitsweise.",
"knowledge": "Wissen & Fakten: stabile Fakten über Stack, Projekte, Tools, Infrastruktur und Umgebung.",
"rules": "Regeln & Konventionen: verbindliche Anweisungen und Workflows — wie etwas gemacht werden "
"soll, Do's und Don'ts.",
"events": "Ereignisse & Entscheidungen: was passiert ist, getroffene Entscheidungen, zeitgebundene Vorgänge.",
}
class NoThinkLLM(OpenAILLM):
"""`fast` (Qwen3.6) ist ein Thinking-Modell. Unter response_format=json_object
verbrennt das Reasoning Tokens und schneidet die JSON-Fakten ab (leeres Ergebnis).
`enable_thinking=false` (chat_template_kwargs) deterministische, schnelle Extraktion."""
def generate_response(self, messages, response_format=None, tools=None,
tool_choice="auto", **kwargs):
eb = dict(kwargs.pop("extra_body", {}) or {})
eb.setdefault("chat_template_kwargs", {"enable_thinking": False})
return super().generate_response(
messages, response_format=response_format, tools=tools,
tool_choice=tool_choice, extra_body=eb, **kwargs,
)
def build_memory() -> Memory:
os.environ.setdefault("OPENAI_API_KEY", API_KEY)
os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
os.makedirs(CHROMA_PATH, exist_ok=True)
os.makedirs(os.path.dirname(HISTORY_DB), exist_ok=True)
cfg = {
"vector_store": {"provider": "chroma", "config": {
"collection_name": COLLECTION, "path": CHROMA_PATH}},
"embedder": {"provider": "openai", "config": {
"model": EMBED_MODEL, "embedding_dims": EMBED_DIMS,
"openai_base_url": EMBED_URL, "api_key": API_KEY}},
"llm": {"provider": "openai", "config": {
"model": LLM_MODEL, "openai_base_url": LLM_URL, "api_key": API_KEY,
"temperature": 0.1, "max_tokens": LLM_MAX_TOKENS}},
"history_db_path": HISTORY_DB,
# Sprache zwingend Deutsch erzwingen — mem0s englischer Extraktions-Prompt zieht sonst
# Richtung Englisch. Eigennamen/Fachbegriffe bleiben unangetastet.
"custom_instructions": (
"RELEVANZ (ZWINGEND, WICHTIGSTE REGEL): Extrahiere NUR dauerhaft nützliche Fakten ÜBER DEN "
"NUTZER (Identität, Vorlieben, Arbeitsweise), seine Projekte/Entscheidungen/Stände, stabile "
"Fakten über Stack/Tools/Infrastruktur sowie verbindliche Regeln und Workflows. "
"NIEMALS FAKTEN ÜBER DEN ASSISTENTEN SELBST (ZWINGEND): Extrahiere KEINE Aussagen über den "
"Assistenten/die KI (Hermes, Lucy), seine Fähigkeiten, seinen Zustand, seine Tools/MCP-Server, "
"seine Cronjobs, Commit-/Versionsstände oder welche Modelle er nutzt. Das Subjekt eines Fakts "
"ist IMMER der Nutzer oder sein Projekt — niemals 'der Assistent', 'Hermes' oder 'Lucy'. "
"KEINE ZEITGEBUNDENEN ZUSTÄNDE: Ignoriere transiente Momentaufnahmen (Fehler-/Job-Läufe, "
"Commit-Rückstand, Versions-Updates, 'gerade dabei'-/'hochgefahren'-/'aufgewärmt'-Status, "
"Test- und Onboarding-Fragen). IGNORIERE VOLLSTÄNDIG und gib KEINE Fakten zurück bei: "
"Tagesnachrichten und Welt-/Politik-/Börsen-/Promi-Ereignissen, Wetter, allgemeinem Weltwissen "
"ohne Bezug zum Nutzer, vom Assistenten erzeugten Zusammenfassungen oder Recherche-Ergebnissen, "
"Smalltalk, sowie Meta-Aussagen über den Gesprächsverlauf (z.B. 'der Nutzer fragt nach X', "
"'der Nutzer möchte Y prüfen', 'der Assistent tat Z'). Speichere nur, was in Wochen noch "
"nützlich ist; im Zweifel NICHT speichern. "
"SPRACHE (ZWINGEND): Formuliere ALLE Fakten auf DEUTSCH — unabhängig von der "
"Eingabesprache, niemals auf Englisch übersetzen. Eigennamen, Befehle, Pfade, Modell- und "
"Produktnamen sowie etablierte Fachbegriffe (z.B. PowerShell-Push, Deploy, Qwen3.6, Neovim, "
"Vulkan) bleiben unverändert. Schreibe jeden Fakt knapp und atomar — genau eine Aussage."
),
}
m = Memory.from_config(cfg)
# LLM gegen die Thinking-freie Variante tauschen (gleiche Verbindung).
m.llm = NoThinkLLM(OpenAIConfig(
model=LLM_MODEL, openai_base_url=LLM_URL, api_key=API_KEY,
temperature=0.1, max_tokens=LLM_MAX_TOKENS,
))
return m
_mem: Memory | None = None
def mem() -> Memory:
global _mem
if _mem is None:
_mem = build_memory()
return _mem
@asynccontextmanager
async def lifespan(_app: FastAPI):
logging.basicConfig(level=logging.INFO)
try:
mem() # Memory beim Start aufbauen (Chroma öffnen, Modelle anpingen)
log.info("Mem0 bereit (chroma=%s, embed=%s, llm=%s)", CHROMA_PATH, EMBED_MODEL, LLM_MODEL)
except Exception:
log.exception("Mem0-Init fehlgeschlagen (Dienst läuft, /health meldet down)")
yield
app = FastAPI(title="MC2 Mem0 Sidecar", lifespan=lifespan)
# --- Mapping Mem0 <-> MC2-API-Form -----------------------------------------------
def _to_item(r: dict, score=None) -> dict:
meta = r.get("metadata") or {}
out = {
"id": r.get("id"),
"content": r.get("memory", ""),
"category": meta.get("category") or "stable",
"bereich": meta.get("bereich") or "",
"source": meta.get("source") or "auto",
"created_at": r.get("created_at") or "",
"updated_at": r.get("updated_at") or r.get("created_at") or "",
}
if score is not None:
try:
out["score"] = round(float(score), 4)
except (TypeError, ValueError):
pass
return out
class MemIn(BaseModel):
content: str
category: str = DEFAULT_CATEGORY
source: str = "manual"
class MemUp(BaseModel):
content: str | None = None
category: str | None = None
class LearnIn(BaseModel):
text: str | None = None
messages: list[dict] | None = None
source: str = "auto"
category: str = DEFAULT_CATEGORY
classify: bool = True # Hirn ordnet die gelernten Fakten automatisch ein
@app.get("/health")
def health() -> dict:
try:
mem().get_all(filters={"user_id": USER_ID}, top_k=1)
return {"ok": True}
except Exception as exc:
raise HTTPException(503, f"mem0 nicht bereit: {exc}")
def _rerank(query: str, items: list[dict]) -> list[dict]:
"""Suchtreffer nach echter Relevanz umordnen; bei jedem Fehler unverändert zurück."""
if not (RERANK_ENABLED and len(items) > 1):
return items
try:
import httpx
r = httpx.post(RERANK_URL, json={
"model": RERANK_MODEL,
"query": query,
"documents": [i.get("content", "") for i in items],
"top_n": len(items),
}, timeout=RERANK_TIMEOUT)
r.raise_for_status()
results = r.json().get("results") or []
ranked = []
for s in sorted(results, key=lambda x: x.get("relevance_score", 0.0), reverse=True):
idx = s.get("index")
if isinstance(idx, int) and 0 <= idx < len(items):
item = dict(items[idx])
item["rerank_score"] = round(float(s.get("relevance_score", 0.0)), 4)
ranked.append(item)
# Nur übernehmen, wenn der Reranker ALLE Kandidaten bewertet hat (sonst Verlustgefahr).
return ranked if len(ranked) == len(items) else items
except Exception as exc:
log.debug("rerank übersprungen: %s", exc)
return items
@app.get("/memory")
def list_memory(q: str = "", category: str = "", bereich: str = "") -> list[dict]:
"""q gesetzt → semantische Suche (mit Relevanz-Score). Sonst → alle Fakten.
bereich='alltag'|'projekt' filtert (Fakten OHNE Bereichs-Feld laufen immer mit,
damit Alt-Bestand nie verschwindet)."""
if q:
res = mem().search(q, filters={"user_id": USER_ID}, top_k=SEARCH_TOP_K)
items = [_to_item(r, r.get("score")) for r in res.get("results", [])]
# Zweitstufe: die vorderen Kandidaten nach echter Relevanz ordnen (Rest bleibt hinten dran).
items = _rerank(q, items[:RERANK_CANDIDATES]) + items[RERANK_CANDIDATES:]
else:
res = mem().get_all(filters={"user_id": USER_ID}, top_k=1000)
items = [_to_item(r) for r in res.get("results", [])]
items.sort(key=lambda i: i.get("created_at", ""), reverse=True)
if category:
items = [i for i in items if i["category"] == category]
if bereich in ("alltag", "projekt"):
items = [i for i in items if i.get("bereich") in (bereich, "")]
return items
@app.post("/memory", status_code=201)
def add_memory(body: MemIn) -> dict:
"""Manueller/agentischer Einzel-Fakt → VERBATIM speichern (infer=False, keine
LLM-Umformung). Auto-Lernen aus Gesprächen läuft über /learn (infer=True)."""
content = body.content.strip()
if not content:
raise HTTPException(400, "content leer")
res = mem().add(
[{"role": "user", "content": content}],
user_id=USER_ID, infer=False,
metadata={"category": body.category, "source": body.source},
)
results = res.get("results", [])
if not results:
raise HTTPException(500, "Mem0 hat nichts gespeichert")
new_id = results[0]["id"]
full = mem().get(new_id) or {}
return _to_item(full) if full else _to_item({"id": new_id, "memory": content,
"metadata": {"category": body.category, "source": body.source}})
@app.put("/memory/{mid}")
def update_memory(mid: str, body: MemUp) -> dict:
existing = mem().get(mid)
if not existing:
raise HTTPException(404, "Eintrag nicht gefunden")
cur = _to_item(existing)
new_content = body.content.strip() if body.content is not None else cur["content"]
new_cat = body.category if body.category is not None else cur["category"]
mem().update(mid, data=new_content,
metadata={"category": new_cat, "source": cur["source"]})
full = mem().get(mid) or {}
return _to_item(full)
@app.delete("/memory/{mid}")
def delete_memory(mid: str) -> dict:
if not mem().get(mid):
raise HTTPException(404, "Eintrag nicht gefunden")
mem().delete(mid)
return {"ok": True}
@app.get("/graph")
def graph(min_score: float = 0.45, top_k: int = 3) -> dict:
"""Fakten als Graph: Knoten = Fakten, Kanten = semantische Ähnlichkeit (Kosinus
der gespeicherten Embeddings, je Knoten die top_k Nachbarn min_score). Für die
Obsidian-artige Gedächtnis-Visualisierung im UI."""
items = mem().get_all(filters={"user_id": USER_ID}, top_k=2000).get("results", [])
nodes = [_to_item(r) for r in items]
node_ids = [n["id"] for n in nodes]
idx = {nid: i for i, nid in enumerate(node_ids)}
# Embeddings direkt aus der Chroma-Collection ziehen (kein Re-Embedding).
col = mem().vector_store.collection
raw = col.get(include=["embeddings"])
raw_ids = raw.get("ids") or []
raw_embs = raw.get("embeddings")
edges: list[dict] = []
vecs = [None] * len(node_ids)
have = 0
if raw_embs is not None:
for rid, emb in zip(raw_ids, raw_embs):
if rid in idx and emb is not None:
vecs[idx[rid]] = emb
have += 1
if have >= 2:
import numpy as np
present = [i for i, v in enumerate(vecs) if v is not None]
M = np.array([vecs[i] for i in present], dtype=float)
norms = np.linalg.norm(M, axis=1, keepdims=True)
norms[norms == 0] = 1.0
Mn = M / norms
sim = Mn @ Mn.T
seen: set[tuple[int, int]] = set()
for a in range(len(present)):
order = np.argsort(-sim[a])
cnt = 0
for b in order:
if b == a:
continue
s = float(sim[a][b])
if s < min_score:
break
i, j = present[a], present[b]
key = (min(i, j), max(i, j))
if key not in seen:
seen.add(key)
edges.append({"source": node_ids[i], "target": node_ids[j],
"weight": round(s, 3)})
cnt += 1
if cnt >= top_k:
break
return {"nodes": nodes, "edges": edges}
def classify_facts(facts: list[tuple[str, str]]) -> dict:
"""Ordnet jeden Fakt per LLM einer Kategorie UND einem Bereich zu →
{id: {"category": <slug>, "bereich": "alltag"|"projekt"}}.
Bereich (19.07.2026, Trennung der Welten): "alltag" = der Nutzer als Person
(Vorlieben, Leben, Arbeitsweise, Entscheidungen), "projekt" = Technik-/Code-/
Infrastruktur-Stände. Grundlage für einen späteren Recall-Filter, damit
Projekt-Schutt nicht in Alltags-Gespräche streut OHNE zweites Gedächtnis.
Nutzt dasselbe (Thinking-freie) Hirn wie die Extraktion. Fehler = leeres Mapping."""
if not facts:
return {}
import json as _json
import re as _re
cat_lines = "\n".join(f"- {k}: {v}" for k, v in CATEGORY_DESCRIPTIONS.items())
system = (
"Du bist ein präziser Klassifikator für ein Langzeitgedächtnis. Ordne jeden Fakt GENAU EINER "
"Kategorie zu. Verfügbare Kategorien (nur diese Slugs verwenden):\n" + cat_lines +
"\nOrdne zusätzlich jeden Fakt GENAU EINEM Bereich zu: 'alltag' (der Nutzer als Person: "
"Vorlieben, Leben, Anrede, Arbeitsweise, grundsätzliche Entscheidungen) oder 'projekt' "
"(Technik: Code, Modelle, Server, Infrastruktur, Projekt- und Fehlerstände). "
"Antworte NUR als JSON-Objekt der Form "
"{\"<id>\": {\"kategorie\": \"<slug>\", \"bereich\": \"alltag|projekt\"}, …}."
)
body = "\n".join(f"{fid}: {txt}" for fid, txt in facts)
try:
resp = mem().llm.generate_response(
messages=[{"role": "system", "content": system},
{"role": "user", "content": "Fakten:\n" + body}],
response_format={"type": "json_object"},
)
m = _re.search(r"\{.*\}", resp or "", _re.DOTALL)
data = _json.loads(m.group(0)) if m else {}
out: dict = {}
for k, v in data.items():
if isinstance(v, str): # Alt-Form {"id": "slug"} weiter akzeptieren
if v in CATEGORIES:
out[str(k)] = {"category": v, "bereich": ""}
elif isinstance(v, dict):
cat = v.get("kategorie") or v.get("category") or ""
ber = v.get("bereich") or ""
if cat in CATEGORIES or ber in ("alltag", "projekt"):
out[str(k)] = {"category": cat if cat in CATEGORIES else DEFAULT_CATEGORY,
"bereich": ber if ber in ("alltag", "projekt") else ""}
return out
except Exception:
log.exception("Auto-Klassifikation fehlgeschlagen")
return {}
# Deterministischer Guard: mem0-OSS extrahiert trotz custom_instructions hartnäckig Fakten ÜBER
# den Assistenten selbst (Hermes/Lucy) und transiente Zustände (Commit-Rückstand, Cronjob-Läufe,
# Onboarding-/Test-Fragen). Diese Muster werden nach der Extraktion verworfen (der Fakt wird wieder
# gelöscht) — reine Sicherungsschicht, unabhängig vom Extraktions-LLM.
_JUNK_FACT_PATTERNS = [
r"^\s*(?:der|die|das)\s+assistent", # "Der Assistent ..." (Selbstbeschreibung)
r"\bassistent(?:in)?\s+(?:hermes|lucy)\b", # "... Assistent Hermes/Lucy ..."
r"\bhermes[- ]agent\b",
r"commits?\s+hinter", # Versions-Rückstand (transient)
r"\bcronjob\b",
r"beim\s+letzten\s+(?:lauf|mal)",
r"onboarding[-\s]?frage|\btest-?frage\b",
r"hat\s+den\s+assistenten\s+gebeten",
r"auf\s+betriebstemperatur|hochgefahren",
r"aktualisiert\s+sein\s+ged(?:ä|ae)chtnis", # Selbst-Meta über das Gedächtnis
]
_JUNK_FACT_RE = re.compile("|".join(_JUNK_FACT_PATTERNS), re.IGNORECASE)
def _is_junk_fact(text: str) -> bool:
"""True, wenn der extrahierte Fakt Assistenten-Meta oder ein transienter Zustand ist."""
return bool(_JUNK_FACT_RE.search(text or ""))
@app.post("/learn")
def learn(body: LearnIn) -> dict:
"""Auto-Lernen: Gesprächs-Turns/Text durchreichen → Mem0 EXTRAHIERT Fakten selbst
(infer=True). Anschließend ordnet das Hirn jeden neuen Fakt automatisch einer Kategorie zu."""
msgs = body.messages
if not msgs and body.text:
msgs = [{"role": "user", "content": body.text}]
if not msgs:
raise HTTPException(400, "text oder messages erforderlich")
res = mem().add(
msgs, user_id=USER_ID, infer=True,
metadata={"category": body.category, "source": body.source},
)
results = res.get("results", [])
# Guard: frisch extrahierte Assistenten-Meta/transiente Fakten sofort wieder verwerfen.
kept = []
for r in results:
if r.get("event") in ("ADD", "UPDATE") and _is_junk_fact(r.get("memory", "")):
try:
mem().delete(r["id"])
except Exception:
log.debug("Junk-Fakt-Löschung fehlgeschlagen für %s", r.get("id"))
log.info("mem0 guard: Junk-Fakt verworfen: %s", (r.get("memory") or "")[:80])
continue
kept.append(r)
results = kept
cats: dict = {}
if body.classify:
to_classify = [(r["id"], r.get("memory", "")) for r in results
if r.get("id") and r.get("memory") and r.get("event") in ("ADD", "UPDATE")]
cats = classify_facts(to_classify)
for r in results:
info = cats.get(r.get("id"))
if info:
meta = {"category": info["category"], "source": body.source}
if info.get("bereich"):
meta["bereich"] = info["bereich"]
try:
mem().update(r["id"], data=r.get("memory", ""), metadata=meta)
except Exception:
log.debug("Kategorie-Update fehlgeschlagen für %s", r.get("id"))
return {"results": [
{"id": r.get("id"), "content": r.get("memory"), "event": r.get("event"),
"category": (cats.get(r.get("id")) or {}).get("category", body.category),
"bereich": (cats.get(r.get("id")) or {}).get("bereich", "")}
for r in results
]}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=int(os.environ.get("MEM0_PORT", "8765")))
-62
View File
@@ -1,62 +0,0 @@
#!/usr/bin/env python3
"""
Einmalige Migration: alte SQLite-Fakten (`memories`-Tabelle aus dem v1/v2-Memory)
Mem0. Läuft im ~/.mem0/venv. Idempotent über eine Marker-Datei.
Jeder Alt-Eintrag wird VERBATIM übernommen (infer=False), Kategorie/Source als
Metadaten. Beim aktuellen Box-Stand ist die DB leer No-Op, aber für
Reproduzierbarkeit/künftige Migrationen vorhanden.
Aufruf: ~/.mem0/venv/bin/python migrate.py [pfad/zur/mc2-memory.db]
"""
import os
import sqlite3
import sys
from app import USER_ID, mem # nutzt dieselbe Mem0-Instanz/Config
DEFAULT_DB = os.environ.get("MC_MEMORY_DB", "/srv/models/mc2-memory.db")
MARKER = os.environ.get("MEM0_MIGRATION_MARKER", "/srv/models/mem0/.migrated_sqlite")
def main() -> int:
db_path = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_DB
if os.path.exists(MARKER):
print(f"Migration bereits erledigt (Marker {MARKER}).")
return 0
if not os.path.exists(db_path):
print(f"Keine Alt-DB unter {db_path} — nichts zu migrieren.")
os.makedirs(os.path.dirname(MARKER), exist_ok=True)
open(MARKER, "w").close()
return 0
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
try:
rows = conn.execute(
"SELECT content, category, source FROM memories ORDER BY created_at"
).fetchall()
except sqlite3.OperationalError:
rows = []
conn.close()
m = mem()
n = 0
for r in rows:
content = (r["content"] or "").strip()
if not content:
continue
m.add([{"role": "user", "content": content}], user_id=USER_ID, infer=False,
metadata={"category": r["category"] or "stable",
"source": r["source"] or "migrated"})
n += 1
os.makedirs(os.path.dirname(MARKER), exist_ok=True)
open(MARKER, "w").close()
print(f"Migriert: {n} Einträge aus {db_path} → Mem0.")
return 0
if __name__ == "__main__":
raise SystemExit(main())
-9
View File
@@ -1,9 +0,0 @@
# Mem0-Sidecar-Deps im ~/.mem0/venv (Python 3.12). BEWUSST GEPINNT — Upgrades nur
# absichtlich, weil der Sidecar mem0-INTERNA nutzt (NoThink-LLM-Swap, Roh-Zugriff auf die
# Chroma-Collection für den Graphen, Annahmen übers Result-Format). Ein unkontrolliertes
# mem0/chromadb-Upgrade kann das still brechen.
# Upgrade-Prozedur (Backup → Pin anheben → smoke_test.py): siehe docs/UPGRADE.md.
mem0ai==2.0.8
chromadb==1.5.9
fastapi==0.138.1
uvicorn[standard]==0.49.0
-71
View File
@@ -1,71 +0,0 @@
#!/usr/bin/env python3
"""
Smoke-Test für den Mem0-Sidecar VOR und NACH jedem mem0/chromadb-Upgrade laufen lassen.
Prüft genau die Pfade, die mem0-INTERNA nutzen (LLM-Extraktion/NoThink, Auto-Einordnung,
Embeddings/Suche, Roh-Chroma-Graph). Nutzt eine WEGWERF-Collection unter /tmp das echte
Gedächtnis (/srv/models/mem0) wird NICHT angefasst.
Aufruf: ~/.mem0/venv/bin/python ~/mission-control-v2/mem0_service/smoke_test.py
Exit 0 = alles grün, sonst 1.
"""
import os
import shutil
import sys
import tempfile
import time
# WICHTIG: Env vor dem Import von app setzen (app liest die Pfade beim Modul-Load).
_TMP = tempfile.mkdtemp(prefix="mem0_smoke_")
os.environ["MEM0_CHROMA_PATH"] = os.path.join(_TMP, "chroma")
os.environ["MEM0_HISTORY_DB"] = os.path.join(_TMP, "history.db")
os.environ["MEM0_COLLECTION"] = f"smoke_{int(time.time())}"
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import app
_fails: list[str] = []
def check(name: str, cond: bool, detail: str = "") -> None:
print(("PASS" if cond else "FAIL"), "", name, ("" if cond else f":: {detail}"))
if not cond:
_fails.append(name)
def main() -> int:
app.mem() # Memory bauen (öffnet Chroma, pingt Modelle)
# 1) Verbatim add + Rücklesen
r = app.add_memory(app.MemIn(content="Smoke: der Himmel ist blau.", category="knowledge", source="smoke"))
check("verbatim add + get", bool(r.get("id")) and "blau" in r.get("content", ""), str(r)[:160])
# 2) Embeddings / semantische Suche (Bedeutung, nicht Wort)
res = app.list_memory(q="Welche Farbe hat der Himmel?")
check("semantische Suche", any("blau" in i.get("content", "") for i in res), str(res)[:160])
# 3) Auto-Lernen: LLM-Extraktion (NoThink) + Deutsch erzwungen
lr = app.learn(app.LearnIn(text="Ich heiße Max und programmiere am liebsten in Go.", source="smoke"))
items = lr.get("results", [])
check("Auto-Lernen extrahiert Fakten", len(items) > 0, str(lr)[:160])
joined = " ".join((i.get("content") or "") for i in items).lower()
check("Extraktion auf Deutsch", any(w in joined for w in ("heißt", "programmiert", "mag", "bevorzugt", "name", "nutzt")), joined[:160])
# 4) Auto-Einordnung in die 4 gültigen Kategorien
cats = {i.get("category") for i in items if i.get("category")}
check("Auto-Einordnung gültige Kategorie", bool(cats) and cats <= set(app.CATEGORIES), str(cats))
# 5) Graph aus den Roh-Embeddings (Chroma-Interna)
g = app.graph()
check("Graph liefert Knoten", len(g.get("nodes", [])) >= 1, str({k: len(v) for k, v in g.items()}))
print("\n" + ("ALLE TESTS GRÜN ✓" if not _fails else f"FEHLGESCHLAGEN: {_fails}"))
return 0 if not _fails else 1
if __name__ == "__main__":
try:
code = main()
finally:
shutil.rmtree(_TMP, ignore_errors=True)
sys.exit(code)