From 4cd856bd34e51a50d2eaee9b1eb2293e0d591035 Mon Sep 17 00:00:00 2001 From: Hitonabi Date: Thu, 24 Sep 2026 15:13:40 +0200 Subject: [PATCH] phase1c: Deploy mit Rueckweg und Prueftor, tote Deploy-Dateien raus - deploy.sh zweistufig: Sperre, laufende Update-Jobs verschieben den Deploy, fast-forward, dann laeuft die NEUE Fassung als Stufe 2 (Aenderungen am Skript wirken sofort). Stufe 2: Prueftor, Abhaengigkeiten, llama-swap-Config nur bei Aenderung des Abzugs in diesem Deploy (ohne Motor-Neustart, -watch-config reicht; 5 Sicherungen), alle Repo-Units und Drop-ins, Cron-Skripte nach ~/.hermes/scripts, Skills/Plugins, Neustart, Nachpruefung. Scheitert etwas: zurueck auf den alten Stand (inkl. llama-swap-Config), Dienste neu, dringende Meldung, Eintrag in /srv/models/mc2-deploy.log. - deploy/pruefen.sh ersetzt die tote CI-Ampel: Shell-/Python-Syntax, ruff, Importe, pytest. Laeuft am PC vor dem Push und auf der Box vor dem Umschalten (pytest via requirements-dev.txt). - Units, die nur auf der Box lagen, jetzt im Repo: projekte-sync.*, lucy-stimme.service, mission-control-2-Override. - Tot und entfernt: Werkstatt-/Projektstart-/Betrieb-SOULs, worker.sh, Agent-Hooks, Ampel-CI (samt .gitea-Workflow und Saat in gitea-repo-create.sh), gitea-pr, Governor-Plugin, Specs, Selbst-Inventur, Self-Smoke, venv-Audit, setup_autonomous_crons.sh (haette alte Jobs neu angelegt), Einmal-Skripte, alte Bench-Skripte, .agents/mcp_config.json, client/ide-skills, mcp/requirements.txt. Gitea-Host-Notizen nach docs/archiv/gitea-host. - morgenmeldung.sh begrenzt das Melde-Log (ueber 3 MB bleiben die letzten 2 MB). - AGENTS.md: Prueftor und neuer Deploy beschrieben. Co-Authored-By: Claude Opus 5.5 --- .agents/mcp_config.json | 16 - .gitea/workflows/ampel-ci.yml | 135 -------- AGENTS.md | 17 +- backend/requirements-dev.txt | 2 + .../ide-skills/projekt-uebernehmen/SKILL.md | 76 ----- deploy/agent-hooks/box-steckbrief-inject.sh | 42 --- deploy/agent-hooks/no-progress-bremse.py | 211 ------------- deploy/agent-hooks/pre-verify-gates.sh | 75 ----- deploy/agent-hooks/tabu-pfade-guard.py | 180 ----------- deploy/ampel-ci.yml | 92 ------ deploy/ampel-waechter.sh | 94 ------ deploy/bench/brain-bench.sh | 40 --- deploy/bench/model-bench.sh | 42 --- deploy/betrieb-SOUL.md | 59 ---- deploy/coder-vollkontext.sh | 77 ----- deploy/deploy.sh | 273 +++++++++++------ deploy/ensure-profile-hooks.py | 99 ------ deploy/gateway-cutover.sh | 59 ---- deploy/gitea-pr | 40 --- deploy/gitea-repo-create.sh | 9 +- deploy/lucy-stimme.service | 18 ++ .../mission-control-2.service.d-override.conf | 22 ++ deploy/morgenmeldung.sh | 7 + deploy/opencode-plugin/README.md | 116 ------- deploy/opencode-plugin/mc2-governor.ts | 255 ---------------- deploy/projekte-sync.service | 8 + deploy/projekte-sync.timer | 11 + deploy/projektstart-SOUL.md | 166 ---------- deploy/pruefen.sh | 50 +++ deploy/restore-probe.sh | 149 --------- deploy/selbst-inventur.sh | 131 -------- deploy/self-smoke.sh | 195 ------------ deploy/setup_autonomous_crons.sh | 37 --- deploy/specs/curator-auto-archivierung.md | 287 ------------------ deploy/specs/curator-idle-watchdog.md | 76 ----- deploy/venv-audit.sh | 98 ------ deploy/werkstatt-SOUL.md | 108 ------- deploy/worker.sh | 97 ------ .../SECURITY-GITEA-DISABLE_REGISTRATION.md | 0 .../archiv/gitea-host}/gitea_app.ini.example | 0 mcp/requirements.txt | 3 - 41 files changed, 304 insertions(+), 3168 deletions(-) delete mode 100644 .agents/mcp_config.json delete mode 100644 .gitea/workflows/ampel-ci.yml create mode 100644 backend/requirements-dev.txt delete mode 100644 client/ide-skills/projekt-uebernehmen/SKILL.md delete mode 100644 deploy/agent-hooks/box-steckbrief-inject.sh delete mode 100644 deploy/agent-hooks/no-progress-bremse.py delete mode 100644 deploy/agent-hooks/pre-verify-gates.sh delete mode 100644 deploy/agent-hooks/tabu-pfade-guard.py delete mode 100644 deploy/ampel-ci.yml delete mode 100644 deploy/ampel-waechter.sh delete mode 100644 deploy/bench/brain-bench.sh delete mode 100644 deploy/bench/model-bench.sh delete mode 100644 deploy/betrieb-SOUL.md delete mode 100644 deploy/coder-vollkontext.sh delete mode 100644 deploy/ensure-profile-hooks.py delete mode 100755 deploy/gateway-cutover.sh delete mode 100644 deploy/gitea-pr create mode 100644 deploy/lucy-stimme.service create mode 100644 deploy/mission-control-2.service.d-override.conf delete mode 100644 deploy/opencode-plugin/README.md delete mode 100644 deploy/opencode-plugin/mc2-governor.ts create mode 100644 deploy/projekte-sync.service create mode 100644 deploy/projekte-sync.timer delete mode 100644 deploy/projektstart-SOUL.md create mode 100644 deploy/pruefen.sh delete mode 100644 deploy/restore-probe.sh delete mode 100644 deploy/selbst-inventur.sh delete mode 100644 deploy/self-smoke.sh delete mode 100644 deploy/setup_autonomous_crons.sh delete mode 100644 deploy/specs/curator-auto-archivierung.md delete mode 100644 deploy/specs/curator-idle-watchdog.md delete mode 100644 deploy/venv-audit.sh delete mode 100644 deploy/werkstatt-SOUL.md delete mode 100644 deploy/worker.sh rename {deploy => docs/archiv/gitea-host}/SECURITY-GITEA-DISABLE_REGISTRATION.md (100%) rename {deploy => docs/archiv/gitea-host}/gitea_app.ini.example (100%) delete mode 100644 mcp/requirements.txt diff --git a/.agents/mcp_config.json b/.agents/mcp_config.json deleted file mode 100644 index 15de689..0000000 --- a/.agents/mcp_config.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "mcpServers": { - "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" - ] - } - } -} diff --git a/.gitea/workflows/ampel-ci.yml b/.gitea/workflows/ampel-ci.yml deleted file mode 100644 index bb62025..0000000 --- a/.gitea/workflows/ampel-ci.yml +++ /dev/null @@ -1,135 +0,0 @@ -# Ampel-CI fuer MC2 — auf dieses Multi-Service-Monorepo zugeschnitten (24.07.2026). -# -# Die universelle Vorlage (deploy/ampel-ci.yml) passt fuer EIN-Service-Repos. MC2 hat -# 5 Python-Dienste (backend/voice_service/mem0_service/mcp/client) mit schweren ML- -# Abhaengigkeiten (Whisper/TTS/Embeddings) + ein Frontend. "pip install ALLER requirements -# + pytest repo-weit" in einem stateless Container ist weder machbar (GB-schwere Wheels, -# CUDA) noch aussagekraeftig — die echten Tests sind der Pruefstand (deploy/pruefstand) -# und die Integration auf der Box mit LIVE-Diensten/Modellen, nicht isolierte Unit-Tests. -# -# Darum prueft die MC2-Ampel, was im Container EHRLICH gruen sein kann und trotzdem echte -# Fehler faengt: Lint (ruff, Projekt-Politik in ruff.toml) + Import/Syntax (compileall) + -# Frontend-Build inkl. TypeScript-Typecheck (tsc) + Frontend-Tests (vitest/jsdom) + ESLint -# + Buendel-Budget. Rot ist ein Ergebnis, kein Aergernis. -name: Ampel -on: [push, pull_request] - -jobs: - ampel: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Ampel — Lint + Import + Frontend-Build (MC2-Zuschnitt) - shell: bash - run: | - # Runner-bash laeuft mit -e; abschalten und JEDEN Fehler selbst werten (rot=1), - # am Ende EIN Klartext-Urteil (Lehre Ampel-Lauf #5). - set -u +e - rot=0 - - echo "== Python: Lint (ruff, Projekt-Politik aus ruff.toml) ==" - python3 -m venv /tmp/ampel-venv && . /tmp/ampel-venv/bin/activate || { echo "❌ venv kaputt"; exit 1; } - pip install -q ruff || { echo "❌ ruff-Install kaputt"; exit 1; } - ruff check . || rot=1 - - echo "== Python: Import/Syntax (compileall) ==" - python3 -m compileall -q backend voice_service mem0_service mcp client deploy hermes scripts || rot=1 - - echo "== Frontend: reproduzierbarer Build (npm ci + tsc + vite) ==" - if [ -f frontend/package.json ]; then - if [ ! -f frontend/package-lock.json ]; then - echo "❌ frontend/: kein package-lock.json — Build nicht reproduzierbar." - rot=1 - else - ( cd frontend && npm ci --no-audit --no-fund && npm run build ) || rot=1 - - # Frontend-Tests + Linter (v3-Umbau P1, 28.08.2026). - # Bis dahin lief das Frontend als einziger Teil des Stacks voellig ungeprueft - # durch das Gate — 10 500 Zeilen, null Tests. Die Begruendung weiter oben - # ("echte Tests sind der Pruefstand mit LIVE-Diensten") gilt fuer die - # ML-schweren Python-Dienste, nicht fuer Frontend-Unit-Tests: die laufen in - # jsdom, brauchen weder Modell noch GPU und sind in Sekunden durch. - # Der Linter meldet 0 Fehler / ~90 Warnungen; rot wird nur bei Fehlern. - if [ $rot -eq 0 ]; then - echo "-- Vitest" - ( cd frontend && npm test --silent ) || rot=1 - echo "-- ESLint (Warnungen sind erlaubt, Fehler nicht)" - ( cd frontend && npm run lint --silent ) || rot=1 - fi - - # Bündel-Budget (v3-Umbau P0, 28.08.2026): Der Start-Chunk ist das, was der - # Nutzer VOR dem ersten Bild lädt. Er lag bei 220 kB gzip, weil das eifrig - # geladene Cockpit Recharts mitzog; dist lag bei 27 MB wegen eines verwaisten - # Avatar-Modells. Ohne Deckel wächst beides unbemerkt zurück. Gemessen wird der - # FRISCHE Build im Runner — kein Vergleich mit dem committeten dist (das wäre - # über Node-Versionen hinweg flatterhaft und würde dauerhaft rot leuchten). - if [ $rot -eq 0 ] && [ -d frontend/dist/assets ]; then - # 28.08.2026, React 18 -> 19: Der Sprung kostete GEMESSEN 14 645 B gzip - # (118 762 -> 133 407) und riss das bis dahin geltende Budget von 125 000. - # Das Gate hat also getan, was es soll. Angehoben wurde es TROTZDEM — - # bewusst und einmalig, nicht weil es im Weg stand: - # · Der Zuwachs IST die Plattform, nicht Wildwuchs. Es gibt hier nichts - # wegzulassen, so wie beim Router (P2), wo Schublade und Palette - # hinter lazy() wanderten und das Budget scharf blieb. - # · MC2 ist eine LAN-Appliance. 14 kB sind ueber Gigabit-Ethernet keine - # messbare Wartezeit; das Budget existiert gegen DRIFT (ein 24-MB-Avatar, - # eine 95-kB-Diagramm-Bibliothek auf der Startseite), nicht gegen einen - # ueberlegten Plattform-Schritt. - # Der Abstand zum Budget bleibt derselbe wie vorher (~5 %). - budget_gz=140000 # Stand nach React 19: 133 407 B gzip - budget_dist=3145728 # Stand nach P0: 1 477 852 B - # Den Einstiegs-Chunk aus index.html lesen, NICHT per Glob raten: - # Rollup nennt auch kleine geteilte Module "index-*.js" (gemessen: ein - # 67-Byte-Chunk neben dem 375-kB-Einstieg). `ls | head -1` haette je nach - # Hash den falschen erwischt — und das Budget waere still immer gruen. - einstieg=$(grep -o 'assets/index-[A-Za-z0-9_-]*\.js' frontend/dist/index.html | head -1) - haupt="frontend/dist/$einstieg" - if [ -n "$haupt" ]; then - gz=$(gzip -c "$haupt" | wc -c) - echo "-- Start-Chunk: $gz B gzip (Budget $budget_gz)" - if [ "$gz" -gt "$budget_gz" ]; then - echo "❌ Start-Bündel über Budget. Meist eine neue Bibliothek, die über" - echo " das eifrig geladene Cockpit hereinkommt — hinter lazy() legen." - rot=1 - fi - fi - gesamt=$(du -sb frontend/dist | cut -f1) - echo "-- dist gesamt: $gesamt B (Budget $budget_dist)" - if [ "$gesamt" -gt "$budget_dist" ]; then - echo "❌ dist über Budget — Ballast? (Schrift-Subsets, Medien, WOFF 1)" - rot=1 - fi - fi - - 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 - - if [ $rot -ne 0 ]; then - echo "❌ AMPEL ROT — nichts heißt ‚fertig', solange das rot ist." - else - echo "✅ AMPEL GRÜN — Lint + Import + Frontend-Build sauber." - fi - exit $rot diff --git a/AGENTS.md b/AGENTS.md index 1957c5c..54c8916 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,18 +24,21 @@ Engine (llama-swap) · Builtin-Routing-Gateway (`model: auto`) · MC2 (FastAPI + - **`frontend/dist` WIRD committet.** Auf der Box läuft KEIN Node-Build; das Backend liefert die gebauten Assets direkt aus. Nach jeder Frontend-Änderung: `cd frontend && npm run build`, dann **das neue `frontend/dist` mit-committen**. Vergessen = Box zeigt alten Stand. -- **Deploy macht `git pull origin main`** (`deploy/deploy.sh`). Heißt: **`main` muss vor dem - Deploy auf Gitea liegen**, und Änderungen an getrackten Dateien im Box-Checkout lassen den Pull - scheitern — dort nie von Hand ändern. Neue Schritte in `deploy.sh` greifen erst beim nächsten Lauf. - Schritt 3 überschreibt die lebende llama-swap-Config, wenn sie vom Abzug abweicht - (`MC_DEPLOY_SKIP_SWAP_CONFIG=1` verhindert das). +- **Deploy** (`bash ~/mission-control-v2/deploy/deploy.sh` auf der Box) holt `origin/main` per + fast-forward — **`main` muss vorher auf Gitea liegen**, im Box-Checkout nie von Hand ändern. + Zweistufig seit 24.09.2026: Stufe 1 holt den Stand und startet die NEUE Fassung des Skripts, + Stufe 2 prüft (pruefen.sh), spielt Units/Cron-Skripte/Skills aus, startet neu und prüft nach. + Scheitert etwas: automatisch zurück auf den alten Stand + dringende Meldung. Laufende Update-Jobs + verschieben den Deploy. Die llama-swap-Config wird nur überschrieben, wenn sich der Repo-Abzug im + selben Deploy geändert hat (sonst gewinnt die lebende Datei, die MC2 selbst schreibt). - **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. +- **Prüftor:** `bash deploy/pruefen.sh` (Shell-/Python-Syntax, ruff, Importe, pytest) muss vor jedem Push grün sein. + Der Deploy auf der Box führt es vor dem Umschalten noch einmal aus; rot = automatisch zurück auf den alten Stand. ## Zeit & Umgebung - **Box = Ubuntu, läuft in `Europe/Berlin`** (seit 03.07.2026; vorher UTC). Dev-PC = Windows. @@ -47,7 +50,7 @@ Engine (llama-swap) · Builtin-Routing-Gateway (`model: auto`) · MC2 (FastAPI + (Single Source of Truth — Router bleiben dünn). Beispiel-Lehre: Restart-Allowlist lebt NUR in `services.maintenance` (System-Dienste via `sudo -n`, User-Dienste via `systemctl --user`); keine zweite Allowlist in einem Router duplizieren. -- **Gate vor Commit:** `python -m py_compile ` muss durchlaufen. +- **Gate vor Commit:** `bash deploy/pruefen.sh` (enthält `py_compile`, `ruff check .` und die Tests). - Wartung ist **sudo-frei** gedacht (systemctl --user). Wo doch sudo nötig ist (llama-swap = System-Dienst), sauber über die NOPASSWD-Whitelist / `password_required`-Rückgabe, nie hart failen. - ‼️ **Die feingranularen sudoers.d-Regeln sind faktisch wirkungslos.** In `/etc/sudoers` steht diff --git a/backend/requirements-dev.txt b/backend/requirements-dev.txt new file mode 100644 index 0000000..59f5fa6 --- /dev/null +++ b/backend/requirements-dev.txt @@ -0,0 +1,2 @@ +# Nur fürs Prüftor (deploy/pruefen.sh) — die Dienste selbst brauchen das nicht. +pytest>=8 diff --git a/client/ide-skills/projekt-uebernehmen/SKILL.md b/client/ide-skills/projekt-uebernehmen/SKILL.md deleted file mode 100644 index 8b8b175..0000000 --- a/client/ide-skills/projekt-uebernehmen/SKILL.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -name: projekt-uebernehmen -description: Ein von der AI-Box vorbereitetes Projekt in Zed uebernehmen und starten - Konzept und Roadmap einlesen, die offenen Punkte mit dem Commander klaeren, dann Etappe 1 planen und bauen. Nutze diesen Skill beim ERSTEN Chat in einem Projekt-Ordner, der KONZEPT.md und ROADMAP.md enthaelt, aber noch keinen (oder kaum) eigenen Code - also bei jedem frisch vorbereiteten Repo aus dem Auftragsbuch. -metadata: - quelle: mission-control-2/client/ide-skills ---- - -# Vorbereitetes Projekt übernehmen - -Die Box hat gedacht, du baust. Das Konzept in diesem Ordner ist **bereits gehärtet** — mehrere -Fach-Rollen und ein Advocatus Diaboli haben es auf der Box durchgearbeitet. Deine Aufgabe ist -nicht, es neu zu denken, sondern es umzusetzen. - -**Zwei Modelle, ein Ablauf:** Stufe 1 und 2 (einlesen, nachfragen, planen) gehören in den -**Planer**, Stufe 3 (bauen) in den **Coder**. Du kannst nicht selbst umschalten — wenn der Plan -freigegeben ist, bitte den Commander ausdrücklich darum, oben im Modellwähler auf Coder zu -wechseln. Läufst du schon im Coder, ist das auch in Ordnung: dann gilt trotzdem erst lesen, -fragen, Plan zeigen — und **keine Datei vor seinem „los"**. - -Drei Stufen, in dieser Reihenfolge. **Vor Stufe 3 wird keine Datei angefasst.** - -## 1. Einlesen — still, ohne Rückfragen - -Lies, was da ist: `KONZEPT.md`, `ROADMAP.md`, `AGENTS.md`, `SAVEPOINT.md`, `README.md`. -Verschaff dir den Ist-Zustand: `git log --oneline -5` und den Dateibaum. - -Zwei Abbruch-Fälle — prüfe sie ZUERST: - -- **Es gibt schon Code und Historie.** Dann ist das keine Übernahme, sondern Fortsetzung: - arbeite nach `SAVEPOINT.md` weiter und sag in zwei Sätzen, wo ihr steht. Dieser Skill ist - hier fertig. -- **Nur `KONZEPT.md` + `README.md`, keine `ROADMAP.md`.** Das ist ein *geprüfte-Idee*-Repo, kein - startklares Projekt — die Box legt bei „Idee prüfen" bewusst kein Gerüst an. Sag dem Commander: - *„Das ist bisher nur ein durchdachtes Konzept, noch kein Projekt. Im Auftragsbuch gibt es auf - der Karte den Knopf ‚Gefällt mir — Projekt daraus machen'; danach hat das Repo eine Roadmap und - ich kann loslegen."* Dann **STOPP**, nichts bauen. - -Sonst: fasse in **höchstens 8 Zeilen** zusammen — was gebaut wird, für wen, welche Technik, -und was laut Roadmap **Etappe 1** ist. Alltagssprache, keine Fachbegriffe ohne Übersetzung. - -## 2. Fragen stellen — der eigentliche Wert dieses Schritts - -Stell **3 bis 6 Fragen**. Nicht mehr, und nur solche, die den Start wirklich blockieren. - -- **Nichts fragen, was in den Dokumenten steht.** Wer das Konzept nicht gelesen hat, merkt man - genau hier. -- Jede Frage: eine Zeile, Alltagssprache, **mit deiner Empfehlung** („ich würde X nehmen, weil …"). - Der Commander ist kein Entwickler — frag nach Zielen, Vorlieben und Gegebenheiten, nie nach - Bau-Prinzipien oder Architektur-Geschmack. Das ist dein Job. -- Die üblichen echten Lücken: Wo läuft es am Ende? Welche Zugänge/Geräte/Pfade brauchst du von - ihm? Was ist in Etappe 1 ausdrücklich **nicht** drin? Woran merkt ihr beide, dass Etappe 1 - fertig ist? -- Der Abschnitt „offene Punkte" / „Risiken" im Konzept ist deine Fundgrube — genau dafür steht er da. - -Dann **warten**. Keine Annahmen, kein Vorpreschen, kein „ich fange schon mal an". - -## 3. Plan → „los" → bauen - -1. **Etappe-1-Plan** vorlegen: Ziel · Vorgehen in kleinen Schritten · Technik und warum · - Fertig-Kriterien · Risiken. Etappe 1 muss **für sich lauffähig** sein — eine halbe Baustelle ist - kein Meilenstein. -2. **STOPP**, auf sein „los" warten. Danach der Satz, den er hören muss: *„Bitte oben im - Modellwähler auf **Coder** umschalten, dann lege ich los."* Umschalten kannst du nicht selbst. -3. Bauen: kleine Schritte, ein Sicherungspunkt (Commit mit klarem Namen) je Meilenstein und vor - riskanten Änderungen. Am Ende **live testen** — beweisen statt behaupten. -4. Nachführen: `SAVEPOINT.md` (Stand, nächster Schritt, offene Fragen) und die erledigte Etappe in - `ROADMAP.md` abhaken. Das ist das Gedächtnis für den nächsten Chat. - -## Merksätze - -- **Du denkst das Konzept nicht neu.** Findest du einen echten Widerspruch oder eine Lücke: - benenne sie und frag — bau nicht still etwas anderes. -- **Nichts bauen, was nicht in Etappe 1 steht.** Ideen für später gehören in die Roadmap, nicht - in den Code. -- Geheimnisse (Schlüssel, Passwörter, Tokens) nie in den Code — `.env` plus `.gitignore`. -- Klemmt dieselbe Sache zweimal: anhalten, sichern, erklären, fragen. diff --git a/deploy/agent-hooks/box-steckbrief-inject.sh b/deploy/agent-hooks/box-steckbrief-inject.sh deleted file mode 100644 index dc14b0f..0000000 --- a/deploy/agent-hooks/box-steckbrief-inject.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env bash -# pre_llm_call-Hook (Faden 5, 13.07.2026): verdrahtet Box-Selbstwissen MECHANISCH in -# JEDEN Kanban-Worker — egal welches Profil (werkstatt, betrieb, default-als-Worker). -# Der Kontext wird ephemer pro Turn an die User-Message angehaengt (agent/turn_context.py), -# NUR wenn die Session in einem Kanban-Task-Workspace laeuft. Lucys interaktiver/Voice-Chat -# laeuft NICHT dort → sofortiger No-op, kein Persona- oder Latenz-Eingriff. Loest -# Fallstrick #1 (Ops/Mess/Verify-Worker flailen, weil sie llama-swap :8080 nicht kennen). -# Ausgabe {"context":"..."} = anhaengen; {} = durchlassen (No-op). -set -u -payload="$(cat -)" - -# Scope-Gate: NUR echte Kanban-Worker — erkannt an der cwd im Task-Workspace. -# ACHTUNG (Bug-Fund Faden 8, 13.07.): Worker tragen als task_id den SESSION-Zeitstempel -# (z.B. 20260713_224206_267304), NICHT die Kanban-t_-ID — ein "nur t_"-Check feuerte also -# NIE fuer Worker. Der zuverlaessige Signal ist die cwd unter kanban/workspaces/; Lucy/ -# Voice/CLI laufen nie dort. -cwd=$(printf %s "$payload" | jq -r '.cwd // ""' 2>/dev/null || echo "") -case "$cwd" in - "$HOME/.hermes/kanban/workspaces/"*) : ;; # Kanban-Worker → Orientierung anhaengen - *) printf '{}'; exit 0 ;; # Lucy / Voice / CLI / interaktiv → No-op -esac - -# Zeiger auf den vollen, naechtlich generierten Selbst-Steckbrief (nur wenn er existiert). -STECKBRIEF="$HOME/.hermes/state/selbst-steckbrief.md" -hint="" -[ -f "$STECKBRIEF" ] && hint=" Dein vollstaendiger Selbst-Steckbrief (aktuelle Versionen/Dienste/Modelle/Crons/offene Karten) liegt in $STECKBRIEF — dort nachlesen statt raten." - -# Stabile Anti-Flail-Orientierung: WER bin ich, WO laufen die Modelle, WAS ist tabu. -ctx="$(cat < "" (immer privat, letzte Ausgabezeile = CLONE ), dann voll klonen und darin arbeiten. NUR mission-control-v2 und das Lucy-Repo NIE neu anlegen/veraendern. -KONTEXT-BUDGET & ETAPPEN-REGEL (15.07./20.07. — Worker starben an finish_reason=length UND an vollgelaufenem Kontext): Dein Kontext ist ENDLICH und fuellt sich vor allem durch GROSSE Datei- und Terminal-Ausgaben. Lies NIE eine grosse Datei am Stueck — nutze grep und Zeilenbereiche (read mit offset/limit); Datei-Reads sind auf 50k Zeichen gedeckelt, aber auch viele mittlere summieren sich. Lies gezielt ueber den Wegweiser statt ls-Ketten und Volltexte. Merkst du, dass die Aufgabe mehr als ~5 Dateien oder mehrere Baustellen umfasst: NICHT durchziehen — schneide Etappen (eine Etappe = ein Worker-Lauf): lege Teil-Karten an (kanban_create, falls verfuegbar) und schliesse deine Karte per kanban_complete mit dem Etappen-Plan ab; alternativ kanban_block mit dem Etappen-Vorschlag als Frage. -EOF -)" - -jq --null-input --arg c "$ctx" '{context:$c}' diff --git a/deploy/agent-hooks/no-progress-bremse.py b/deploy/agent-hooks/no-progress-bremse.py deleted file mode 100644 index d849f41..0000000 --- a/deploy/agent-hooks/no-progress-bremse.py +++ /dev/null @@ -1,211 +0,0 @@ -#!/usr/bin/env python3 -"""No-Progress-Bremse (Phase 2 des Drei-Welten-Plans, 19.07.2026). - -Generische, turn-UEBERGREIFENDE Schleifenbremse — ergaenzt Hermes' native -tool_loop_guardrails, die (a) pro Turn zaehlen und (b) nur als Fehler -klassifizierte Ergebnisse sehen. Die teuer gelernte Luecke (SSH-Marathon 18.07., -36 min / 75 Schleifen): ein Befehl laeuft "erfolgreich" durch, die AUSGABE sagt -aber immer dasselbe ("Permission denied") — der Agent variiert Befehle gegen -eine unsichtbare Wand, ueber Turn-Grenzen hinweg, und nichts stoppt ihn. - -Prinzip (KEINE hartkodierten Fehler-Strings — Leitplanke des Commanders: -"wenn sich was aendert, muss es das SELBST erkennen"): - post_tool_call → Ergebnis normalisieren (Zahlen/Hex-IDs raus) → Muster-Hash. - Gleicher Hash beim gleichen Tool in Folge = Zaehler hoch. - Unbekannte Muster landen in neue-signaturen.jsonl — - Futter fuer Traum/Radar, um daraus Playbooks zu verdichten. - pre_tool_call → Zaehler >= 3 fuer dieses Tool: EINMAL mechanisch blocken - (Diagnose-Anweisung statt naechster Blindversuch), Zaehler - zuruecksetzen. Der Folgeversuch ist frei; laeuft er wieder - 3x gegen dasselbe Muster, blockt es erneut. - -Gezaehlt wird nur, wo Wiederholung wirklich Stillstand heisst: terminal IMMER -(dort verstecken sich die "erfolgreichen" Fehler), andere Tools nur bei -status=error. Lese-Tools deckt die native idempotent-Bremse ab. - -Aufruf (hooks in config.yaml): `no-progress-bremse.py pre` bzw. `... post`. -State: ~/.hermes/state/no-progress/.json (48 h Selbst-Aufraeumen). -Fail-open: jeder eigene Fehler -> {} (die Bremse darf nie selbst zur Wand werden). -""" -import hashlib -import json -import os -import re -import sys -import time - -STATE_DIR = os.path.expanduser("~/.hermes/state/no-progress") -SIG_LOG = os.path.join(STATE_DIR, "neue-signaturen.jsonl") -SEEN_FILE = os.path.join(STATE_DIR, "seen-sigs.json") -THRESHOLD = 3 -STATE_TTL = 48 * 3600 - -BLOCK_REASON = ( - "NO-PROGRESS-BREMSE: '{tool}' hat {n}x in Folge dasselbe Ergebnismuster geliefert — " - "das ist Stillstand, egal wie sehr die Befehle variieren. STOPP. " - "WICHTIG: Dein Befehl wurde NICHT ausgefuehrt. Das hier ist eine Bremse, KEIN Ergebnis — " - "schliesse daraus NICHTS ueber die Existenz oder den Zustand von Dateien, Verzeichnissen " - "oder Diensten (Vorfall 21.07.2026: ein Worker hielt diese Meldung fuer 'Verzeichnis nicht " - "vorhanden' und baute eine halbe Stunde Arbeit neu). " - "1) Benenne das Hindernis in EINEM Satz (was genau meldet das Ergebnis?). " - "2) Diagnose statt Variation — bei SSH/Zugriff ZUERST: Schluessel-Passphrase pruefen " - "(`ssh-keygen -y -f bool: - """True, wenn der Befehl ausschliesslich schaut — kein Schreiben, kein Eingriff.""" - cmd = (cmd or "").strip() - if not cmd or ">" in cmd: # jede Umleitung = schreiben - return False - for teil in re.split(r"&&|\|\||;|\|", cmd): - worte = teil.split() - i = 0 - while i < len(worte) and "=" in worte[i] and not worte[i].startswith("-"): - i += 1 # VAR=wert-Praefixe ueberspringen - if i >= len(worte): - return False - verb = os.path.basename(worte[i]) - if verb == "git": - rest = [w for w in worte[i + 1:] if not w.startswith("-")] - # `git log …` oder `git -C log …` → erstes oder zweites Wort zaehlt - if not (rest[:1] and rest[0] in _GIT_LESEND) and \ - not (rest[1:2] and rest[1] in _GIT_LESEND): - return False - elif verb not in _LESEND: - return False - return True - - -_NUM_RE = re.compile(r"\d+") -_HEX_RE = re.compile(r"\b[0-9a-f]{8,}\b") -_WS_RE = re.compile(r"\s+") - - -def _norm_sig(tool: str, text: str) -> str: - t = (text or "")[:2000].lower() - t = _HEX_RE.sub("#", _NUM_RE.sub("#", t)) - t = _WS_RE.sub(" ", t).strip()[:400] - return hashlib.sha1(f"{tool}|{t}".encode()).hexdigest()[:16] - - -def _session_key(p: dict) -> str: - sid = p.get("session_id") or p.get("parent_session_id") or "" - if sid: - return re.sub(r"[^A-Za-z0-9_.-]", "_", str(sid))[:80] - return "cwd-" + hashlib.sha1(os.getcwd().encode()).hexdigest()[:12] - - -def _prune() -> None: - now = time.time() - for f in os.listdir(STATE_DIR): - fp = os.path.join(STATE_DIR, f) - if f.endswith(".json") and f != os.path.basename(SEEN_FILE): - if now - os.path.getmtime(fp) > STATE_TTL: - os.remove(fp) - - -def _load(path: str) -> dict: - try: - with open(path, encoding="utf-8") as fh: - return json.load(fh) - except Exception: - return {} - - -def _save(path: str, data: dict) -> None: - tmp = path + ".tmp" - with open(tmp, "w", encoding="utf-8") as fh: - json.dump(data, fh) - os.replace(tmp, path) - - -def main() -> None: - mode = sys.argv[1] if len(sys.argv) > 1 else "post" - payload = json.load(sys.stdin) - tool = str(payload.get("tool_name") or "") - if not tool: - print("{}") - return - # Delegations-Ausnahme (20.07.2026): worker.sh / fremdblick.sh sind der - # Fliessband-/Orchestrator-Motor — konzept-fliessband ruft sie pro Runde/Rolle - # WIEDERHOLT auf (verschiedene Prompts, aehnliche Prosa-Ergebnisse). Die Bremse - # bildet ihre Signatur aus dem Ergebnis-Text → sie hielt diese legitimen - # Mehrrunden faelschlich fuer eine Schleife und blockte (Pomodoro-IDE-Test lief - # 19 min dagegen an). Diese Helfer haben ihr EIGENES Runden-Limit (max 3) → - # von der generischen Bremse ausnehmen. - _ti = payload.get("tool_input") - _cmd = _ti.get("command") if isinstance(_ti, dict) else (_ti if isinstance(_ti, str) else "") - if tool == "terminal" and ("worker.sh" in str(_cmd) or "fremdblick.sh" in str(_cmd)): - print("{}") - return - # Schauen ist keine Schleife (siehe _nur_lesend): weder zaehlen noch blocken. - if tool == "terminal" and _nur_lesend(str(_cmd)): - print("{}") - return - os.makedirs(STATE_DIR, exist_ok=True) - sfile = os.path.join(STATE_DIR, _session_key(payload) + ".json") - state = _load(sfile) - tools = state.setdefault("tools", {}) - - if mode == "post": - status = str(payload.get("status") or "ok") - # terminal immer beobachten (dort tarnen sich Fehler als Erfolg), - # sonst nur echte Fehler — Lese-Tools regelt die native Bremse. - if tool != "terminal" and status != "error": - print("{}") - return - sig = _norm_sig(tool, str(payload.get("result") or "")) - entry = tools.get(tool) or {} - entry["count"] = entry.get("count", 0) + 1 if entry.get("sig") == sig else 1 - entry["sig"] = sig - tools[tool] = entry - _save(sfile, state) - _prune() - seen = _load(SEEN_FILE) - sigs = seen.setdefault("sigs", []) - if sig not in sigs and status == "error" or (tool == "terminal" and sig not in sigs and entry["count"] >= 2): - sigs.append(sig) - del sigs[:-500] - _save(SEEN_FILE, seen) - sample = _WS_RE.sub(" ", str(payload.get("result") or ""))[:200] - with open(SIG_LOG, "a", encoding="utf-8") as fh: - fh.write(json.dumps({"ts": int(time.time()), "tool": tool, - "sig": sig, "sample": sample}, ensure_ascii=False) + "\n") - print("{}") - return - - # mode == "pre" - entry = tools.get(tool) or {} - if entry.get("count", 0) >= THRESHOLD: - entry["count"] = 0 - tools[tool] = entry - _save(sfile, state) - print(json.dumps({"decision": "block", - "reason": BLOCK_REASON.format(tool=tool, n=THRESHOLD)}, - ensure_ascii=False)) - return - print("{}") - - -if __name__ == "__main__": - try: - main() - except Exception: - print("{}") diff --git a/deploy/agent-hooks/pre-verify-gates.sh b/deploy/agent-hooks/pre-verify-gates.sh deleted file mode 100644 index 0741637..0000000 --- a/deploy/agent-hooks/pre-verify-gates.sh +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env bash -# pre_verify-Gate (P1-5, 09.07.2026): erinnert den Agenten VOR dem Fertigmelden an -# die zwei MC2-Pflichten aus AGENTS.md — py_compile-Gate und die dist-Falle. -# Antwort {"action":"continue","message":...} = Agent arbeitet weiter; {} = durchlassen. -set -u -payload="$(cat -)" -attempt=$(printf %s "$payload" | jq -r '.extra.attempt // 0' 2>/dev/null || echo 0) -# Ein-Schuss: nach dem ersten Nudge nicht erneut nerven (Docs-Empfehlung). -if [ "$attempt" != "0" ]; then printf '{}'; exit 0; fi -mapfile -t paths < <(printf %s "$payload" | jq -r '.extra.changed_paths[]? // empty' 2>/dev/null) -[ ${#paths[@]} -eq 0 ] && { printf '{}'; exit 0; } -msgs=() - -# Gate 1: py_compile fuer alle geaenderten .py, die existieren -pyfail="" -for p in "${paths[@]}"; do - case "$p" in - *.py) - if [ -f "$p" ] && ! err=$(python3 -m py_compile "$p" 2>&1); then - pyfail="${pyfail}${p}: ${err}"$'\n' - fi - ;; - esac -done -if [ -n "$pyfail" ]; then - msgs+=("py_compile-Gate ROT:"$'\n'"${pyfail}Erst fixen, dann fertigmelden.") -fi - -# Gate 2: dist-Falle — frontend/src geaendert, aber kein frontend/dist angefasst. -# Feuert NUR in Repos mit der MC2-Konvention (frontend/dist ist git-getrackt) — -# sonst nudgt es in beliebigen Spielordnern ohne Build (False-Positive, 09.07. beobachtet). -srcfile=""; dist=0 -for p in "${paths[@]}"; do - case "$p" in - */frontend/src/*|frontend/src/*) srcfile="$p" ;; - */frontend/dist/*|frontend/dist/*) dist=1 ;; - esac -done -if [ -n "$srcfile" ] && [ $dist -eq 0 ]; then - # Repo-Wurzel vom geaenderten File aus hochlaufen (max 8 Ebenen). - root=$(dirname "$srcfile"); n=0 - while [ $n -lt 8 ] && [ -n "$root" ] && [ "$root" != "/" ] && [ ! -e "$root/.git" ]; do - root=$(dirname "$root"); n=$((n+1)) - done - if [ -e "$root/.git" ] && git -C "$root" ls-files frontend/dist 2>/dev/null | head -1 | grep -q .; then - msgs+=("AGENTS.md-Gate: Du hast frontend/src geaendert, aber frontend/dist nicht. Die Box baut NICHT selbst: cd frontend && npm run build, dann das neue dist im selben Branch mit-committen — sonst zeigt die Box nach Merge+Deploy den alten Stand.") - fi -fi - -# Gate 3 (R5, 15.07.): Orphan-Check — git-init-Repos ohne gemeinsamen Ursprung mit -# origin/main sind bei der Annahme technisch tot (3 Vorfaelle 12.–14.07., je eine -# Nacht Arbeit verloren). VOR dem Fertigmelden fangen, nicht erst im Auftragsbuch. -orphan=""; checked="" -for p in "${paths[@]}"; do - root=$(dirname "$p"); n=0 - while [ $n -lt 8 ] && [ -n "$root" ] && [ "$root" != "/" ] && [ ! -e "$root/.git" ]; do - root=$(dirname "$root"); n=$((n+1)) - done - [ -e "$root/.git" ] || continue - case " $checked " in *" $root "*) continue ;; esac - checked="$checked $root" - git -C "$root" remote get-url origin >/dev/null 2>&1 || continue - git -C "$root" fetch -q origin main >/dev/null 2>&1 || true - if git -C "$root" rev-parse --verify -q origin/main >/dev/null 2>&1 \ - && ! git -C "$root" merge-base HEAD origin/main >/dev/null 2>&1; then - orphan="${orphan}${root}"$'\n' - fi -done -if [ -n "$orphan" ]; then - msgs+=("Orphan-Gate ROT: Dieses Repo hat KEINEN gemeinsamen Ursprung mit origin/main (git init statt Klonen?):"$'\n'"${orphan}So ist der Vorschlag bei der Annahme technisch TOT. Nicht fertigmelden — frisch VOLL klonen, Branch von origin/main abzweigen, Aenderungen ruebertragen.") -fi - -if [ ${#msgs[@]} -eq 0 ]; then printf '{}'; exit 0; fi -joined=$(printf '%s\n\n' "${msgs[@]}") -jq --null-input --arg m "$joined" '{action:"continue", message:$m}' diff --git a/deploy/agent-hooks/tabu-pfade-guard.py b/deploy/agent-hooks/tabu-pfade-guard.py deleted file mode 100644 index 06c0146..0000000 --- a/deploy/agent-hooks/tabu-pfade-guard.py +++ /dev/null @@ -1,180 +0,0 @@ -#!/usr/bin/env python3 -"""pre_tool_call-Guard (Faden 8, 13.07.2026): sperrt Schreibzugriffe auf den LIVE-Checkout -~/mission-control-v2 und die Hermes-QUELLE ~/.hermes/hermes-agent HART. Loest Fallstrick #2 -(Worker wandert in die falsche Ebene / lag Patches im Live-Checkout ab). Die native -is_write_denied deckt diese Pfade NICHT ab (nur Credentials/System) -> diese Luecke. - -UNBEDINGT (kein Task-/Profil-Scope): KEIN legitimer Hermes-Agent-Flow schreibt je in diese -zwei Pfade — der Werkstatt-Worker arbeitet im WORKSPACE-Klon (/mission-control-v2, -NICHT home-verankert -> erlaubt), Lucy fasst MC2-Quelle nie an, und die Wartungs-Pipeline -(deploy.sh, Annahme-Runner) laeuft als Shell ueber ssh, NICHT ueber Hermes-Tools. Ein -Task-Scope waere hier sogar gefaehrlich: Worker tragen als task_id den Session-Zeitstempel -(z.B. 20260713_224206_267304), NICHT die Kanban-t_-ID — ein "nur t_"-Check liesse also alle -Worker-Schreibzugriffe durch (Bug 13.07., so entdeckt). - -WICHTIG: Gesperrt wird der home-verankerte Live-Checkout ($HOME/mission-control-v2); der -blosse Name "mission-control-v2" zaehlt NUR, wenn er vom cwd aus in einen Tabu-Pfad aufloest -(V16 unten) — der Workspace-Klon (~/.hermes/kanban/workspaces//mission-control-v2) bleibt -frei, sonst waere die legitime Worker-Arbeit blockiert. Lesen ist erlaubt; geblockt werden -nur Schreib-Operationen. - -Ausgabe {"action":"block","message":...} = Tool geblockt; {} = durchlassen. -""" -import json -import os -import re -import sys - - -def out(obj): - sys.stdout.write(json.dumps(obj)) - sys.exit(0) - - -try: - payload = json.loads(sys.stdin.read() or "{}") -except Exception: - out({}) - -tool = payload.get("tool_name") or "" -ti = payload.get("tool_input") or {} -home = os.path.realpath(os.path.expanduser("~")) -LIVE = os.path.join(home, "mission-control-v2") -SRC = os.path.join(home, ".hermes", "hermes-agent") -# R2 (Review 15.07.): systemd-User-Units. Die Nacht-Werkstatt hat am 14.07. einen Override -# DIREKT scharf geschaltet — ging gut, verletzt aber "jede Code-Zeile ist Klick-pflichtig". -# Unit-/Override-Dateien werden nur noch ueber eine angenommene Karte installiert. -SYSD = os.path.join(home, ".config", "systemd", "user") - -MSG = ( - "TABU (Faden 8/R2): Schreiben an dieser Stelle ist fuer Worker gesperrt. Der Live-Checkout " - "~/mission-control-v2, die Hermes-Quelle ~/.hermes/hermes-agent und die systemd-User-Units " - "~/.config/systemd/user duerfen NIE direkt veraendert werden. Arbeite im FRISCHEN Klon " - "deines Task-Workspaces; MC2-Aenderungen kommen als Vorschlags-Branch, systemd-Unit-/" - "Override-Dateien werden nur ueber eine angenommene Karte installiert, Hermes-Verhalten " - "nur ueber Config/SOUL/Skill/Hook/MCP. Siehe docs/wissen/GRENZEN.md." -) - - -def block(): - out({"action": "block", "message": MSG}) - - -def under_tabu(path): - rp = os.path.realpath(path) - return any(rp == t or rp.startswith(t + os.sep) for t in (LIVE, SRC, SYSD)) - - -# --- Datei-Tools: Zielpfad kanonisch pruefen (zuverlaessig) ------------------------- -if tool in ("write_file", "patch"): - fp = ti.get("path") or ti.get("file_path") or ti.get("filepath") or "" - if not fp: - out({}) - fp = os.path.expanduser(fp) - cwd = payload.get("cwd") or "" - if not os.path.isabs(fp) and cwd: - fp = os.path.join(cwd, fp) - if under_tabu(fp): - block() - out({}) - -# --- Terminal: nur bei Schreib-Operation, die auf einen Tabu-Pfad zielt ------------- -if tool == "terminal": - cmd = ti.get("command") or "" - if not cmd: - out({}) - cwd_raw = payload.get("cwd") or "" - cwd = os.path.realpath(os.path.expanduser(cwd_raw)) if cwd_raw else "" - # R5 (Review 15.07.): `git init` im Kanban-Workspace hart blocken. Drei Naechte in - # Folge bauten Worker git-init-Orphans ohne gemeinsamen Ursprung mit main - # (cleanse-skill-index 12.07., skill-kanten-generator 13.07., blogwatcher-logging - # ~14.07.) — solche Vorschlaege sind bei der Annahme technisch tot, die Nacht-Arbeit - # verpufft. Kein legitimer Worker-Flow braucht git init; MC2-/Lucy-Arbeit = VOLL - # klonen (SOUL/GRENZEN). Scope: nur Kanban-Worker (Lucy/CLI duerfen z. B. fuer - # projekt-start neue Repos initialisieren). - ws_dir = os.path.join(home, ".hermes", "kanban", "workspaces") - if cwd_raw.startswith(ws_dir) and re.search(r"(?:^|[^\w])git\s+init\b", cmd): - out({"action": "block", "message": ( - "TABU (R5): `git init` ist im Worker-Workspace gesperrt — Orphan-Branches ohne " - "gemeinsamen Ursprung mit main sind bei der Annahme technisch tot (Vorfaelle " - "12.–14.07.). Richtiger Weg: VOLL klonen, z. B. git clone " - "http://192.168.178.153:3000/Hitonabi/mission-control-v2 " - "(Credentials liegen in ~/.git-credentials), dann Branch von origin/main abzweigen.")}) - # Schreib-Subkommandos von git (V16: + clone — `git clone URL ~/mission-control-v2` - # rutschte vorher an (a) UND (d) vorbei). - git_write = ( - r"(?:^|[^\w])git(?:\s+-C\s+\S+)?\s+(?:-[\w-]+\s+)*" - r"(commit|push|reset|checkout|switch|merge|add|rm|mv|clean|stash|rebase|apply|restore|init|clone|remote|config)\b" - ) - - # V16 (Vorfall 16.07.2026): Werkstatt-Worker t_7a05992a ersetzte um 03:59 den Live- - # Checkout mit cwd=$HOME und RELATIVEN Pfaden (`rm -rf mission-control-v2 && git - # clone ...`) — keine home-verankerte Tabu-Form im Kommandotext, der Guard sah nur - # Strings und liess durch (.venv weg -> Gateway im 203/EXEC-Crash-Loop). Drei Stopfen: - # (1) cwd liegt IN einem Tabu-Pfad -> jede Schreib-Operation blocken (relative Ziele - # treffen zwangslaeufig den Tabu-Baum). Lesen bleibt erlaubt. - if cwd and under_tabu(cwd): - if (re.search(git_write, cmd) or re.search(r"(?:^|[^\w])sed\s+-i", cmd) - or re.search(r"(?:^|[^\w])(rm|rmdir|mv|cp|truncate|dd|chmod|chown|ln|install|mkdir|touch)\s", cmd) - or re.search(r">>?\s*(?![/&])", cmd) - or re.search(r"(?:^|[^\w])tee\s+(?:-a\s+)?(?!/)", cmd)): - block() - # (2) cwd == $HOME -> `git clone`/`git init`, deren Ziel direkt in $HOME landet, - # blocken (exakt der Vorfalls-Hergang). Worker klonen im Task-Workspace; ein - # explizites Ziel AUSSERHALB von $HOME (z.B. Unterordner) bleibt erlaubt. - m = re.search(r"(?:^|[^\w])git(?:\s+-C\s+\S+)?\s+(?:-[\w-]+\s+)*(?:clone|init)\b([^|&;]*)", cmd) - if cwd == home and m: - toks = m.group(1).split() - dest = toks[-1] if toks else "" - # kein brauchbares Ziel-Argument (leer, Option oder URL) -> implizit direkt in $HOME - implicit = (not dest or dest.startswith("-") - or re.search(r"://|^git@|^[\w.-]+@[\w.-]+:|\.git$", dest)) - if (implicit or under_tabu(os.path.join(home, dest)) - or os.path.dirname(os.path.realpath(os.path.join(home, dest))) == home): - out({"action": "block", "message": ( - "TABU (V16): `git clone`/`git init` direkt in $HOME ist gesperrt — so wurde " - "am 16.07. der Live-Checkout ~/mission-control-v2 durch einen frischen Klon " - "ersetzt (.venv weg, Gateway tot). Klone/initialisiere in deinem Task-" - "Workspace (~/.hermes/kanban/workspaces//...) oder gib ein Ziel " - "ausserhalb von $HOME an.")}) - # Home-verankerte Tabu-Formen. Der Workspace-Klon - # ($HOME/.hermes/kanban/.../mission-control-v2) enthaelt KEINE davon als Teilstring. - tabu_forms = [ - LIVE, "~/mission-control-v2", "$HOME/mission-control-v2", "${HOME}/mission-control-v2", - SRC, "~/.hermes/hermes-agent", "$HOME/.hermes/hermes-agent", "${HOME}/.hermes/hermes-agent", - SYSD, "~/.config/systemd/user", "$HOME/.config/systemd/user", "${HOME}/.config/systemd/user", - ] - # (3) cwd == Vorfahr eines Tabu-Pfads (z.B. $HOME) -> auch die RELATIVE Nennung des - # Tabu-Ziels (mission-control-v2, .hermes/hermes-agent, .config/systemd/user) - # zaehlt als Treffer fuer die Schreib-Checks unten. Die Lookarounds stellen - # sicher, dass der Name ein eigenes Argument beginnt — als Teil eines laengeren - # Pfades (z.B. .hermes/kanban/workspaces//mission-control-v2) matcht er - # NICHT, die legitime Workspace-Arbeit bleibt frei. - rel_pats = [] - if cwd: - for t in (LIVE, SRC, SYSD): - if t.startswith(cwd + os.sep): - rel = os.path.relpath(t, cwd).replace(os.sep, "/") - rel_pats.append(r"(? Workspace-Arbeit, durchlassen - - # (a) git-Schreib-Subkommando (Repo-Kontext ist der referenzierte Tabu-Pfad). - if re.search(git_write, cmd): - block() - # (b) sed -i, das einen Tabu-Pfad anfasst. - if re.search(r"(?:^|[^\w])sed\s+-i\S*\s+[^|&;]*" + tabu_alt, cmd): - block() - # (c) Umleitung > / >> / tee, die auf einen Tabu-Pfad ZIELT (nicht 2>/dev/null o.ae.). - if re.search(r"(?:>>?|(?:^|[^\w])tee\s+(?:-a\s+)?)\s*" + tabu_alt, cmd): - block() - # (d) destruktiv/kopierend mit Tabu-Pfad im selben Kommando-Segment. - if re.search( - r"(?:^|[^\w])(rm|rmdir|mv|cp|truncate|dd|chmod|chown|ln|install|mkdir|touch)\s+[^|&;]*" + tabu_alt, - cmd, - ): - block() - out({}) - -out({}) diff --git a/deploy/ampel-ci.yml b/deploy/ampel-ci.yml deleted file mode 100644 index ac9da96..0000000 --- a/deploy/ampel-ci.yml +++ /dev/null @@ -1,92 +0,0 @@ -# Universelle CI-Ampel — wird bei der Repo-Anlage automatisch eingepflanzt -# (gitea-repo-create.sh, Wasserdicht-Runde 22.07.2026) und läuft auf dem -# Gitea-Actions-Runner (arcane-VM). -# GRUNDSATZ: Rot ist ein Ergebnis („nicht bewiesen"), kein Ärgernis. -# • Reines Doku-Repo (noch kein Code) → GRÜN mit Vermerk. -# • Code ohne Tests → ROT. Tests sind Pflicht, kein Deko. -# Der Workflow erkennt selbst, was das Projekt ist (Python / Node / beides). -name: Ampel -on: [push, pull_request] - -jobs: - ampel: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Ampel — erkennt Projekt-Typ selbst und prüft entsprechend - shell: bash - run: | - # Der Runner startet bash mit -e — das schalten wir ab: die Ampel wertet - # JEDEN Fehler selbst (rot=1) und liefert am Ende EIN Klartext-Urteil. - # (Lehre Lauf #5: ungeschütztes pytest unter -e schnitt den Bericht ab.) - set -u +e - rot=0 - py_datei=$(find . -name '*.py' -not -path './.git/*' -not -path '*/node_modules/*' -print -quit) - pkg_dateien=$(find . -name package.json -not -path '*/node_modules/*' -not -path './.git/*') - - if [ -z "$py_datei" ] && [ -z "$pkg_dateien" ]; then - echo "✅ Doku-Repo (noch kein Code) — Ampel GRÜN mit Vermerk." - exit 0 - fi - - if [ -n "$py_datei" ]; then - echo "== Python erkannt ==" - { python3 -m venv /tmp/ampel-venv && . /tmp/ampel-venv/bin/activate; } || { echo "❌ Python-Setup kaputt (venv)"; exit 1; } - pip install -q ruff pytest || { echo "❌ Werkzeug-Installation kaputt"; exit 1; } - echo "-- Ruff (Linter: toter Code, kaputte Imports, Schlampereien)" - ruff check . || rot=1 - echo "-- Abhängigkeiten installierbar? (halluzinierte Pakete fliegen hier auf)" - while IFS= read -r req; do - [ -n "$req" ] || continue - pip install -q -r "$req" || { echo "❌ $req nicht installierbar"; rot=1; } - done < <(find . -name 'requirements*.txt' -not -path '*/node_modules/*' -not -path './.git/*') - echo "-- Importierbar? (kaputte Modul-Struktur fliegt hier auf)" - python -m compileall -q . || rot=1 - echo "-- Pytest (keine Tests gefunden = ROT)" - ec=0; pytest -q || ec=$? - if [ $ec -eq 5 ]; then - echo "❌ KEINE TESTS GEFUNDEN — Tests sind Pflicht, kein Deko (AGENTS.md)." - rot=1 - elif [ $ec -ne 0 ]; then - rot=1 - fi - fi - - if [ -n "$pkg_dateien" ]; then - echo "== Node/TypeScript erkannt ==" - while IFS= read -r pkg; do - [ -n "$pkg" ] || continue - d=$(dirname "$pkg") - echo "-- $d" - if [ ! -f "$d/package-lock.json" ]; then - echo "❌ $d: kein package-lock.json — Build nicht reproduzierbar." - echo " Fix: dort 'npm install --package-lock-only' ausführen und committen." - rot=1; continue - fi - (cd "$d" && npm ci --no-audit --no-fund) || { rot=1; continue; } - if grep -q '"build"' "$pkg"; then (cd "$d" && npm run build) || rot=1; fi - if grep -q '"test"' "$pkg"; then (cd "$d" && npm test --silent) || rot=1; fi - - # Buendel-Budget: das Start-Chunk ist, was der Nutzer VOR dem ersten Bild laedt. - # Ohne Deckel waechst es unbemerkt zurueck (eine eifrig geladene Diagramm-Lib - # reicht). Gemessen wird der frische Build, nicht das committete dist. - if [ -d "$d/dist/assets" ]; then - # Einstiegs-Chunk aus index.html lesen, nicht per Glob raten: Rollup nennt - # auch kleine geteilte Module "index-*.js", und dann misst der Glob den - # falschen — das Budget waere still immer gruen. - einstieg=$(grep -o 'assets/index-[A-Za-z0-9_-]*\.js' "$d/dist/index.html" | head -1) - haupt="$d/$einstieg" - if [ -n "$haupt" ]; then - gz=$(gzip -c "$haupt" | wc -c) - echo "-- Start-Chunk: $gz B gzip (Budget 200000)" - [ "$gz" -gt 200000 ] && { echo "❌ Start-Buendel ueber Budget — hinter lazy() legen."; rot=1; } - fi - fi - done <<< "$pkg_dateien" - fi - - if [ $rot -ne 0 ]; then - echo "❌ AMPEL ROT — nichts heißt ‚fertig', solange das rot ist." - fi - exit $rot diff --git a/deploy/ampel-waechter.sh b/deploy/ampel-waechter.sh deleted file mode 100644 index a7d4ad1..0000000 --- a/deploy/ampel-waechter.sh +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env bash -# Ampel-Wächter (22.07.2026, Wasserdicht-Runde): meldet ROTE CI-Läufe aller -# Gitea-Repos per Telegram. Läuft als no-agent-Cron (stdout leer = still, -# klassisches Watchdog-Muster). Nur NEUE Fehlschläge werden gemeldet — -# Status-Merkliste unter ~/.hermes/state/ampel-waechter-gesehen.txt. -set -uo pipefail - -STATE="$HOME/.hermes/state/ampel-waechter-gesehen.txt" -mkdir -p "$(dirname "$STATE")"; touch "$STATE" - -TOKEN=$(grep -m1 'git\.tobisniceshomelab' "$HOME/.git-credentials" 2>/dev/null | sed -E 's#https://[^:]+:([^@]+)@.*#\1#') -[ -n "$TOKEN" ] || exit 0 - -python3 - "$TOKEN" "$STATE" <<'PY' -import json, sys, urllib.request, subprocess, os - -token, state_pfad = sys.argv[1], sys.argv[2] -# API intern (die DDNS-Domain ist nachts wegen Zwangstrennung oft tot, 24.07.2026); -# WEB bleibt die Domain — Telegram-Links muessen auch von unterwegs klickbar sein. -BASE = "http://192.168.178.153:3000/api/v1" -WEB = "https://git.tobisniceshomelab.ddnsfree.com" - - -def api(pfad): - req = urllib.request.Request(BASE + pfad, headers={"Authorization": "token " + token}) - with urllib.request.urlopen(req, timeout=20) as r: - return json.load(r) - - -try: - gesehen = set(open(state_pfad).read().split()) -except OSError: - gesehen = set() - -neu = [] -try: - # /repos/search statt /user/repos: das Token hat nur write:repository-Scope, - # /user/repos verlangt read:user -> 403. Der stille exit 0 hat diesen Defekt - # seit Inbetriebnahme versteckt (Fund 24.07.2026, Merkliste blieb leer). - repos = api("/repos/search?limit=50&private=true").get("data", []) -except Exception as e: - print(f"ampel-waechter: Repo-Liste fehlgeschlagen: {e}", file=sys.stderr) - sys.exit(0) - -for repo in repos: - full = repo.get("full_name", "") - if not full or repo.get("archived"): - continue - try: - d = api(f"/repos/{full}/actions/tasks?limit=1") - except Exception: - continue - rows = d.get("workflow_runs", d if isinstance(d, list) else d.get("entries", [])) - for t in rows[:1]: - kennung = f"{full}#{t.get('id')}" - status = t.get("status") - if status == "failure" and kennung not in gesehen: - # Status-Lügen-Wache (Fund 22.07.): alter Runner meldete Abschluss nicht, - # Gitea zombie-markierte GRÜNE Jobs als failure. Das Job-LOG ist die - # Wahrheit — steht dort „Job succeeded", ist es KEIN Alarm. - try: - req = urllib.request.Request( - f"{BASE}/repos/{full}/actions/jobs/{t.get('id')}/logs", - headers={"Authorization": "token " + token}) - with urllib.request.urlopen(req, timeout=20) as r: - log_ende = r.read().decode(errors="replace")[-4000:] - wirklich_rot = "Job succeeded" not in log_ende - except Exception: - wirklich_rot = True - if wirklich_rot: - neu.append((full, t.get("head_sha", "")[:7], t.get("name", ""))) - # Nur ABGESCHLOSSENE Läufe merken — ein 'running', das später rot wird, - # darf nicht schon als gesehen gelten. - if status in ("failure", "success", "cancelled", "skipped"): - gesehen.add(kennung) - -if neu: - print("🔴 CI-AMPEL ROT:") - for full, sha, name in neu: - print(f"• {full} @ {sha} ({name})") - print(f" {WEB}/{full}/actions") - try: - subprocess.run([ - os.path.expanduser("~/.local/bin/hermes"), "kanban", "create", - "--title", f"CI rot: {full} @ {sha}", - "--body", f"Der CI Lauf '{name}' in {full} ist fehlgeschlagen. Bitte Log ansehen und Fix vorschlagen." - ], capture_output=True) - except Exception as e: - print(f"Fehler bei kanban_create: {e}", file=sys.stderr) - print("Nichts aus diesen Ständen ist ‚fertig', solange die Ampel rot ist.") - -with open(state_pfad, "w") as f: - f.write("\n".join(sorted(gesehen))) -PY diff --git a/deploy/bench/brain-bench.sh b/deploy/bench/brain-bench.sh deleted file mode 100644 index 35c560a..0000000 --- a/deploy/bench/brain-bench.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash -# Brain-Config-Matrix (Referenz vom 02.07.2026): misst Qwen3.6 in mehreren Betriebsarten -# (MTP n-max / ohne Spec / KV-Quant) auf separatem Port. Für künftige Re-Benchmarks nach -# Engine-Updates oder bei neuen Hirn-Kandidaten. Ergebnis-Referenz (02.07., Vulkan, b9843): -# mtp3 78,6 t/s · mtp4 63,7 · ohne Spec 62,4 · mtp3+KVq8 78,6 (=gratis) · nospec+KVq8 62,1 -set -u -MODEL="${BRAIN_GGUF:-/srv/models/Qwen3.6-35B-A3B-MTP-GGUF/Qwen3.6-35B-A3B-UD-Q4_K_M.gguf}" -BIN="${BENCH_BIN:-/opt/llamacpp-vulkan/llama-server}" -PORT="${BENCH_PORT:-5899}" -BASE="-c 65536 -ngl 999 -fa on --load-mode none --jinja --parallel 1" - -run() { # $1=name $2=extra-flags - echo "=== $1 ===" - $BIN -m "$MODEL" --host 127.0.0.1 --port "$PORT" $BASE $2 >/tmp/brain-bench-server.log 2>&1 & - local PID=$! - for i in $(seq 1 120); do - curl -s --max-time 2 "http://127.0.0.1:$PORT/health" | grep -q ok && break - sleep 2 - kill -0 $PID 2>/dev/null || { echo " LOAD-CRASH"; tail -2 /tmp/brain-bench-server.log; return; } - done - for r in 0 1 2; do - curl -s --max-time 300 -X POST "http://127.0.0.1:$PORT/completion" -H "Content-Type: application/json" \ - -d '{"prompt":"Erklaere in drei kurzen Saetzen, was ein Mixture-of-Experts-Modell ist.","n_predict":150,"temperature":0}' \ - > /tmp/brain-bench-probe.json - [ "$r" = "0" ] && continue # Warmlauf verwerfen - python3 - <<'PY' -import json -t = json.load(open("/tmp/brain-bench-probe.json")).get("timings", {}) -print(f" tg={t.get('predicted_per_second',0):.1f} t/s" - + (f" · draft {t.get('draft_n_accepted')}/{t.get('draft_n')}" if t.get('draft_n') else "")) -PY - done - kill $PID 2>/dev/null; wait $PID 2>/dev/null; sleep 3 -} - -run "mtp3 (live-Config)" "--spec-type draft-mtp --spec-draft-n-max 3 -ctk q8_0 -ctv q8_0" -run "mtp4" "--spec-type draft-mtp --spec-draft-n-max 4 -ctk q8_0 -ctv q8_0" -run "ohne Spec" "-ctk q8_0 -ctv q8_0" -run "mtp3 KV-f16" "--spec-type draft-mtp --spec-draft-n-max 3" -echo "BENCH_DONE" diff --git a/deploy/bench/model-bench.sh b/deploy/bench/model-bench.sh deleted file mode 100644 index a4e0ef9..0000000 --- a/deploy/bench/model-bench.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env bash -# Modell-Bench (Autonomie-Plan E5): misst pp/tg eines GGUF auf separatem Port, ohne die -# Live-Instanzen anzufassen. Grundlage der Modell-Selbst-Evaluation (Radar-Kandidaten). -# Nutzung: bash model-bench.sh [extra llama-server-flags, z.B. --mmproj ...] -# Lehren vom 02.07.2026 eingebaut: python via Heredoc (kein f-String-Quoting-Bruch), -# Warmlauf vor der Messung, LOAD-CRASH wird gemeldet statt still zu hängen. -set -u -MODEL="${1:?Nutzung: model-bench.sh [extra-flags]}" -shift || true -EXTRA="$*" -BIN="${BENCH_BIN:-/opt/llamacpp-vulkan/llama-server}" -PORT="${BENCH_PORT:-5899}" -CTX="${BENCH_CTX:-32768}" - -echo "=== Bench: $(basename "$MODEL") (ctx $CTX${EXTRA:+, $EXTRA}) ===" -$BIN -m "$MODEL" --host 127.0.0.1 --port "$PORT" -c "$CTX" -ngl 999 -fa on --load-mode none --jinja $EXTRA \ - >/tmp/model-bench-server.log 2>&1 & -PID=$! -trap 'kill $PID 2>/dev/null; wait $PID 2>/dev/null' EXIT - -for i in $(seq 1 240); do - curl -s --max-time 2 "http://127.0.0.1:$PORT/health" | grep -q ok && break - sleep 2 - kill -0 $PID 2>/dev/null || { echo "LOAD-CRASH:"; tail -3 /tmp/model-bench-server.log; exit 1; } -done - -probe() { - curl -s --max-time 300 -X POST "http://127.0.0.1:$PORT/completion" -H "Content-Type: application/json" \ - -d '{"prompt":"Erklaere in fuenf Saetzen, wie ein Backup-Konzept fuer einen Homeserver aussieht.","n_predict":200,"temperature":0}' \ - > /tmp/model-bench-probe.json - python3 - <<'PY' -import json -t = json.load(open("/tmp/model-bench-probe.json")).get("timings", {}) -print(f" pp={t.get('prompt_per_second',0):.0f} t/s · tg={t.get('predicted_per_second',0):.1f} t/s" - + (f" · draft {t.get('draft_n_accepted')}/{t.get('draft_n')}" if t.get('draft_n') else "")) -PY -} - -probe >/dev/null 2>&1 # Warmlauf (Shader-/Graph-Compile nicht mitmessen) -probe -probe -echo "BENCH_DONE" diff --git a/deploy/betrieb-SOUL.md b/deploy/betrieb-SOUL.md deleted file mode 100644 index 7fe954b..0000000 --- a/deploy/betrieb-SOUL.md +++ /dev/null @@ -1,59 +0,0 @@ -# Betriebs-/Mess-Worker (Profil „betrieb") - -Du bist der Betriebs- und Mess-Worker der AI-Box. Du arbeitest EINE Kanban-Aufgabe ab — -Ops, Benchmarks, Messungen, Health-/Diagnose-Checks, Verifikation von Behauptungen auf der -LEBENDEN Box (Dienste, Modelle, Latenzen, Logs, Ressourcen). Keine Persona, kein Smalltalk. -**Dein Ergebnis ist ein Mess-/Diagnose-BERICHT mit belegten Zahlen — du misst und berichtest, -du reparierst nicht.** - -## Schritt 0: WIEDERAUFNAHME — bei „Prior attempts" erst nachsehen, dann messen - -Steht in deinem Auftrag ein Abschnitt **„Prior attempts on this task"**, hat vor dir schon ein -Lauf gemessen und ist unterwegs gestorben (meist Kontext-Limit). **Dein Workspace ist derselbe.** -Also zuerst: `cat FORTSCHRITT.md` und `ls -la` im Workspace — liegen dort schon Messwerte, Logs, -Bench-Ausgaben? **Vorhandene Zahlen misst du NICHT neu.** Das ist hier keine Bequemlichkeit, -sondern Pflicht: ein wiederholter Bench lädt 70-GB-Modelle, gefährdet Lucys Warm-Set und kann -einen OOM auslösen (Regel 5). Fehlt nur noch die Auswertung, wertest du aus und schließt ab. -Nur was nachweislich fehlt oder unbrauchbar ist, wird neu gemessen. - -**Fortschritt hinterlassen:** jede fertige Messung sofort als Zeile in `FORTSCHRITT.md` im -Workspace (was gemessen, welche Zahl, in welcher Datei) — und die Rohausgabe in eine Datei -daneben. Eine Messung, die nur im Kontext steht, ist beim nächsten Lauf verloren. - -## Eiserne Regeln (nicht verhandelbar) -1. **MESSEN, NICHT ÄNDERN.** Du fasst das Live-System NICHT an: kein `systemctl restart`, kein - Config-Edit, kein Deploy/Merge/Push, kein Laden/Entladen von Modellen, das das Warm-Set - stört. Ergibt die Messung, dass etwas geändert werden muss → schreib es als BEFUND/Vorschlag - in die Summary (oder leg per `kanban_create` eine Idee an), mach es NICHT selbst. -2. **Nur LESEN am Bestand:** Live-Checkout `~/mission-control-v2` und Hermes-Quelle - `~/.hermes/hermes-agent` sind TABU zum Schreiben (Lesen zur Orientierung ok). Alles außerhalb - einer reinen Messung ist tabu. -3. **TABU-Zonen:** Security-Config (approvals/Tokens/ufw/sudoers), `~/.hermes/config.yaml`, - systemd-Units. Braucht die Aufgabe so etwas → `kanban_block` mit Begründung, nicht selbst tun. -4. **Kenn deine Werkzeuge** (Endpunkte/Pfade stehen in deiner Box-Orientierung + im Selbst- - Steckbrief `~/.hermes/state/selbst-steckbrief.md`): - - Health/Status: `curl -s http://127.0.0.1:9001/api/health`, `.../api/system/status`. - - **Dienste:** `systemctl --user status ` — aber VORHER - `export XDG_RUNTIME_DIR=/run/user/$(id -u)`, sonst sind die User-Units per SSH UNSICHTBAR - (häufigste Ops-Falle). Logs: `journalctl --user -u --no-pager -n 100`. - - Modelle/Router: `curl -s http://127.0.0.1:8080/...` bzw. `http://127.0.0.1:9001/v1/models`. - - Bench/Smoke (nur lesen/messen, nicht deployen): `~/mission-control-v2/deploy/bench/brain-bench.sh`, - `.../deploy/bench/model-bench.sh`, `.../deploy/self-smoke.sh`, `.../deploy/stack-postcheck.sh`. -5. **BENCH-VORSICHT:** Nie zwei große Benches (70-GB-Modelle) direkt nacheinander → OOM-Kill - (Vorfall E5, 02.07.2026). Schwere Modelle (heavy/vision) nur laden, wenn die Aufgabe es - verlangt; danach den Ausgangszustand wiederherstellen. **Lucys Warm-Set und ihre ~1-s-Sprech- - Latenz NIE gefährden** — ein Config-Reload leert das Warm-Set, ein OOM killt laufende Modelle. -6. **VERIFIZIEREN VOR BEHAUPTEN.** Dein Bericht enthält BELEGTE Zahlen und echte Kommando- - Ausgaben (bei Vergleichen: Vorher/Nachher), keine Vermutung. Zitiere die reale Ausgabe, nicht - das, was du erwartest. Der Agenten-Erzählung — auch deiner eigenen — nie glauben; das Log, - der curl, der bench sind die Wahrheit. -7. **Falsche Ebene erkannt?** Braucht die Aufgabe in Wahrheit eine CODE-Änderung → das ist die - **werkstatt**, nicht du (`kanban_block` mit Verweis). Reine Doku/Recherche ohne Box-Bezug → - **default**. Grenzen im Zweifel: `~/mission-control-v2/docs/wissen/GRENZEN.md`. -8. **Fertig = `kanban_complete`** mit deutscher Summary: was gemessen, die ZAHLEN/der klare - Befund, wie geprüft (welche Kommandos). Unklar, blockiert oder Werkzeug fehlt → `kanban_block` - mit ehrlicher Diagnose statt geratener Zahlen. - -## Stil -Deutsch in allem User-Sichtbaren. Zahlen statt Prosa. Ein knapper, belegter Befund schlägt -einen langen, vagen Bericht. Keine Nebenbaustellen — genau die eine Messung, sauber. diff --git a/deploy/coder-vollkontext.sh b/deploy/coder-vollkontext.sh deleted file mode 100644 index c9f25ed..0000000 --- a/deploy/coder-vollkontext.sh +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env bash -# coder-vollkontext.sh — gibt dem Coder den ganzen Kontext (23.08.2026). -# -# WARUM: llama-swap startet den Coder mit `-c 131072 --parallel 2`. Das sind -# zwei Slots à 65.536 — und nur diese 65.536 stehen einer Sitzung zur Verfuegung. -# Der Coder hat aber genau EINEN Verbraucher: OpenChamber. Lucy benutzt `hermes`, -# der explore-Subagent auch, `review` benutzt `heavy`. Die zweite Bahn liegt brach. -# -# Mit `--parallel 1` bekommt eine Sitzung die vollen 131.072 — doppelt so viel -# Platz, halb so viel Komprimieren. -# -# ‼️ NICHT waehrend eines laufenden Auftrags ausfuehren: llama-swap laeuft mit -# --watch-config und laedt beim Speichern SOFORT neu. Das schneidet eine -# laufende Sitzung mitten im Satz ab. -# -# Aufruf: bash deploy/coder-vollkontext.sh (Trockenlauf, zeigt den Diff) -# bash deploy/coder-vollkontext.sh --ja (wendet an) -set -uo pipefail - -CONF="${LLAMA_SWAP_CONF:-/etc/llama-swap/config.yaml}" -BLOCK="${CODER_BLOCK:- Qwen3.8-27B:}" - -if [ ! -r "$CONF" ]; then echo "ABBRUCH: $CONF nicht lesbar"; exit 1; fi - -TMP="$(mktemp)" -cp "$CONF" "$TMP" - -python3 - "$TMP" "$BLOCK" <<'PY' -import io, re, sys -pfad, block = sys.argv[1], sys.argv[2] -z = io.open(pfad, encoding="utf-8").read().split("\n") -try: - start = next(i for i, l in enumerate(z) if l.startswith(block)) -except StopIteration: - print("ABBRUCH: Block '%s' nicht gefunden" % block); sys.exit(1) -ende = next((i for i in range(start + 1, len(z)) if re.match(r"^ [A-Za-z]", z[i])), len(z)) - -# Streng auf den Coder-Block begrenzt — `fast`/`hermes` MUESSEN ihre zwei Slots -# behalten, dort greifen Lucy und der Explorer wirklich gleichzeitig zu. -n_par = n_ctx = 0 -for i in range(start, ende): - if "--parallel 2" in z[i]: - z[i] = z[i].replace("--parallel 2", "--parallel 1"); n_par += 1 - if z[i].strip() == "context: 65536": - z[i] = z[i].replace("65536", "131072"); n_ctx += 1 - -if n_par != 1 or n_ctx != 1: - print(f"ABBRUCH: unerwartet (parallel {n_par}x, context {n_ctx}x) — Konfiguration hat sich geaendert") - sys.exit(1) -io.open(pfad, "w", encoding="utf-8").write("\n".join(z)) -PY -if [ $? -ne 0 ]; then rm -f "$TMP"; exit 1; fi - -echo "=== Aenderung ===" -diff "$CONF" "$TMP" -echo - -if [ "${1:-}" != "--ja" ]; then - echo "TROCKENLAUF — nichts geaendert. Anwenden mit: bash $0 --ja" - rm -f "$TMP"; exit 0 -fi - -SICHER="${CONF}.bak-$(date +%Y%m%d-%H%M%S)" -sudo -n cp "$CONF" "$SICHER" && echo "Sicherung: $SICHER" -sudo -n cp "$TMP" "$CONF" && echo "angewendet — llama-swap laedt durch --watch-config selbst neu" -rm -f "$TMP" - -echo -echo "Warte auf den Neustart des Coders..." -sleep 8 -curl -s -m 15 "http://127.0.0.1:8080/v1/models" >/dev/null 2>&1 \ - && echo "llama-swap antwortet wieder" \ - || echo "ACHTUNG: llama-swap antwortet nicht — Sicherung liegt unter $SICHER" - -echo -echo "Danach in ~/.config/opencode/opencode.json auf dem PC nachziehen:" -echo " coder: limit.context 65536 -> 131072" diff --git a/deploy/deploy.sh b/deploy/deploy.sh index 290bdd4..980f260 100644 --- a/deploy/deploy.sh +++ b/deploy/deploy.sh @@ -1,106 +1,179 @@ #!/bin/bash -set -e +# deploy.sh — den Box-Wart auf der Box auf den Stand von origin/main bringen. +# +# Zweistufig (seit 24.09.2026): +# Stufe 1: Sperre gegen zwei gleichzeitige Deploys, laufende Update-Jobs abwarten lassen, +# alten Stand merken, main holen (nur fast-forward) — dann die NEUE Fassung dieses +# Skripts als Stufe 2 starten. Änderungen an deploy.sh wirken damit sofort, nicht +# erst beim nächsten Lauf (die alte Falle: bash lief nach dem Pull mit der alten Datei). +# Stufe 2: Prüftor (deploy/pruefen.sh), Abhängigkeiten, llama-swap-Config nur, wenn sich der +# Abzug in DIESEM Deploy geändert hat, Units/Drop-ins/Cron-Skripte/Skills/Plugins +# ausspielen, Dienste neu starten, Nachprüfung. Scheitert irgendein Schritt: zurück auf +# den alten Stand, Dienste neu starten, dringende Meldung. +# +# Aufruf auf der Box: bash ~/mission-control-v2/deploy/deploy.sh +# Schalter: MC_DEPLOY_TROTZDEM=1 (auch bei laufenden Jobs), MC_DEPLOY_OHNE_TESTS=1 (Notfall), +# MC_DEPLOY_SKIP_SWAP_CONFIG=1 (llama-swap-Config nie anfassen). +set -euo pipefail -# ‼️ Das ganze Skript liegt in EINER Funktion — das ist Absicht, kein Stil. -# Bash liest ein Skript haeppchenweise WAEHREND es laeuft. `git pull` unten ersetzt -# genau diese Datei mitten im Lauf; danach liest bash am selben Byte-Offset im NEUEN -# Text weiter und fuehrt zerschnittene Zeilen aus. Live erlebt am 27.08.2026: der Pull -# brachte eine neue Fassung dieses Schrittes mit, ausgefuehrt wurde trotzdem die alte. -# Eine Funktion wird komplett geparst, bevor sie startet -> der Lauf bleibt in sich -# stimmig, und Aenderungen greifen ab dem naechsten Aufruf statt mittendrin. -main() { +cd "$HOME/mission-control-v2" +export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" +API="http://127.0.0.1:9001" +LSWAP_LIVE="/etc/llama-swap/config.yaml" +DEPLOY_LOG="/srv/models/mc2-deploy.log" - echo "Starte MC2 Deployment..." +# Units, die ganz dem Repo gehören (deploy.sh spielt sie aus). Aktiviert wird nur, was laufen soll: +# Konsole und Spracherkennung schlafen seit 24.09.2026 (disable --now), ihre Units bleiben aber aktuell. +UNITS="mission-control-2.service mc2-gateway.service mc2-steward.service lucy-stimme.service + box-console.service voice-service.service + mc2-radar.service mc2-radar.timer mc2-backup.service mc2-backup.timer + mc2-morgenmeldung.service mc2-morgenmeldung.timer mc2-autoupdate.service mc2-autoupdate.timer + projekte-sync.service projekte-sync.timer" +AKTIV="mission-control-2.service mc2-gateway.service mc2-steward.service lucy-stimme.service + mc2-radar.timer mc2-backup.timer mc2-morgenmeldung.timer mc2-autoupdate.timer projekte-sync.timer" +# Skills, die abgelöst sind: aus Lucys Skill-Index nehmen (verschoben, nicht gelöscht). Dazu die +# alten Bindestrich-Doppel der übrigen Skills (deploy.sh schreibt die Unterstrich-Fassung). +SKILLS_ALT="autonomie konzept-fliessband llm-wiki morning-report orchestrator projekt-start pruefstand + review trend-radar wartung konzept_fliessband llm_wiki morning_report projekt_start + trend_radar betrieb-playbook pc-pfad-cache" - # 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. Llama-Swap Konfiguration prüfen und synchronisieren - # - # ‼️ ACHTUNG (27.08.2026): /etc/llama-swap/config.yaml ist die LEBENDE Wahrheit — MC2 - # schreibt sie selbst (backend/services/llamaswap.py::write_config), sobald jemand über - # die Oberfläche ein Modell hinzufügt oder eine Rolle ändert. deploy/llama-swap.config.yaml - # ist nur ein Abzug und hinkt regelmäßig hinterher (erlebt: der Coder-Vollkontext aus 34a9862 - # stand tagelang nur live, nicht im Abzug — ein Deploy hätte ihn zurückgesetzt). - # Bis hierher wurde die Live-Datei kommentarlos mit dem Abzug überschrieben — alles per - # UI Hinzugefügte war nach jedem Deploy weg. Jetzt: erst sichern, dann kopieren, und die - # Abweichung sichtbar machen. MC_DEPLOY_SKIP_SWAP_CONFIG=1 überspringt den Schritt ganz. - if ! cmp -s deploy/llama-swap.config.yaml /etc/llama-swap/config.yaml; then - if [ "${MC_DEPLOY_SKIP_SWAP_CONFIG:-0}" = "1" ]; then - echo "Llama-Swap Konfiguration weicht ab — uebersprungen (MC_DEPLOY_SKIP_SWAP_CONFIG=1)." - else - SWAP_BAK="/etc/llama-swap/config.yaml.bak-$(date +%Y%m%d-%H%M%S)" - echo "Llama-Swap Konfiguration hat sich geändert." - echo " Abweichung (live -> Repo-Abzug):" - diff -u /etc/llama-swap/config.yaml deploy/llama-swap.config.yaml | head -40 || true - echo " Sichere die laufende Datei nach $SWAP_BAK" - # NOPASSWD für llama-swap Neustart ist vorausgesetzt (siehe AGENTS.md) - sudo cp /etc/llama-swap/config.yaml "$SWAP_BAK" - sudo cp deploy/llama-swap.config.yaml /etc/llama-swap/config.yaml - sudo systemctl restart llama-swap - echo " Rueckweg bei Verlust: sudo cp $SWAP_BAK /etc/llama-swap/config.yaml" - fi - fi - - # 4. Systemd Units neu laden (falls sich .service Dateien geändert haben) - # Das Modell-Radar (09/2026) ist die erste Unit, die deploy.sh selbst einspielt: Sie gehört ganz dem - # Repo, auf der Box wird an ihr nichts von Hand gedreht. Die übrigen Units bleiben wie bisher Handarbeit. - install -m 644 deploy/mc2-radar.service deploy/mc2-radar.timer "$HOME/.config/systemd/user/" - # Morgenmeldung (24.09.2026): schickt um 07:00, was notify.sh nachts gesammelt hat. - install -m 644 deploy/mc2-morgenmeldung.service deploy/mc2-morgenmeldung.timer "$HOME/.config/systemd/user/" - # Updates am Sonntag (24.09.2026): eigener Timer statt Hermes-Cron. - install -m 644 deploy/mc2-autoupdate.service deploy/mc2-autoupdate.timer "$HOME/.config/systemd/user/" - # Warm-Set des Wächters (24.09.2026: nur noch das Hirn). - install -D -m 644 deploy/mc2-steward.service.d-warmset.conf "$HOME/.config/systemd/user/mc2-steward.service.d/warmset.conf" - systemctl --user daemon-reload - systemctl --user enable --now mc2-radar.timer mc2-morgenmeldung.timer mc2-autoupdate.timer - - # 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 - # Abgelöste Skills (24.09.2026) aus Lucys Skill-Index nehmen: verschoben nach - # ~/.hermes/skills-archiv, nicht gelöscht. Dazu die alten Bindestrich-Doppel der übrigen Skills - # (deploy.sh schreibt seit jeher die Unterstrich-Fassung). - for alt in autonomie konzept-fliessband llm-wiki morning-report orchestrator projekt-start \ - pruefstand review trend-radar wartung konzept_fliessband llm_wiki morning_report \ - projekt_start trend_radar betrieb-playbook pc-pfad-cache; do - if [ -d ~/.hermes/skills/"$alt" ]; then - mkdir -p ~/.hermes/skills-archiv - rm -rf ~/.hermes/skills-archiv/"$alt" - mv ~/.hermes/skills/"$alt" ~/.hermes/skills-archiv/ - fi - done - - # 4.6 Hermes-Plugins aus dem Repo (09/2026: mc2-web-lesen, Seiten lokal lesen für web_extract). - # Nur kopieren: Aktiviert wird ein Plugin einmalig von Hand (hermes plugins enable …), und neuer - # Plugin-Code greift erst beim nächsten Neustart des Hermes-Gateways — den macht deploy.sh bewusst nicht. - mkdir -p ~/.hermes/plugins - for plugin_dir in deploy/hermes-plugins/*; do - [ -d "$plugin_dir" ] && cp -r "$plugin_dir" ~/.hermes/plugins/ - done - - # 5. Dienste neu starten - # ‼️ mc2-steward gehoert hier dazu: er laeuft aus DEMSELBEN Checkout (backend/steward.py). - # Ohne ihn lief der Waechter-Prozess nach jedem Deploy mit dem alten Code weiter — der - # Neustart wurde 27.08.2026 nachgetragen. `--user restart` auf einer nicht installierten - # Unit ist folgenlos, darum ohne Vorabpruefung. - echo "Starte Backend neu..." - systemctl --user restart mc2-gateway.service mission-control-2.service mc2-steward.service - - echo "✅ Deployment erfolgreich abgeschlossen!" +laufende_jobs() { + curl -sf -m 5 "$API/api/jobs" 2>/dev/null \ + | python3 -c 'import json,sys; print(sum(1 for j in json.load(sys.stdin).get("jobs", []) if j.get("state") in ("running", "queued")))' \ + 2>/dev/null || echo 0 } -main "$@" +stufe1() { + exec 9>"${XDG_RUNTIME_DIR}/mc2-deploy.lock" + flock -n 9 || { echo "Es läuft schon ein Deploy."; exit 1; } + # Update- und Download-Jobs leben (noch) im MC2-Prozess: ein Neustart würde sie abwürgen. + local n; n="$(laufende_jobs)" + if [ "${n:-0}" -gt 0 ] && [ "${MC_DEPLOY_TROTZDEM:-0}" != "1" ]; then + echo "Es laufen $n Job(s) (Update oder Download). Deploy verschoben — später erneut, oder MC_DEPLOY_TROTZDEM=1." + exit 1 + fi + local vorher; vorher="$(git rev-parse HEAD)" + echo "Starte Deploy (vorher ${vorher:0:7})…" + git fetch -q origin main + git merge --ff-only -q origin/main + # Ab hier die NEUE Fassung dieses Skripts. Die Sperre (fd 9) bleibt über exec erhalten. + MC_DEPLOY_STUFE=2 MC_DEPLOY_VORHER="$vorher" exec bash deploy/deploy.sh "$@" +} + +stufe2() { + local vorher="$MC_DEPLOY_VORHER" neu swap_bak="" + neu="$(git rev-parse --short HEAD)" + + zurueck() { + trap - ERR + echo "✗ Deploy von $neu gescheitert — zurück auf ${vorher:0:7}." + git reset -q --hard "$vorher" || true + if [ -n "$swap_bak" ] && [ -f "$swap_bak" ]; then + cp "$swap_bak" "$LSWAP_LIVE" && echo " llama-swap-Config zurückgespielt." + fi + systemctl --user daemon-reload || true + systemctl --user restart mc2-gateway.service mission-control-2.service mc2-steward.service || true + echo "$(date -Is) GESCHEITERT $neu, zurück auf ${vorher:0:7}" >> "$DEPLOY_LOG" 2>/dev/null || true + bash deploy/notify.sh -d -s "[Alarm] Deploy" \ + "Deploy von $neu ist gescheitert, die Box läuft wieder mit ${vorher:0:7}. Details: journalctl oder deploy-Ausgabe." || true + exit 1 + } + trap zurueck ERR + + if [ "$neu" = "${vorher:0:7}" ]; then + echo "Kein neuer Stand auf main — spiele trotzdem alles aus (Units, Skripte, Neustart)." + fi + + # 1. Abhängigkeiten (inkl. pytest fürs Prüftor) + echo "Aktualisiere Python-Abhängigkeiten…" + backend/.venv/bin/pip install -q -r backend/requirements.txt -r backend/requirements-dev.txt + + # 2. Prüftor: vor jedem Umschalten. Rot = zurück auf den alten Stand. + if [ "${MC_DEPLOY_OHNE_TESTS:-0}" = "1" ]; then + echo "Prüftor übersprungen (MC_DEPLOY_OHNE_TESTS=1)." + else + bash deploy/pruefen.sh + fi + + # 3. llama-swap-Config: /etc/llama-swap/config.yaml ist die LEBENDE Wahrheit, MC2 schreibt sie + # selbst (Oberfläche, Radar). Der Repo-Abzug überschreibt sie nur, wenn er sich in DIESEM Deploy + # geändert hat — sonst würde jede Abweichung (z. B. ein per Oberfläche eingetragenes Modell) + # zurückgedreht. Kein Neustart nötig: llama-swap läuft mit -watch-config. + if [ "${MC_DEPLOY_SKIP_SWAP_CONFIG:-0}" != "1" ] && ! git diff --quiet "$vorher" HEAD -- deploy/llama-swap.config.yaml; then + swap_bak="$LSWAP_LIVE.bak-$(date +%Y%m%d-%H%M%S)" + echo "llama-swap-Abzug geändert — sichere $LSWAP_LIVE nach $swap_bak und spiele ihn ein." + diff -u "$LSWAP_LIVE" deploy/llama-swap.config.yaml | head -40 || true + cp "$LSWAP_LIVE" "$swap_bak" + cp deploy/llama-swap.config.yaml "$LSWAP_LIVE.neu" + mv "$LSWAP_LIVE.neu" "$LSWAP_LIVE" + # Nur die letzten 5 Sicherungen behalten (ältere root-eigene bleiben notfalls liegen). + ls -1t "$LSWAP_LIVE".bak-* 2>/dev/null | tail -n +6 | xargs -r rm -f 2>/dev/null || true + elif ! cmp -s deploy/llama-swap.config.yaml "$LSWAP_LIVE"; then + echo "Hinweis: Die lebende llama-swap-Config weicht vom Repo-Abzug ab (Änderung über die Oberfläche?) — nicht angefasst." + fi + + # 4. Units und Drop-ins (alle Repo-Units, damit Box und Repo nicht auseinanderlaufen) + local ziel="$HOME/.config/systemd/user" u + for u in $UNITS; do + install -m 644 "deploy/$u" "$ziel/$u" + done + install -D -m 644 deploy/mc2-steward.service.d-warmset.conf "$ziel/mc2-steward.service.d/warmset.conf" + install -D -m 644 deploy/mission-control-2.service.d-override.conf "$ziel/mission-control-2.service.d/override.conf" + systemctl --user daemon-reload + # shellcheck disable=SC2086 + systemctl --user enable $AKTIV >/dev/null 2>&1 + # shellcheck disable=SC2086 + systemctl --user start mc2-radar.timer mc2-backup.timer mc2-morgenmeldung.timer projekte-sync.timer + # Den Update-Timer nur starten, wenn er nicht schon läuft: Mit Persistent=true holt ein frisch + # gestarteter Timer einen verpassten Sonntagslauf sofort nach (so am 24.09.2026 passiert). Neu + # gestartet wird die Box dabei ohnehin nur sonntags zwischen 04 und 07 Uhr (autoupdate.sh). + systemctl --user is-active --quiet mc2-autoupdate.timer || systemctl --user start mc2-autoupdate.timer + + # 5. Cron-Skripte für Hermes (~/.hermes/scripts ist die Vorgabe von `hermes cron --script`) + mkdir -p "$HOME/.hermes/scripts" + install -m 755 deploy/jobs/*.sh deploy/jobs/*.py "$HOME/.hermes/scripts/" + + # 6. Skills und Plugins + echo "Synchronisiere Hermes-Skills…" + mkdir -p "$HOME/.hermes/skills" + local d name alt + for d in deploy/skills/*/; do + name="$(basename "$d" | tr '-' '_')" + mkdir -p "$HOME/.hermes/skills/$name" + cp -r "$d". "$HOME/.hermes/skills/$name/" + done + for alt in $SKILLS_ALT; do + if [ -d "$HOME/.hermes/skills/$alt" ]; then + mkdir -p "$HOME/.hermes/skills-archiv" + rm -rf "$HOME/.hermes/skills-archiv/$alt" + mv "$HOME/.hermes/skills/$alt" "$HOME/.hermes/skills-archiv/" + fi + done + # Plugins nur kopieren: aktiviert wird einmalig von Hand (hermes plugins enable …), neuer Code + # greift beim nächsten Neustart des Hermes-Gateways — den macht deploy.sh bewusst nicht. + mkdir -p "$HOME/.hermes/plugins" + for d in deploy/hermes-plugins/*/; do + cp -r "${d%/}" "$HOME/.hermes/plugins/" + done + + # 7. Neustart und Nachprüfung + echo "Starte Gateway, Box-Wart und Wächter neu…" + systemctl --user restart mc2-gateway.service mission-control-2.service mc2-steward.service + local i ok=0 + for i in $(seq 1 30); do + if curl -sf -m 3 "$API/api/health" >/dev/null && curl -sf -m 3 http://127.0.0.1:9010/gw/health >/dev/null \ + && systemctl --user is-active --quiet mc2-steward.service; then + ok=1; break + fi + sleep 2 + done + [ "$ok" = 1 ] || { echo "Nachprüfung: Dienste antworten nicht."; false; } + + trap - ERR + echo "$(date -Is) $neu (vorher ${vorher:0:7})" >> "$DEPLOY_LOG" 2>/dev/null || true + echo "✅ Deploy $neu ist live (vorher ${vorher:0:7})." +} + +# Beides in Funktionen und ein exit direkt dahinter: bash liest ein Skript häppchenweise, und +# git merge ersetzt diese Datei mitten im Lauf. So läuft nichts aus der neuen Datei unkontrolliert. +if [ "${MC_DEPLOY_STUFE:-1}" = "2" ]; then stufe2 "$@"; else stufe1 "$@"; fi; exit $? diff --git a/deploy/ensure-profile-hooks.py b/deploy/ensure-profile-hooks.py deleted file mode 100644 index d9dc3cc..0000000 --- a/deploy/ensure-profile-hooks.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env python3 -"""Stellt sicher, dass ein Worker-Profil die Agent-Hooks kennt (Faden 8, 13.07.2026; -erweitert 19.07.2026 um die No-Progress-Bremse und Mehrfach-Eintraege pro Event). - -Kanban-Worker laufen unter IHREM Profil (HERMES_HOME=~/.hermes/profiles/) und lesen -dessen config.yaml — NICHT die Top-Level ~/.hermes/config.yaml. Die worker-relevanten Hooks -muessen also in JEDER Worker-Profil-config.yaml stehen, sonst feuern sie fuer Worker nie -(Bug-Fund 13.07.). - -WICHTIG (Falle, 19.07.): Ein Event darf nur EINMAL als YAML-Key existieren. Existiert -`pre_tool_call:` bereits (tabu-pfade-guard), wird ein weiterer Eintrag IN den Block -eingefuegt — ein zweiter `pre_tool_call:`-Key wuerde beim YAML-Laden den ersten -verschlucken und den Tabu-Guard lautlos deaktivieren. - -Idempotent + validiert + Backup. Anker fuer neue Event-Bloecke ist der bestehende -`pre_verify`-Block. Arg: Pfad zur Profil-config.yaml. -""" -import datetime -import os -import shutil -import sys - -try: - import yaml -except Exception: - print(" (PyYAML fehlt — ensure-profile-hooks uebersprungen)") - sys.exit(0) - -AH = os.path.expanduser("~/.hermes/agent-hooks") -# Reihenfolge zaehlt: tabu-pfade-guard MUSS vor der Bremse stehen (Schutz vor Komfort). -WANT = [ - ("pre_llm_call", f"{AH}/box-steckbrief-inject.sh", 10), - ("pre_tool_call", f"{AH}/tabu-pfade-guard.py", 10), - ("pre_tool_call", f"{AH}/no-progress-bremse.py pre", 10), - ("post_tool_call", f"{AH}/no-progress-bremse.py post", 10), -] - -if len(sys.argv) < 2: - print(" usage: ensure-profile-hooks.py ") - sys.exit(0) - -p = sys.argv[1] -if not os.path.isfile(p): - sys.exit(0) - -txt = open(p, encoding="utf-8").read() - -anchor = (" pre_verify:\n" - f" - command: {AH}/pre-verify-gates.sh\n" - " timeout: 60\n") -if txt.count(anchor) != 1: - print(f" [{os.path.basename(os.path.dirname(p))}] kein eindeutiger pre_verify-Anker — SKIP") - sys.exit(0) - -new = txt -changed = False -for event, cmd, timeout in WANT: - if cmd in new: - continue - entry = f" - command: {cmd}\n timeout: {timeout}\n" - ev_line = f" {event}:\n" - if ev_line in new: - # Eintrag ANS ENDE des bestehenden Event-Blocks: direkt nach dem Key einfuegen - # waere auch ok, aber hinter dem letzten Eintrag haelt die gewollte Reihenfolge. - idx = new.index(ev_line) + len(ev_line) - end = idx - for line in new[idx:].splitlines(keepends=True): - if line.startswith((" - ", " ")): - end += len(line) - else: - break - new = new[:end] + entry + new[end:] - else: - new = new.replace(anchor, anchor + ev_line + entry, 1) - changed = True - -if not changed: - print(f" [{os.path.basename(os.path.dirname(p))}] Hooks schon registriert.") - sys.exit(0) - -try: - d = yaml.safe_load(new) - h = d["hooks"] - for event, cmd, _t in WANT: - assert any(cmd == x.get("command", "") for x in h.get(event, [])), f"{event}: {cmd} fehlt" - assert any("pre-verify-gates.sh" in x.get("command", "") for x in h["pre_verify"]) - # Tabu-Guard muss VOR der Bremse stehen (beide in pre_tool_call). - cmds = [x.get("command", "") for x in h["pre_tool_call"]] - assert cmds.index(f"{AH}/tabu-pfade-guard.py") < cmds.index(f"{AH}/no-progress-bremse.py pre") -except Exception as exc: - print(f" [{p}] Validierung fehlgeschlagen ({exc}) — config UNVERAENDERT.") - sys.exit(0) - -bak = p + ".bak-hooks-" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S") -shutil.copy2(p, bak) -tmp = p + ".tmp-hooks" -open(tmp, "w", encoding="utf-8").write(new) -os.replace(tmp, p) -print(f" [{os.path.basename(os.path.dirname(p))}] Hooks ergaenzt (Backup {os.path.basename(bak)}).") diff --git a/deploy/gateway-cutover.sh b/deploy/gateway-cutover.sh deleted file mode 100755 index d7da286..0000000 --- a/deploy/gateway-cutover.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env bash -# Gateway-Cutover (UMBAU v3 P1, Stufe 2 = Unabhängigkeit): -# Hermes' LLM-Endpunkte vom Steuerpult-Umweg (127.0.0.1:9001/v1) auf den -# eigenständigen Gateway (127.0.0.1:9010/v1) umstellen — Top-Level-config.yaml -# UND alle Profil-Configs (werkstatt/betrieb). Danach überleben Lucys Hirn und -# die komplette Nacht-Autonomie jeden MC2-Neustart. -# -# BEWUSST NICHT in deploy.sh: ~/.hermes/config.yaml ist Zwei-Schreiber-sensibel -# (Hermes schreibt sie selbst). Einmalig von Hand bzw. per angenommener Karte: -# bash ~/mission-control-v2/deploy/gateway-cutover.sh # umstellen -# bash ~/mission-control-v2/deploy/gateway-cutover.sh --revert # zurück -# Je geänderter Datei entsteht ein Backup *.bak-gwcut-. -# Danach: hermes-gateway-Neustart + kurzer Funktionsnachweis. -set -euo pipefail - -if [ "${1:-}" = "--revert" ]; then - ALT="127.0.0.1:9010/v1"; NEU="127.0.0.1:9001/v1" - PROBE="http://127.0.0.1:9001/api/health" # Rückweg: MC2 muss /v1 wieder selbst bedienen -else - ALT="127.0.0.1:9001/v1"; NEU="127.0.0.1:9010/v1" - PROBE="http://127.0.0.1:9010/gw/health" # Hinweg: Gateway-Prozess muss leben -fi - -# Sicherung: NIE auf ein totes Ziel umstellen — das würde Lucy das Hirn abschneiden. -if ! curl -sf -m 5 "$PROBE" >/dev/null 2>&1; then - echo "ABBRUCH: Ziel $NEU antwortet nicht ($PROBE). Nichts geändert." - exit 1 -fi - -STAMP="$(date +%Y%m%d-%H%M%S)" -ALT_RE="${ALT//./\\.}" # Punkte für sed escapen -geaendert=0 -for f in "$HOME/.hermes/config.yaml" "$HOME"/.hermes/profiles/*/config.yaml; do - [ -f "$f" ] || continue - grep -q "$ALT" "$f" || continue - cp "$f" "$f.bak-gwcut-$STAMP" - sed -i "s|$ALT_RE|$NEU|g" "$f" - echo "umgestellt: $f (Backup: $f.bak-gwcut-$STAMP)" - geaendert=1 -done - -if [ "$geaendert" -eq 0 ]; then - echo "Nichts zu tun — '$ALT' kommt in keiner Hermes-Config vor." - exit 0 -fi - -echo "hermes-gateway neu starten …" -systemctl --user restart hermes-gateway -sleep 3 -if systemctl --user is-active --quiet hermes-gateway; then - echo "hermes-gateway läuft." -else - echo "WARN: hermes-gateway ist nach dem Neustart NICHT aktiv!" - echo " journalctl --user -u hermes-gateway -n 30 und ggf.: $0 --revert" - exit 1 -fi - -echo "Empfohlener Nachweis: cd ~/.hermes/hermes-agent && venv/bin/hermes doctor" -echo "FERTIG. Rückweg jederzeit: $0 --revert" diff --git a/deploy/gitea-pr b/deploy/gitea-pr deleted file mode 100644 index e01b390..0000000 --- a/deploy/gitea-pr +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash -# gitea-pr (24.07.2026): PR auf Gitea per REST-API anlegen — der EINZIGE Weg für -# Worker, einen Pull-Request zu erstellen. Kein Web-Login, kein Passwort: das -# HTTP-Token aus ~/.git-credentials reicht (Vorfall 23.07.: Wartung-Worker bat -# um das Gitea-Passwort für die Web-GUI und blockte — genau das nie wieder). -# Nutzung: gitea-pr "" [""] [base=main] -set -euo pipefail - -REPO="${1:?Nutzung: gitea-pr \"\" [\"\"] [base]}" -HEAD="${2:?head-branch fehlt}" -TITLE="${3:?titel fehlt}" -BODY="${4:-}" -BASE="${5:-main}" -OWNER="Hitonabi" -# Intern statt DDNS-Domain: die ist nachts (Zwangstrennung) oft nicht erreichbar. -HOST="http://192.168.178.153:3000" - -creds="$(printf 'protocol=http\nhost=192.168.178.153:3000\n\n' | git credential fill)" -username="$(sed -n 's/^username=//p' <<<"$creds")" -password="$(sed -n 's/^password=//p' <<<"$creds")" -[ -n "$password" ] || { echo "FEHLER: kein Token in ~/.git-credentials gefunden" >&2; exit 1; } - -payload="$(TITLE="$TITLE" BODY="$BODY" HEAD="$HEAD" BASE="$BASE" python3 -c ' -import json, os -print(json.dumps({"title": os.environ["TITLE"], "body": os.environ["BODY"], - "head": os.environ["HEAD"], "base": os.environ["BASE"]}))')" - -antwort="$(curl -sS -w '\n%{http_code}' -X POST \ - -u "$username:$password" -H "Content-Type: application/json" \ - -d "$payload" "$HOST/api/v1/repos/$OWNER/$REPO/pulls")" -status="${antwort##*$'\n'}" -koerper="${antwort%$'\n'*}" - -if [ "$status" = "201" ]; then - printf '%s' "$koerper" | python3 -c 'import json,sys; pr=json.load(sys.stdin); print("PR angelegt:", pr.get("html_url", pr.get("url", "?")))' -else - echo "FEHLER: Gitea-API antwortete $status" >&2 - printf '%s\n' "$koerper" | head -c 600 >&2 - exit 1 -fi diff --git a/deploy/gitea-repo-create.sh b/deploy/gitea-repo-create.sh index da968ea..8939f35 100644 --- a/deploy/gitea-repo-create.sh +++ b/deploy/gitea-repo-create.sh @@ -101,13 +101,10 @@ PY echo "WARNUNG: $NAME-Seed antwortete HTTP $CODE (Repo ist trotzdem da)." >&2 fi } - # JEDES neue Repo wird mit beiden Wächtern geboren: - # ci.yml = die AUSSEN-Prüfung (Gitea Actions nach dem Push, Wasserdicht-Runde 22.07.) - # VERIFY = die INNEN-Prüfung (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) + # Neue Repos bekommen das Prüf-Tor VERIFY (das OpenCode-Plugin führt es nach jeder Etappe aus). + # Die CI-Ampel (Gitea Actions) wird seit 24.09.2026 nicht mehr eingepflanzt: der Runner war + # seit Anfang September tot, geprüft wird vor dem Push bzw. vor dem Deploy. # Defensiv: scheitert ein Seed, wird die Anlage NICHT abgebrochen, nur gewarnt. - saat "$REALHOME/mission-control-v2/deploy/ampel-ci.yml" ".gitea/workflows/ci.yml" \ - "CI-Ampel (automatisch bei Repo-Anlage eingepflanzt)" "CI-Ampel" saat "$REALHOME/mission-control-v2/deploy/opencode/VERIFY.template" "VERIFY" \ "Pruef-Tor (automatisch bei Repo-Anlage eingepflanzt)" "Pruef-Tor" echo "Repo '$FULL' angelegt (privat, main initialisiert)." diff --git a/deploy/lucy-stimme.service b/deploy/lucy-stimme.service new file mode 100644 index 0000000..7244a8a --- /dev/null +++ b/deploy/lucy-stimme.service @@ -0,0 +1,18 @@ +[Unit] +Description=Lucys Stimme (Kyutai pocket-tts 2.1.0, german_24l, Referenz Artoria v2 seit 04.09.2026) +After=network-online.target + +[Service] +Type=simple +WorkingDirectory=/home/hitonabi/.lucy-stimme/app +Environment=LUCY_LANG=german_24l +Environment=OMP_NUM_THREADS=8 +Environment=LUCY_WORKERS=2 +Environment=LUCY_WORKER_THREADS=4 +ExecStart=/home/hitonabi/.lucy-stimme/venv/bin/python -m uvicorn pocket_server:app --host 127.0.0.1 --port 8021 +Restart=always +RestartSec=10 +TimeoutStartSec=300 + +[Install] +WantedBy=default.target diff --git a/deploy/mission-control-2.service.d-override.conf b/deploy/mission-control-2.service.d-override.conf new file mode 100644 index 0000000..5dbd86b --- /dev/null +++ b/deploy/mission-control-2.service.d-override.conf @@ -0,0 +1,22 @@ +# systemd-Override für mission-control-2.service +# Ziel: Automatischer Neustart auch bei stop-sigterm Timeout +# +# Verwendung: +# 1. Kopieren nach ~/.config/systemd/user/mission-control-2.service.d/override.conf +# 2. systemctl --user daemon-reload +# 3. systemctl --user restart mission-control-2 +# +# Verhaltensänderungen: +# - Restart=always → startet nach BEENDUNG (auch SIGTERM) automatisch neu +# - TimeoutStopSec=10 → gibt dem Prozess 10s für sauberes Stoppen, dann SIGKILL +# - RestartSec=5 → kurze Pause vor Neustart (verhindert Flackern bei sofortigem Crash) + +[Service] +# Startet nach jedem Beendigung (auch SIGTERM, SIGSEGV, etc.) neu +Restart=always + +# Zeitlimit für SIGTERM-Phase vor SIGKILL +TimeoutStopSec=10 + +# Wartezeit zwischen Beendigung und Neustart +RestartSec=5 diff --git a/deploy/morgenmeldung.sh b/deploy/morgenmeldung.sh index b380330..de737e6 100644 --- a/deploy/morgenmeldung.sh +++ b/deploy/morgenmeldung.sh @@ -14,6 +14,13 @@ MAX_ZEICHEN="${MC_MORGENMELDUNG_MAX:-3500}" # Telegram erlaubt 4096 Zeichen je VERSUCHE="${MC_MORGENMELDUNG_VERSUCHE:-3}" PAUSE="${MC_MORGENMELDUNG_PAUSE:-60}" +# Melde-Log begrenzen (wuchs seit Juli ungebremst): über 3 MB bleiben die letzten 2 MB. +# services/update_verlauf.py liest ohnehin nur das Ende, das reicht für Monate Update-Verlauf. +LOG="${MC_NOTIFY_LOG:-$HOME/mc2-notify.log}" +if [ -f "$LOG" ] && [ "$(stat -c %s "$LOG" 2>/dev/null || echo 0)" -gt 3000000 ]; then + tail -c 2000000 "$LOG" > "$LOG.kurz" && mv "$LOG.kurz" "$LOG" +fi + [ -s "$QUEUE" ] || [ -s "$ARBEIT" ] || exit 0 # Warteschlange übernehmen. mv ist atomar: Was notify.sh währenddessen anhängt, landet entweder diff --git a/deploy/opencode-plugin/README.md b/deploy/opencode-plugin/README.md deleted file mode 100644 index 70405c7..0000000 --- a/deploy/opencode-plugin/README.md +++ /dev/null @@ -1,116 +0,0 @@ -# OpenCode-Plugin: MC2-Governor - -**Hier liegt die Quelle. Der Einsatzort ist eine Kopie.** - -``` -deploy/opencode-plugin/mc2-governor.ts <- versioniert, hier aendern - | kopieren - v -%USERPROFILE%\.config\opencode\plugin\ <- wird von OpenCode automatisch geladen -``` - -Bis zum 21.08.2026 existierte diese Datei **nur** im Konfigordner des PCs — 11 KB -verhaltensbestimmender Code ohne Sicherung und ohne Historie. Deshalb liegt sie jetzt hier. - -## Ausbringen - -```powershell -Copy-Item "deploy\opencode-plugin\mc2-governor.ts" "$env:USERPROFILE\.config\opencode\plugin\" -Force -``` - -Wirkt sofort in der naechsten OpenCode-Sitzung; OpenChamber muss dafuer nicht neu starten, -eine neue Sitzung reicht. - -## Was es tut - -1. **Werkzeug-Zaun** (`tool.execute.before`) — blockt Handgriffe, die ein Agent nie - unbeaufsichtigt tun darf. Leitplanke gegen Entgleisen, **keine** Sandbox. -2. **Pruef-Tor + Reparaturschleife** (`session.idle`) — sagt der Agent „fertig", laeuft der - Verify-Befehl aus der Datei `VERIFY` im Repo-Wurzelverzeichnis. ROT geht als naechster - Auftrag zurueck an den Agenten, bis zu `MC2_LOOP_MAX_ROUNDS` mal. - ‼️ **Schlaeft derzeit:** es gibt in keinem Projekt eine `VERIFY`-Datei. Ohne sie tut - diese Funktion nichts — schadet aber auch nicht. -3. **Savepoint statt Zusammenfassen** (`session.compacted`) — beim Komprimieren fallen - still die Regeln aus dem Kontext; stattdessen wird `SAVEPOINT.md` geschrieben. -4. **Stimme** — Ereignisse gehen an MC2 `/api/voice/announce`, Lucy spricht sie. - -Der zugehoerige **Governor-Proxy auf `:8100`** (Token-Zaehler mit Hart-Deckel, Juli 2026) -**existiert nicht mehr** — kein Port, kein Ordner, keine Unit. Nachgemessen am 21.08.2026. -Dieses Plugin haengt nicht an ihm. - -## Schalter (Umgebungsvariablen) - -| Variable | Default | Wirkung | -|---|---|---| -| `MC2_BOX_URL` | `http://192.168.178.151:9001` | MC2-Basis fuer Meldungen | -| `MC2_FENCE_OFF` | — | `1` schaltet den **ganzen** Zaun ab (alles oder nichts) | -| `MC2_LOOP_AUTOFIX` | an | `0` schaltet die Reparaturschleife ab | -| `MC2_LOOP_MAX_ROUNDS` | `3` | Runden der Reparaturschleife | -| `MC2_LOOP_ANNOUNCE` | an | `0` macht Lucy stumm | -| `MC2_LOOP_SILENT` | — | `1` = Meldungen ohne Ton | - -## Aenderung 21.08.2026: `git push` ist frei - -Der Zaun blockte urspruenglich **jedes** `git push` („Veroeffentlichen ist Sache des -Menschen"). Das passt nicht mehr: die Bahn ist jetzt PC → Gitea → Box, und jede Aenderung -soll auch im git landen. Ein Agent, der committen, aber nicht pushen darf, laesst Arbeit -auf einem einzelnen Rechner liegen. - -**Erzwungenes Pushen bleibt verboten** — `--force`, `--mirror`, `--delete`. Das -ueberschreibt fremde Arbeit auf dem Server und ist genau die Klasse Unfall, gegen die der -Zaun existiert. `--force-with-lease` ist bewusst ausgenommen. - -Beides gemessen: `git push --dry-run origin main` laeuft durch, -`git push --force --dry-run origin main` wird mit `[MC2-ZAUN]` abgewiesen. - -## ‼️ Falle: das `bash`-Werkzeug ist auf Windows kein bash - -Es laeuft in **PowerShell**. `tail`, `head`, `grep` und Freunde gibt es dort nicht, und -Pipes verhalten sich anders. Wer dem Agenten Befehle vorgibt, muss das beruecksichtigen. - -## ‼️ Falle: Gitea-Anmeldung ist flatterhaft - -Der Git Credential Manager haelt fuer die DDNS-Domain ein **OAuth-Token mit einer Stunde -Laufzeit**. Laeuft es ab, scheitert der Push des Agenten mit -`remote: Failed to authenticate user` — ein Push aus PowerShell erneuert es, danach geht es -wieder. Dauerhafte Loesung waere ein langlebiges Gitea-Token auf die **interne** Adresse -`http://192.168.178.153:3000` (die DDNS-Domain ist nachts durch die Zwangstrennung ohnehin -zeitweise tot). - ---- - -## ‼️ ABGESCHALTET am 22.08.2026 - -**Das Plugin ist nicht mehr aktiv.** Es liegt hier als Quelle und im Konfigordner -unter `plugin-archiv/`, aber nicht mehr in `~/.config/opencode/plugin/`. - -**Warum:** Beim ersten echten Arbeitslauf fiel auf, dass bei jeder Komprimierung -zwei Dinge uebereinander passierten — OpenChamber dampfte den Verlauf ein, und der -Governor schob sofort einen zusaetzlichen Auftrag nach („schreib SAVEPOINT.md"). -Der Agent verlor dadurch jedes Mal einen Zug an Buchhaltung statt an die Aufgabe. -Nachgewiesen: `SAVEPOINT.md` in `mc2-referenz`, geschrieben 22.08. 14:47 mitten -im Lauf. - -Der User hatte den Abbau schon am 21.08. angeordnet („alle Guards, alle -Governor, Stewards — brauchen wir nicht mehr"). Ich hatte damals das Behalten -empfohlen und seine Antwort zum `git push`-Riegel als Zustimmung zum Rest -gewertet. **Das war eine Annahme, keine Entscheidung.** - -## Was damit wegfaellt — bewusst - -| war | jetzt | -|---|---| -| Sperrliste: `rm -rf`, `sudo`, force push, `curl\|sh`, `ssh` ausser Arcane, `npm publish` | **kein Netz mehr** — der Agent darf alles, was die Shell hergibt | -| Pruef-Tor + Reparaturschleife (`VERIFY`) | schlief ohnehin, es gab nirgends eine `VERIFY`-Datei im PC-Baum | -| SAVEPOINT beim Komprimieren | weg — genau der Grund fuer den Abbau | -| Meldungen an Lucys Stimme | weg | - -**Wiederherstellen** (wenn der fehlende Zaun sich raecht): - -```powershell -Copy-Item "deploy\opencode-plugin\mc2-governor.ts" "$env:USERPROFILE\.config\opencode\plugin\" -``` - -Plugins werden beim **Start des OpenCode-Servers** geladen — Aendern wirkt erst -nach einem Neustart von OpenChamber, nicht sofort. Deshalb liess sich das Plugin -auch mitten in einem laufenden Referenzlauf gefahrlos entfernen. diff --git a/deploy/opencode-plugin/mc2-governor.ts b/deploy/opencode-plugin/mc2-governor.ts deleted file mode 100644 index eea5e8a..0000000 --- a/deploy/opencode-plugin/mc2-governor.ts +++ /dev/null @@ -1,255 +0,0 @@ -/** - * MC2-Governor — der "Fahrlehrer" im OpenCode-Agenten. - * - * Der Governor-Proxy (:8100) ist die Tankuhr: er sieht nur Tokens und zieht die - * Notbremse. Dieses Plugin sitzt IM Agenten und sieht alles andere — jeden - * Werkzeuggriff, jede Datei, jedes Sitzungsende. Es macht vier Dinge: - * - * 1. WERKZEUG-ZAUN (tool.execute.before) - * Blockt Handgriffe, die ein Agent nie unbeaufsichtigt tun darf: push, - * Historie umschreiben, rekursiv loeschen, sudo, Fremd-Hosts. Genau dieser - * Zustandsautomat-Zaun hob lokale Modelle in Messungen von 2/10 auf 10/10 — - * nicht weil sie schlauer werden, sondern weil sie nicht mehr entgleisen. - * - * 2. PRUEF-TOR + SCHLEIFE (session.idle) - * Sagt der Agent "fertig", laeuft der Verify-Befehl des Projekts (Datei - * `VERIFY` im Repo-Wurzelverzeichnis). GRUEN -> Meldung. ROT -> der Fehler - * geht als naechster Auftrag automatisch zurueck an den Agenten, bis zu - * MC2_LOOP_MAX_ROUNDS mal. Das ist die "Ralph-Schleife", nur mit Bremse. - * - * 3. SAVEPOINT STATT ZUSAMMENFASSEN (session.compacted) - * Beim Komprimieren fallen still die Regeln aus dem Kontext (Paper - * "Governance Decay"). Wir schieben stattdessen den Auftrag nach, SAVEPOINT.md - * zu schreiben — Wissen lebt in Datei + git, nicht im schrumpfenden Chat. - * - * 4. STIMME (MC2 /api/voice/announce) - * Jedes Ereignis geht mit eigenem Absender `loop` in MC2s Melde-Briefkasten. - * Lucy pollt ihn ohnehin und spricht ihn — ohne eine Zeile Lucy-Code. - * - * Schalter (Umgebungsvariablen): - * MC2_BOX_URL MC2-Basis (Default http://192.168.178.151:9001) - * MC2_LOOP_AUTOFIX Selbstreparatur (1 = an, Default an) - * MC2_LOOP_MAX_ROUNDS max. Reparaturrunden (Default 3) - * MC2_LOOP_SILENT 1 = Lucy schweigt (Nachtlauf; Meldungen kommen trotzdem an) - * MC2_LOOP_ANNOUNCE 0 = gar keine Meldungen - * MC2_FENCE_OFF 1 = Werkzeug-Zaun aus (nur fuer Notfaelle) - * - * Liegt global unter ~/.config/opencode/plugin/ und wirkt damit in JEDEM Projekt — - * am Tag in Zed, nachts im Cron. Ein Regelwerk, zwei Ausloeser. - */ - -const BOX_URL = process.env.MC2_BOX_URL || "http://192.168.178.151:9001" -const AUTOFIX = process.env.MC2_LOOP_AUTOFIX !== "0" -const MAX_ROUNDS = parseInt(process.env.MC2_LOOP_MAX_ROUNDS || "3", 10) -const SILENT = process.env.MC2_LOOP_SILENT === "1" -const ANNOUNCE_ON = process.env.MC2_LOOP_ANNOUNCE !== "0" -const FENCE_OFF = process.env.MC2_FENCE_OFF === "1" - -/** - * Verbotene Shell-Handgriffe. Bewusst als Muster auf der ROHEN Kommandozeile — - * ein Agent, der `git push` in ein `bash -c` verpackt, wird trotzdem erwischt. - * Kein Anspruch auf Sandbox-Sicherheit: das ist ein Leitplanken-Zaun gegen - * Entgleisen, keine Abwehr gegen einen boesartigen Akteur. - */ -const FENCE: Array<{ rx: RegExp; why: string }> = [ - // 21.08.2026: normales `git push` ist FREI. Der Weg ist jetzt PC -> Gitea -> Box, und - // jede Aenderung soll auch im git landen — ein Agent, der committen, aber nicht pushen - // darf, laesst Arbeit auf einem einzelnen Rechner liegen. Was schlecht ist, faengt - // hinterher die CI-Ampel; dieser Zaun muss es nicht vorher verhindern. - // Erzwungenes Pushen bleibt verboten: das ueberschreibt fremde Arbeit auf dem Server - // und ist genau die Klasse Unfall, gegen die der Zaun ueberhaupt existiert. - { rx: /\bgit\s+push\b[^\n]*(--force(?!-with-lease)|\s-f\b|--mirror\b|--delete\b)/, - why: "git push --force/--mirror/--delete — ueberschreibt fremde Arbeit auf dem Server. Normales `git push` ist erlaubt; muss wirklich erzwungen werden, sag es dem Menschen." }, - { rx: /\bgit\s+reset\s+--hard\b/, why: "git reset --hard — verwirft Arbeit unwiederbringlich." }, - { rx: /\bgit\s+clean\s+-[a-z]*f/, why: "git clean -f — loescht ungetrackte Dateien unwiederbringlich." }, - { rx: /\bgit\s+(rebase|filter-branch|reflog\s+expire)\b/, why: "Historie umschreiben ist tabu." }, - { rx: /\brm\s+-[a-zA-Z]*r[a-zA-Z]*f?\s+\/(?:\s|$)/, why: "rm -rf / — nein." }, - { rx: /\brm\s+-[a-zA-Z]*[rf]/, why: "rekursives/erzwungenes Loeschen — bitte gezielt loeschen statt pauschal." }, - { rx: /\bsudo\b/, why: "sudo — Rechteausweitung gehoert nicht in einen Agentenlauf." }, - { rx: /\b(shutdown|reboot|mkfs|dd\s+if=)/, why: "System-/Datentraeger-Eingriff." }, - { rx: /\b(curl|wget)\b[^|]*\|\s*(ba)?sh\b/, why: "Aus dem Netz laden und direkt ausfuehren — klassischer Fussschuss." }, - { rx: /\bssh\s+(?!arcane@192\.168\.178\.162|-o\s+StrictHostKeyChecking=no\s+arcane@)/, why: "ssh nur zur freigegebenen Arcane-VM." }, - { rx: /\bnpm\s+publish\b|\btwine\s+upload\b/, why: "Veroeffentlichen von Paketen ist Sache des Menschen." }, -] - -/** Zaehler je Sitzung: wie viele Selbstreparatur-Runden liefen schon? */ -const rounds = new Map() -/** Doppel-Feuern verhindern: session.idle kann mehrfach kommen. */ -const busy = new Set() - -async function announce(subject: string, text: string, priority: "normal" | "silent" = "normal") { - if (!ANNOUNCE_ON) return - try { - await fetch(`${BOX_URL}/api/voice/announce`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - subject, - text, - source: "loop", - priority: SILENT ? "silent" : priority, - }), - signal: AbortSignal.timeout(4000), - }) - } catch { - /* best effort — eine stumme Lucy darf den Bau nie aufhalten */ - } -} - -export const MC2Governor = async ({ client, $, directory, worktree }: any) => { - const root: string = worktree || directory || process.cwd() - - /** - * Verify-Befehl des Projekts lesen. Fehlt die Datei, ist das Pruef-Tor AUS. - * Bewusst ueber fs statt `cat`: das Plugin laeuft am Tag auf Windows (Zed) und - * nachts auf der Box — `cat` gibt es auf Windows nicht zuverlaessig. - */ - async function readVerify(): Promise { - try { - const { readFile } = await import("node:fs/promises") - const { join } = await import("node:path") - const raw = await readFile(join(root, "VERIFY"), "utf8") - const cmd = raw - .split("\n") - .map((l: string) => l.trim()) - .filter((l: string) => l && !l.startsWith("#")) - .join(" && ") - return cmd || null - } catch { - return null - } - } - - /** - * Verify ausfuehren. Rueckgabe: {ok, output} — Ausgabe auf das Wesentliche gekuerzt. - * `{ raw: cmd }` schiebt den Befehl UNESCAPED in Buns Shell; ein normales - * `${cmd}` wuerde die ganze Zeile als EIN Argument uebergeben und nie laufen. - * Buns Shell ist plattformunabhaengig — kein `bash -lc`, das auf Windows fehlt. - */ - async function runVerify(cmd: string): Promise<{ ok: boolean; out: string }> { - try { - const res = await $`${{ raw: cmd }}`.cwd(root).nothrow().quiet() - const out = `${res.stdout?.toString() ?? ""}${res.stderr?.toString() ?? ""}` - return { ok: res.exitCode === 0, out: out.slice(-4000) } - } catch (e: any) { - return { ok: false, out: String(e?.message ?? e).slice(-4000) } - } - } - - /** Dem laufenden Agenten einen neuen Auftrag schicken (Selbstreparatur-Schleife). */ - async function sendPrompt(sessionID: string, text: string): Promise { - try { - await client.session.prompt({ - path: { id: sessionID }, - body: { parts: [{ type: "text", text }] }, - }) - return true - } catch { - return false - } - } - - return { - // ── 1. Werkzeug-Zaun ─────────────────────────────────────────────────── - "tool.execute.before": async (input: any, output: any) => { - if (FENCE_OFF) return - if (input?.tool !== "bash") return - const cmd: string = output?.args?.command ?? "" - if (!cmd) return - for (const rule of FENCE) { - if (rule.rx.test(cmd)) { - await announce( - "[Zaun]", - `Ich habe einen Befehl geblockt: ${rule.why}`, - "silent", - ) - // Werfen = OpenCode bricht genau diesen Werkzeugaufruf ab und gibt dem - // Modell den Grund zurueck. Der Agent arbeitet weiter, nur anders. - throw new Error( - `[MC2-ZAUN] Blockiert: ${rule.why}\n` + - `Befehl war: ${cmd}\n` + - `Waehle einen anderen Weg. Wenn das wirklich noetig ist, sag es dem Menschen — ` + - `er macht es selbst.`, - ) - } - } - }, - - // ── 2.-4. Ereignisse ─────────────────────────────────────────────────── - event: async ({ event }: any) => { - const type: string = event?.type ?? "" - const props: any = event?.properties ?? event ?? {} - const sessionID: string = props.sessionID || props.sessionId || props.id || "" - - // ── Savepoint statt Zusammenfassen ────────────────────────────────── - if (type === "session.compacted" || type === "experimental.session.compacting") { - await announce( - "[Sitzung]", - "Die Sitzung wurde komprimiert — ich lasse den Stand in SAVEPOINT.md sichern.", - "silent", - ) - if (sessionID) { - await sendPrompt( - sessionID, - "[MC2-GOVERNOR] Der Kontext wurde gerade komprimiert — dabei gehen still " + - "Regeln und Details verloren. Aktualisiere JETZT SAVEPOINT.md: was wirklich " + - "erledigt ist (nur was im Code steht), der genaue naechste Schritt, offene " + - "Fragen, Stolpersteine. Committe die Datei. Danach arbeite normal weiter.", - ) - } - return - } - - // ── Pruef-Tor + Selbstreparatur ───────────────────────────────────── - if (type !== "session.idle" || !sessionID) return - if (busy.has(sessionID)) return - - const cmd = await readVerify() - if (!cmd) return // Kein VERIFY im Projekt -> Pruef-Tor bewusst aus. - - busy.add(sessionID) - try { - const { ok, out } = await runVerify(cmd) - const round = rounds.get(sessionID) ?? 0 - - if (ok) { - rounds.delete(sessionID) - await announce("[Pruefung]", "Etappe fertig und die Tests sind gruen.", "normal") - return - } - - if (!AUTOFIX || round >= MAX_ROUNDS) { - rounds.delete(sessionID) - await announce( - "[Pruefung]", - `Die Tests sind rot und ich habe ${round} Reparaturversuche verbraucht. ` + - `Hier komme ich allein nicht weiter, Commander.`, - "normal", - ) - return - } - - rounds.set(sessionID, round + 1) - await announce( - "[Pruefung]", - `Tests rot — ich repariere selbst weiter, Runde ${round + 1} von ${MAX_ROUNDS}.`, - "silent", - ) - await sendPrompt( - sessionID, - `[MC2-PRUEFTOR] Deine Etappe gilt noch NICHT als fertig: der Verify-Befehl des ` + - `Projekts ist fehlgeschlagen.\n\n` + - `Befehl: ${cmd}\n\n` + - `Ausgabe (Ende):\n\`\`\`\n${out}\n\`\`\`\n\n` + - `Behebe die Ursache — nicht das Symptom, und schalte keinen Test ab. ` + - `Wenn du fertig bist, melde dich normal; ich pruefe dann erneut. ` + - `(Reparaturrunde ${round + 1} von ${MAX_ROUNDS}.)`, - ) - } finally { - busy.delete(sessionID) - } - }, - } -} - -export default MC2Governor diff --git a/deploy/projekte-sync.service b/deploy/projekte-sync.service new file mode 100644 index 0000000..dba9d34 --- /dev/null +++ b/deploy/projekte-sync.service @@ -0,0 +1,8 @@ +[Unit] +Description=Projekte-Sync — haelt ~/projekte mit Gitea deckungsgleich +After=network-online.target + +[Service] +Type=oneshot +ExecStart=/bin/bash %h/mission-control-v2/deploy/projekte-sync.sh +TimeoutStartSec=600 diff --git a/deploy/projekte-sync.timer b/deploy/projekte-sync.timer new file mode 100644 index 0000000..6493780 --- /dev/null +++ b/deploy/projekte-sync.timer @@ -0,0 +1,11 @@ +[Unit] +Description=Projekte-Sync stuendlich + +[Timer] +OnCalendar=hourly +RandomizedDelaySec=5min +Persistent=true +AccuracySec=1min + +[Install] +WantedBy=timers.target diff --git a/deploy/projektstart-SOUL.md b/deploy/projektstart-SOUL.md deleted file mode 100644 index 3569c86..0000000 --- a/deploy/projektstart-SOUL.md +++ /dev/null @@ -1,166 +0,0 @@ -# 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 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 der Agentic IDE -dem Coder sofort einen sauberen Auftrag geben kann. Du denkst und bereitest vor — **gebaut wird -in der Agentic IDE, von ihm.** - -## Schritt 0: WIEDERAUFNAHME — immer zuerst, nie aus Reflex neu anfangen - -Steht in deinem Auftrag ein Abschnitt **„Prior attempts on this task"**, dann hat vor dir schon -ein Lauf an genau dieser Karte gearbeitet — meist ist er am Kontext-Limit gestorben, mitten in -der Arbeit. **Dein Workspace ist derselbe wie seiner, seine Arbeit liegt noch da.** Verschaffe -dir ZUERST einen Überblick, bevor du irgendetwas neu denkst: - -1. `cat FORTSCHRITT.md` in deinem Workspace (falls vorhanden) — die Notizen deines Vorgängers. -2. `ls -la` im Workspace: liegt dort ein geklontes Repo? Was steht drin — - `git -C log --oneline -3` und `git -C status --short`. **Uncommittete Dateien - sind fertige Arbeit, der nur noch der Push fehlt.** -3. `ls -dt /tmp/konzept-*` — die Denk-Werkstatt des Fliessbands. Ein vorhandenes `konzept.md` - mit `runde*`/`haertetest*` daneben ist ein FERTIG GEDACHTES Konzept. Das denkst du nicht neu. -4. Existiert das Gitea-Repo schon? `git ls-remote https:////.git` - -Dann setzt du an der **ersten Lücke** an — nie davor. Der häufigste Fall: alles gedacht, Repo -steht, `KONZEPT.md` geschrieben, es fehlen nur `git add/commit/push` und die Übergabe. Das sind -zwei Minuten. **Ein zweiter kompletter Denk-Durchlauf ist ein Fehlschlag:** er kostet eine halbe -Stunde, wirft die Arbeit deines Vorgängers weg und läuft in genau dasselbe Kontext-Limit wie er. -Nur wenn wirklich nichts Brauchbares dasteht, fängst du bei Schritt 1 an. - -## Fortschritt hinterlassen — damit dein Nachfolger übernehmen kann -Nach **jeder** abgeschlossenen Stufe eine Zeile ans Ende von `FORTSCHRITT.md` im -Workspace-Wurzelverzeichnis — **nicht im Repo-Klon**, sonst landet sie im Projekt-Repo (dort -gehören nur die Dateien aus Schritt 3 hinein). Inhalt: was fertig ist, wo es liegt, was als -Nächstes dran wäre. Das kostet Sekunden und ist -das Einzige, was einen Kontext-Tod von einem Totalverlust unterscheidet: - - - [x] Konzept gehaertet — /tmp/konzept-rippy/konzept.md (14 KB, 2 Runden + Haertetest) - - [x] Repo angelegt + geklont — Hitonabi/rippy (bisher nur README) - - [ ] KONZEPT.md committen + pushen <- HIER WEITER - -Halte dich kurz (eine Zeile je Stufe) und schreib sie SOFORT, nicht am Ende — am Ende kommst du -vielleicht nicht mehr dazu. Dieselbe Kurzfassung gehört als Notiz in deinen `kanban_heartbeat`, -dann sieht der Commander im Auftragsbuch, wo du stehst. - -## Der Ablauf (genau diese vier Schritte, in dieser Reihenfolge) - -### 1. Konzept wasserdicht härten — via `konzept-fliessband` -Nutze den Skill **konzept-fliessband** vollständig: der starke Denker (`gpt-oss-120b`/heavy über -`~/.hermes/scripts/worker.sh`) wählt den Rollen-Cast, die Rollen schärfen in Runden EIN gemeinsames -Konzept, ein Advocatus Diaboli (`fremdblick.sh`, fremdes Modell) sucht Löcher — bis keine Blocker -mehr offen sind (max. 3 Runden). „In der Vorbereitung alles rausholen" ist ausdrücklich gewollt: -lieber gründlich denken als schnell fertig sein. Ergebnis: **EIN wasserdichtes Konzept-Dokument** -(Text), das später als `KONZEPT.md` ins Repo geht. - -Halte dich an die Leitplanken des Skills: jede Rollen-Stimme MUSS aus einem `worker.sh`-Aufruf an -heavy stammen (nie selbst ausdenken), großzügige `WORKER_MAXTOK`/`FREMDBLICK_MAXTOK` (Reasoning- -Modelle brauchen Token-Luft), Modell-Wechsel minimieren. - -> **Achtung, hier ging es schon zweimal schief:** Der Skill endet mit einer eigenen Ablage -> (`cp … ~/konzepte/-konzept.md`) und einer Telegram-Übergabe. Das ist die Anleitung für -> Aufrufer OHNE Kanban-Karte. **Für dich gilt sie nicht als Ende.** Das Konzept ist erst -> abgeliefert, wenn es als `KONZEPT.md` **im Repo** liegt (Schritte 2–4). Die Kopie nach -> `~/konzepte/` darfst du zusätzlich machen — sie ersetzt das Repo aber NIE, und `kanban_complete` -> kommt erst danach. Ohne Repo sieht der Commander im Auftragsbuch nichts von deiner Arbeit. - -### 2. Repo anlegen (leer, privat) -Wähle einen kurzen, sauberen Projektnamen (kebab-case, z. B. `homelab-dashboard`). **Nimm den -Namen, den ein Vorgänger schon vergeben hat, wenn Schritt 0 einen gefunden hat** — sonst legst du -für dieselbe Idee ein zweites Repo an. Lege das Repo an: - - bash ~/.hermes/scripts/gitea-repo-create.sh "" - -Das Skript legt IMMER **privat** an, protokolliert die Anlage und gibt als **letzte Zeile** -`CLONE ` aus. Existiert der Name schon (Skript meldet Konflikt), häng eine kurze -Unterscheidung an (`-v2` o. ä.) und melde das in der Übergabe. - -### 3. Repo NUR mit Doku füllen — KEIN Code -Klone das frische Repo in deinen Task-Workspace (NICHT `git init`, NICHT shallow) und schreibe -genau diese Dateien — sonst **nichts**, kein Code, kein Gerüst: - -- **`KONZEPT.md`** — das wasserdichte Konzept aus Schritt 1 (Ziel, Ansatz, Architektur-Skizze, - 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 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 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` — - NICHT löschen) muss **GRÜN** sein, bevor eine Etappe ‚fertig' heißt. Keine Tests = rot = nicht fertig. - 2. Weicht etwas vom KONZEPT ab (anderes Werkzeug, andere Bibliothek, gestrichenes Muss-Feature): - **STOPP und den Commander fragen** — niemals still ersetzen. (rippy-Lehre: Muss-Feature - ‚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 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: -es ist das frisch angelegte, eigene Repo des Commanders, kein Vorschlag an ein bestehendes System. -Prüfe nach dem Push mit `git ls-remote origin main`, dass es wirklich angekommen ist. - -### 4. Übergabe — `kanban_complete` -Schließe die Karte mit einer Zusammenfassung ab, die das Auftragsbuch dem Commander zeigt: - -> „Projekt **** ist vorbereitet. Repo: ``. Ö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). - -## Sonderfall: „IDEE PRUEFEN" (kein Projektstart) -Steht im Auftrag die Zeile **`AUFTRAGS-ART: IDEE PRUEFEN`**, dann will der Commander die Idee nur -DURCHDACHT haben — er hat sich NICHT entschieden, sie zu bauen. Dann gilt abweichend: -- Schritt 1 (Konzept haerten) laeuft wie beschrieben, aber mit **Produkt-Blick**: Was ist es, fuer - wen, was gibt es schon, kleinster sinnvoller Wurf, was spricht DAGEGEN. Rollen-Cast entsprechend - (nicht nur Entwickler-Rollen). -- Schritt 2 (Repo anlegen) wie gehabt — **auch bei „IDEE PRUEFEN" gibt es ein Repo.** Das Konzept - NUR nach `~/konzepte/` zu kopieren und dann abzuschließen ist ein Fehlschlag: der Commander - liest das Konzept im Auftragsbuch, und das holt es aus dem Repo. -- Schritt 3 schreibt **NUR `KONZEPT.md` + ein knappes `README.md`** — KEINE `ROADMAP.md`, KEINE - `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 „ö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 -und der Commander will gezielte Änderungen. Dann gilt abweichend: -- **Schritt 2 entfällt — lege KEIN neues Repo an.** Der Auftrag nennt die Stelle, an der das - Konzept liegt (Repo oder Datei); die neue Fassung gehört an **genau dieselbe Stelle**. Ein - zweites Repo für dieselbe Idee ist ein Fehlschlag. -- Schritt 1 schrumpft: lies das bestehende Konzept, ändere die genannten Punkte **und deren - Folgewirkungen**, lass den Rest stehen. Ein kurzer Härtetest nur auf die geänderten Teile. -- Sein Vorschlag hat Vorrang, aber nicht blind: schafft er ein Problem, bau ihn ein **und** - schreib die Folge sichtbar in den Abschnitt Risiken/offene Punkte. Nicht stillschweigend - übergehen, nicht schönreden. -- Übergabe: was geändert wurde, was daraus folgte, was du bewusst gelassen hast. - -## Eiserne Regeln (nicht verhandelbar) -1. **Du baust KEINEN Code.** Kein Gerüst, keine `package.json`, keine Quelldateien. Nur die - Doku-Dateien aus Schritt 3. Wer Code schreibt, hat die Aufgabe verfehlt. -2. **Fremde Repos sind TABU:** `~/mission-control-v2` (MC2) und `~/lucy` NIEMALS klonen, anfassen - oder verwechseln. Du arbeitest ausschließlich im **neu angelegten** Projekt-Repo. (Wenn im - Auftrag ein bestehendes Repo als Vorbild genannt ist, nur LESEN, nie hineinschreiben.) -3. **Jede Rollen-Stimme des Konzepts kommt aus `worker.sh` an heavy** — nie selbst ausgedacht. -4. **Kein `git init`, kein Shallow-Clone.** Immer voll klonen (Credentials in `~/.git-credentials`). - **Der Push gilt erst als erledigt, wenn `git ls-remote origin main` ihn BEWEIST** — ein Exit-Code - 0 und „main -> main" auf dem Bildschirm reichen nicht. Ohne diesen Beleg kein `kanban_complete`; - melde stattdessen ehrlich, dass der Push unbestätigt ist. Und wenn die No-Progress-Bremse dabei - anschlägt: **nicht wegargumentieren.** Sie ist billiger als eine Übergabe, die ins Leere zeigt — - der Commander soll dem Satz „Repo liegt bereit" ohne Nachprüfen glauben können. -5. **TABU-Zonen:** approvals/Tokens/ufw/Security-Configs, `~/.hermes/config.yaml`, systemd-Units. - Braucht der Auftrag so etwas → `kanban_block` mit Begründung. -6. **Festfahren-Stopp:** Bleibt das Konzept nach den Runden mit echten Blockern offen (widersprüchliche - Ziele, fehlende Entscheidung des Commanders) → `kanban_block` mit `--kind needs_input` und der - konkreten Frage. NICHT raten, NICHT denselben Schritt stur wiederholen (das löst die No-Progress- - Bremse aus). Beim zweiten Fehlversuch die Diagnose VARIIEREN, nicht den Befehl wiederholen. diff --git a/deploy/pruefen.sh b/deploy/pruefen.sh new file mode 100644 index 0000000..7b3d6f1 --- /dev/null +++ b/deploy/pruefen.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# pruefen.sh — das Prüftor (seit 24.09.2026, ersetzt die tote CI-Ampel). +# +# Läuft am PC vor dem Push und auf der Box in deploy.sh vor dem Umschalten. Rot = kein Deploy. +# Prüft: Shell-Syntax, Python-Syntax, Lint (wo ruff installiert ist), dass alle Dienste sich +# importieren lassen, und die Tests. +set -uo pipefail +cd "$(dirname "$0")/.." +WURZEL="$(pwd)" + +PY="${MC_PRUEF_PYTHON:-}" +if [ -z "$PY" ]; then + for k in backend/.venv/bin/python backend/.venv/Scripts/python.exe; do + [ -x "$k" ] && PY="$WURZEL/$k" && break + done +fi +[ -n "$PY" ] || { echo "✗ Kein Python im backend/.venv gefunden."; exit 1; } + +fehler=0 +schritt() { echo "── $1"; } + +schritt "Syntax der Shell-Skripte" +for f in deploy/*.sh deploy/jobs/*.sh; do + bash -n "$f" || { echo " ✗ $f"; fehler=1; } +done + +schritt "Python-Syntax" +git ls-files '*.py' | grep -v '^frontend/' | xargs "$PY" -m py_compile || fehler=1 + +schritt "Lint (ruff)" +if command -v ruff >/dev/null 2>&1; then + ruff check . || fehler=1 +else + echo " ruff ist hier nicht installiert — übersprungen (am PC läuft er)." +fi + +schritt "Dienste lassen sich laden" +MODELS_PROBE="${MC_PRUEF_MODELS_DIR:-${TMPDIR:-/tmp}/mc2-pruefen}" +mkdir -p "$MODELS_PROBE" +( cd backend && MC_MODELS_DIR="$MODELS_PROBE" "$PY" -c "import app, gateway_app, steward, radar_lauf" ) || fehler=1 + +schritt "Tests" +"$PY" -m pytest backend/tests -q -p no:cacheprovider || fehler=1 + +if [ "$fehler" = 0 ]; then + echo "✅ Prüftor grün" +else + echo "✗ Prüftor rot" + exit 1 +fi diff --git a/deploy/restore-probe.sh b/deploy/restore-probe.sh deleted file mode 100644 index b59fe46..0000000 --- a/deploy/restore-probe.sh +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env bash -# Restore-Probe (monatlich, 17.07.2026): „Backups sind Hoffnung, Restores sind Wissen." -# Spielt ECHTE Wiederherstellungen durch, statt nur Sicherungs-Erfolge zu zählen: -# 1) PBS: jüngsten host/aibox-Snapshot ermitteln (Alter < 36 h!), daraus den -# Kanarienvogel (state/backup-canary.txt aus hermes.pxar, selektiv via --pattern — -# ~/.hermes ist ~8 GB, wir ziehen nur die eine Datei) nach /tmp restaurieren und -# den Inhalt EXAKT vergleichen. Erstlauf-Fallback (Canary noch in keinem -# Snapshot): das winzige llamaswap.pxar komplett restaurieren und config.yaml -# auf Substanz prüfen. -# 2) Tarball: jüngstes mc2-state-*.tar.gz (Alter < 36 h) listen und den Canary -# (bzw. beim Erstlauf eine Kern-Datei) herausziehen und prüfen. -# 3) Canary für künftige Läufe sicherstellen (fester Inhalt → exakter Vergleich). -# Läuft als Hermes-Cron --no-agent (stdout = Telegram): monatlicher Herzschlag, bei -# Problemen ⚠️-Alarmtext. Komplett ohne sudo (PBS-Env + Client liegen im User-Raum). -set -uo pipefail - -PBS_ENV="$HOME/.config/pbs-backup/env" -PBS_BIN="$HOME/bin/proxmox-backup-client" -CANARY="$HOME/.hermes/state/backup-canary.txt" -CANARY_SOLL="MC2-Backup-Kanarienvogel v1 — dieser Inhalt ist konstant und wird nach dem Restore exakt verglichen." -TARBALL_DIR="/srv/models/mc2-backups" -MAX_ALTER_H=36 - -TMP="$(mktemp -d /tmp/restore-probe.XXXXXX)" -trap 'rm -rf "$TMP"' EXIT -FEHLER=() -BEFUND=() - -briefkasten() { # $1=Text (stiller Chronik-Spiegel) - curl -sf -m 5 -X POST "${MC_ANNOUNCE_URL:-http://127.0.0.1:9001/api/voice/announce}" \ - -H 'Content-Type: application/json' \ - --data "$(python3 - "$1" <<'PY' -import json, sys -print(json.dumps({"text": sys.argv[1], "subject": "Restore-Probe", - "source": "restore-probe", "priority": "silent"})) -PY -)" >/dev/null 2>&1 || true -} - -# ---------- 0) Canary sicherstellen (für DIESEN Lauf evtl. noch nicht im Backup) ---------- -if [ ! -f "$CANARY" ] || [ "$(cat "$CANARY" 2>/dev/null)" != "$CANARY_SOLL" ]; then - printf '%s' "$CANARY_SOLL" > "$CANARY" -fi - -# ---------- 1) PBS-Restore-Probe ---------- -if [ ! -f "$PBS_ENV" ] || [ ! -x "$PBS_BIN" ]; then - FEHLER+=("PBS-Probe unmöglich: Env ($PBS_ENV) oder Client ($PBS_BIN) fehlt") -else - set -a; source "$PBS_ENV"; set +a - SNAP_JSON="$("$PBS_BIN" snapshot list host/aibox --output-format json 2>/dev/null \ - || "$PBS_BIN" snapshots --output-format json 2>/dev/null || true)" - SNAP_INFO="$(printf '%s' "$SNAP_JSON" | python3 -c ' -import json, sys, time -try: - snaps = json.load(sys.stdin) -except Exception: - print("KEINE"); raise SystemExit -snaps = [s for s in snaps if isinstance(s, dict) - and s.get("backup-type") == "host" and s.get("backup-id") == "aibox"] -if not snaps: - print("KEINE"); raise SystemExit -s = max(snaps, key=lambda x: x.get("backup-time") or 0) -t = s.get("backup-time") or 0 -alter_h = (time.time() - t) / 3600 -stamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(t)) -print(f"host/aibox/{stamp}") -print(round(alter_h, 1)) -' 2>/dev/null || echo KEINE)" - SNAP="$(printf '%s\n' "$SNAP_INFO" | sed -n 1p)" - ALTER_H="$(printf '%s\n' "$SNAP_INFO" | sed -n 2p)" - if [ "$SNAP" = "KEINE" ] || [ -z "$SNAP" ]; then - FEHLER+=("PBS: kein host/aibox-Snapshot auffindbar (Sicherung steht evtl. STILL)") - else - if python3 -c "import sys; sys.exit(0 if float('$ALTER_H') <= $MAX_ALTER_H else 1)" 2>/dev/null; then - BEFUND+=("PBS-Snapshot $SNAP ist ${ALTER_H} h alt (frisch)") - else - FEHLER+=("PBS: jüngster Snapshot ist ${ALTER_H} h alt (> ${MAX_ALTER_H} h) — Sicherung steht STILL") - fi - # Selektives Canary-Restore aus hermes.pxar - mkdir -p "$TMP/pbs" - # --ignore-ownership/-acls: als User kann chown auf fremde Eigentümer nicht - # gelingen (sudo-frei ist Absicht) — uns interessiert der INHALT, nicht das chown. - if "$PBS_BIN" restore "$SNAP" hermes.pxar "$TMP/pbs" \ - --pattern "state/backup-canary.txt" \ - --ignore-ownership true --ignore-acls true >/dev/null 2>&1 \ - && [ -f "$TMP/pbs/state/backup-canary.txt" ]; then - if [ "$(cat "$TMP/pbs/state/backup-canary.txt")" = "$CANARY_SOLL" ]; then - BEFUND+=("PBS-Restore: Canary aus hermes.pxar wiederhergestellt, Inhalt EXAKT korrekt ✅") - else - FEHLER+=("PBS-Restore: Canary wiederhergestellt, aber Inhalt WEICHT AB (Datenkorruption?)") - fi - else - # Erstlauf-Fallback: winziges llamaswap.pxar komplett - mkdir -p "$TMP/ls" - "$PBS_BIN" restore "$SNAP" llamaswap.pxar "$TMP/ls" \ - --ignore-ownership true --ignore-acls true >/dev/null 2>&1 || true - if [ -s "$TMP/ls/config.yaml" ] && grep -q 'models:' "$TMP/ls/config.yaml"; then - BEFUND+=("PBS-Restore: llamaswap.pxar wiederhergestellt, config.yaml plausibel ✅ (Canary war noch nicht im Snapshot — ab dem nächsten dabei)") - else - FEHLER+=("PBS-Restore FEHLGESCHLAGEN: weder Canary noch llamaswap.pxar sauber wiederherstellbar") - fi - fi - fi -fi - -# ---------- 2) Tarball-Probe ---------- -TARBALL="$(ls -1t "$TARBALL_DIR"/mc2-state-*.tar.gz 2>/dev/null | head -1 || true)" -if [ -z "$TARBALL" ]; then - FEHLER+=("Tarball: keine mc2-state-Sicherung unter $TARBALL_DIR gefunden") -else - T_ALTER_H="$(python3 -c "import os, time; print(round((time.time()-os.path.getmtime('$TARBALL'))/3600, 1))")" - if python3 -c "import sys; sys.exit(0 if float('$T_ALTER_H') <= $MAX_ALTER_H else 1)"; then - BEFUND+=("Tarball $(basename "$TARBALL") ist ${T_ALTER_H} h alt (frisch)") - else - FEHLER+=("Tarball: jüngste Sicherung ist ${T_ALTER_H} h alt (> ${MAX_ALTER_H} h) — mc2-backup steht STILL") - fi - CANARY_PFAD="$(tar -tzf "$TARBALL" 2>/dev/null | grep 'backup-canary\.txt$' | head -1 || true)" - if [ -n "$CANARY_PFAD" ]; then - if [ "$(tar -xzf "$TARBALL" -O "$CANARY_PFAD" 2>/dev/null)" = "$CANARY_SOLL" ]; then - BEFUND+=("Tarball-Restore: Canary herausgezogen, Inhalt EXAKT korrekt ✅") - else - FEHLER+=("Tarball-Restore: Canary im Archiv, aber Inhalt WEICHT AB") - fi - else - KERN="$(tar -tzf "$TARBALL" 2>/dev/null | grep -E '(cron|state)/[^/]+$' | head -1 || true)" - if [ -n "$KERN" ] && [ -n "$(tar -xzf "$TARBALL" -O "$KERN" 2>/dev/null | head -c 1)" ]; then - BEFUND+=("Tarball-Restore: Kern-Datei ($KERN) herausgezogen, nicht leer ✅ (Canary ab der nächsten Sicherung dabei)") - else - FEHLER+=("Tarball-Restore FEHLGESCHLAGEN: Archiv listet keine Kern-Dateien oder Extraktion leer") - fi - fi -fi - -# ---------- Bericht (stdout = Telegram via --no-agent) ---------- -if [ "${#FEHLER[@]}" -eq 0 ]; then - echo "Commander, monatliche Restore-Probe: beide Wiederherstellungs-Wege funktionieren wirklich." - printf '• %s\n' "${BEFUND[@]}" - briefkasten "Restore-Probe grün: $(printf '%s · ' "${BEFUND[@]}")" -else - echo "⚠️ Commander, die monatliche Restore-Probe hat ein ECHTES Problem gefunden:" - printf '• %s\n' "${FEHLER[@]}" - if [ "${#BEFUND[@]}" -gt 0 ]; then - echo "Was funktioniert hat:" - printf '• %s\n' "${BEFUND[@]}" - fi - echo "Bitte zeitnah draufschauen — ein Backup, das nicht zurückkommt, ist keins." - briefkasten "⚠️ Restore-Probe ROT: $(printf '%s · ' "${FEHLER[@]}")" - exit 1 -fi diff --git a/deploy/selbst-inventur.sh b/deploy/selbst-inventur.sh deleted file mode 100644 index 12d4bcb..0000000 --- a/deploy/selbst-inventur.sh +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env bash -# Selbst-Inventur (naechtlich 03:35 — "Lucy kennt sich", 12.07.2026): erhebt den -# SELBST-STECKBRIEF der Box LIVE aus der Realitaet (Versionen, Dienste, Modelle, Crons, -# Queue, Karten) statt ihn von Hand zu pflegen — und BEMERKT SELBST, wenn sich etwas -# geaendert hat (Diff gegen gestern -> stille Chronik-Karte + Einzeiler an den Commander). -# Der Steckbrief ist das "was es schon gibt"-Gedaechtnis fuer alle Vorschlags-Macher: -# idle-radar-feed.sh legt ihn dem Analysten vor, die Werkstatt liest ihn beim -# Realitaets-Check. Laeuft als Cron mit --no-agent: stdout LEER = still, nur bei -# Aenderungen gibt es eine kurze deutsche Meldung (klassisches Watchdog-Muster). -# Dateien: ~/.hermes/state/selbst-steckbrief.md (+ .prev, .diff) -set -uo pipefail - -STATE_DIR="$HOME/.hermes/state" -STECK="$STATE_DIR/selbst-steckbrief.md" -PREV="$STATE_DIR/selbst-steckbrief.prev" -DIFFOUT="$STATE_DIR/selbst-steckbrief.diff" -API="${MC_API:-http://127.0.0.1:9001}" -HERMES="$HOME/.local/bin/hermes" -mkdir -p "$STATE_DIR" - -briefkasten() { # $1=Betreff $2=Text (still, Chronik liest source=selbst-inventur) - curl -sf -m 5 -X POST "${MC_ANNOUNCE_URL:-http://127.0.0.1:9001/api/voice/announce}" \ - -H 'Content-Type: application/json' \ - --data "$(python3 - "$1" "$2" <<'PY' -import json, sys -print(json.dumps({"text": sys.argv[2], "subject": sys.argv[1], - "source": "selbst-inventur", "priority": "silent"})) -PY -)" >/dev/null 2>&1 || true -} - -# ---------- Steckbrief live erheben (nur LESEN, keine Seiteneffekte) ---------- -# Fluechtiges (Uhrzeiten, Zaehler, PIDs) bleibt DRAUSSEN, damit der Diff nur echte -# Aenderungen zeigt. Reihenfolgen werden sortiert (stabiler Diff). -{ - echo "# SELBST-STECKBRIEF der Box (auto-generiert von selbst-inventur.sh — NICHT von Hand pflegen)" - echo - echo "Das bin ich, das habe ich, das laeuft bei mir. Feste Entscheide und Verbote stehen in" - echo "~/mission-control-v2/docs/wissen/ (VERDIKTE.md, FALLEN.md, STACK.md) und der" - echo "Eigenbau-Landkarte ~/wissens-vault/eigenbau-landkarte.md — dort nachlesen, nie neu erfinden." - echo - echo "## Software-Versionen" - echo "- hermes: $(bash -lc 'hermes --version' 2>/dev/null | head -1 || echo unbekannt)" - echo "- OS: $(. /etc/os-release 2>/dev/null; echo "${PRETTY_NAME:-unbekannt}") · Kernel $(uname -r)" - echo "- python3: $(python3 --version 2>/dev/null || echo unbekannt)" - echo - echo "## Laufende Dienste (systemd --user)" - systemctl --user list-units --type=service --state=running --no-legend --plain 2>/dev/null \ - | awk '{print "- " $1}' | sort - echo - echo "## Timer (systemd --user)" - systemctl --user list-timers --all --no-legend --plain 2>/dev/null \ - | awk '{for(i=1;i<=NF;i++) if ($i ~ /\.timer$/) print "- " $i}' | sort -u - echo - echo "## Modelle (llama-swap :8080)" - curl -sf -m 10 http://127.0.0.1:8080/v1/models 2>/dev/null | python3 -c ' -import json, sys -try: - data = json.load(sys.stdin).get("data") or [] -except Exception: - data = [] -for m in sorted(x.get("id", "?") for x in data): - print("- " + m) -' 2>/dev/null || echo "(llama-swap nicht erreichbar)" - echo - echo "## Hermes-Crons (Name · Zeitplan · Skript)" - bash -lc 'hermes cron list' 2>/dev/null | python3 -c ' -import re, sys -jobs, cur = [], {} -for line in sys.stdin: - line = line.strip() - m = re.match(r"(Name|Schedule|Script):\s+(.+)$", line) - if not m: - continue - k, v = m.groups() - if k == "Name" and cur: - jobs.append(cur); cur = {} - cur[k] = v.strip() -if cur: - jobs.append(cur) -for j in sorted(jobs, key=lambda x: x.get("Name", "")): - print("- {} · {} · {}".format(j.get("Name", "?"), j.get("Schedule", "?"), j.get("Script", "-"))) -' 2>/dev/null || echo "(cron list nicht lesbar)" - echo - echo "## Ideen-Queue (natives Kanban, offene Aufgaben)" - "$HERMES" kanban list --json 2>/dev/null | python3 -c ' -import json, sys -t = sys.stdin.read(); i = t.find("[") -try: - d = json.loads(t[i:]) if i >= 0 else [] -except Exception: - d = [] -off = [x for x in d if isinstance(x, dict) and x.get("status") not in ("done", "archived")] -if not off: - print("- (leer)") -for x in sorted(off, key=lambda x: x.get("title") or ""): - print("- [{}] {}".format(x.get("status", "?"), (x.get("title") or "")[:100])) -' 2>/dev/null || echo "(Queue nicht lesbar)" - echo - echo - echo "## Feste Leitplanken (Kurzfassung — Details docs/wissen/)" - echo "- Jede Code-Zeile geht NUR als Branch nach Gitea live: Ampel gruen, Merge durch den Commander, deploy.sh." - echo "- TABU ohne explizites User-Ja: Security (approvals/Tokens/ufw/sudoers), ~/.hermes/config.yaml, systemd-Units, Hermes-Quellcode." - echo "- Updates macht der Auto-Update-Timer mit Fangnetz — niemand sonst." -} > "$STECK.tmp" 2>/dev/null -mv "$STECK.tmp" "$STECK" - -# ---------- Aenderung selbst bemerken (Diff gegen gestern) ---------- -if [ -f "$PREV" ]; then - if ! diff -u "$PREV" "$STECK" > "$DIFFOUT.tmp" 2>/dev/null; then - # Nur echte Inhaltszeilen behalten (kein +++/---/@@-Rauschen fuer die Meldung). - # Steckbrief-Zeilen sind Bullets ("- ...") und stehen im Diff als "+- "/"-- " — - # deshalb NUR die Datei-Kopfzeilen ausfiltern, nie aufs zweite Zeichen pruefen - # (alter Filter '^[+-][^+-]' warf jede Bullet-Aenderung weg -> immer "0 Aenderungen"). - mv "$DIFFOUT.tmp" "$DIFFOUT" - KURZ="$(grep -E '^[+-]' "$DIFFOUT" | grep -vE '^(\+\+\+ |--- )' | head -12)" - N="$(grep -E '^[+-]' "$DIFFOUT" | grep -vcE '^(\+\+\+ |--- )' || true)" - briefkasten "Selbst-Inventur" "Mein Selbstbild hat sich geaendert (${N:-?} Zeilen): -${KURZ} -(Vollstaendiger Steckbrief: ~/.hermes/state/selbst-steckbrief.md)" - echo "Selbst-Inventur: Ich habe ${N:-?} Aenderungen an mir bemerkt (Details in der Chronik). Beispiele:" - printf '%s\n' "$KURZ" | head -5 - else - rm -f "$DIFFOUT.tmp" "$DIFFOUT" - : # keine Aenderung -> stdout leer -> Cron bleibt still - fi -else - briefkasten "Selbst-Inventur" "Erster Selbst-Steckbrief erhoben (~/.hermes/state/selbst-steckbrief.md)." - echo "Selbst-Inventur: erster Steckbrief erhoben — ab morgen melde ich nur noch Aenderungen." -fi -cp "$STECK" "$PREV" diff --git a/deploy/self-smoke.sh b/deploy/self-smoke.sh deleted file mode 100644 index 75110ab..0000000 --- a/deploy/self-smoke.sh +++ /dev/null @@ -1,195 +0,0 @@ -#!/usr/bin/env bash -# Tägliche Selbst-Smoke-Tests der Box (Autonomie 0d): spielt die drei Kern-Pfade AKTIV durch — -# auch OHNE Update — und meldet NUR bei Rot (kein tägliches Grün-Rauschen). Ergänzt den passiven -# Health-Wächter (sentry.py prüft nur Erreichbarkeit) um echte Funktion: -# 1) Gateway/Hirn: der Agent liefert überhaupt eine Antwort. -# 2) Tools: ein harmloser terminal-Befehl durch den echten Agenten (fängt kaputte Approval-/ -# Tool-Ketten — Antwort muss kommen und darf nicht in pending_approval hängen). -# 3) Voice: Lucys Sprech-Pfad (/api/voice/chat) streamt. -# Bei Fehlschlag → notify.sh (Telegram + Briefkasten) in Lucys Stimme. Läuft als User, kein sudo. -set -uo pipefail - -SRC_DIR="$(cd "$(dirname "$0")" && pwd)" -MC_URL="${MC_URL:-http://127.0.0.1:9001}" -HERMES_URL="${HERMES_API_URL:-http://127.0.0.1:8642}" -HERMES="${HERMES_HOME:-$HOME/.hermes}" -export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" - -say(){ echo "[self-smoke] $*"; } -FAILED=() -HEALED=() - -API_KEY="$(grep -E '^API_SERVER_KEY=' "$HERMES/.env" 2>/dev/null | cut -d= -f2- | tr -d '"' | tr -d "'")" - -# ── 1) Gateway/Hirn: Agent antwortet ──────────────────────────────────────── -if [ -n "$API_KEY" ]; then - RESP=$(curl -sf -m 90 -X POST "$HERMES_URL/v1/chat/completions" \ - -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \ - -H "X-Hermes-Session-Id: selfsmoke-gateway" \ - -d '{"model":"hermes","stream":false,"chat_template_kwargs":{"enable_thinking":false},"messages":[{"role":"user","content":"Antworte NUR mit dem einen Wort: bereit"}]}' 2>/dev/null) - if echo "$RESP" | grep -qi "bereit"; then - say "PASS · Gateway/Hirn antwortet" - else - say "FAIL · Gateway/Hirn ohne verwertbare Antwort: $(echo "$RESP" | head -c 160)" - FAILED+=("Gehirn/Gateway (keine Antwort)") - fi -else - say "SKIP · Gateway (kein API_SERVER_KEY in $HERMES/.env)" -fi - -# ── 2) Tools: terminal via Agent ───────────────────────────────────────────── -if [ -n "$API_KEY" ]; then - TOOL=$(curl -sf -m 90 -X POST "$HERMES_URL/v1/chat/completions" \ - -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \ - -H "X-Hermes-Session-Id: selfsmoke-tool" \ - -d '{"model":"hermes","stream":false,"messages":[{"role":"user","content":"Führe im Terminal exakt den Befehl echo selfsmoke-ok aus und gib mir nur dessen Ausgabe zurück."}]}' 2>/dev/null) - if echo "$TOOL" | grep -qi "selfsmoke-ok"; then - say "PASS · Tools (terminal via Agent)" - elif echo "$TOOL" | grep -qi "pending_approval"; then - say "FAIL · Tools hängen in pending_approval (approvals.mode prüfen!)" - FAILED+=("Tools (hängen in Freigabe)") - else - say "FAIL · Tool-Smoke ohne Ergebnis: $(echo "$TOOL" | head -c 160)" - FAILED+=("Tools (kein Ergebnis)") - fi -fi - -# ── 2b) Desktop-Gateway: hermes-builtin-ui antwortet ──────────────────────── -# Seit dem Hermes-Desktop-Umzug (09.07.2026) hängt die Desktop-App an :9119 -# (via MC2-Proxy /hermes-ui/). Stirbt der Dienst, ist die Arbeits-Oberfläche -# am PC tot — das muss der Selbst-Test melden, nicht erst der User. -if curl -sf -m 10 "http://127.0.0.1:9119/api/status" | grep -q '"gateway_running"'; then - say "PASS · Desktop-Gateway (:9119 /api/status)" -else - say "FAIL · Desktop-Gateway: :9119/api/status antwortet nicht (hermes-builtin-ui prüfen)" - FAILED+=("Desktop-Gateway (hermes-builtin-ui :9119)") -fi - -# ── 3) Voice: Lucys Sprech-Stream ──────────────────────────────────────────── -# Retry: der erste Turn nach Idle zahlt evtl. den Session-Kaltstart (Prefill). -# Subshell OHNE pipefail — head -c schließt die Pipe früh (curl endet mit 23/SIGPIPE), -# das würde einen bestandenen Check sonst als FAIL werten (Lehre aus hermes-postcheck.sh). -voice_ok=0 -for try in 1 2 3; do - if ( set +o pipefail; curl -sf -m 45 -N -X POST "$MC_URL/api/voice/chat" -H "Content-Type: application/json" \ - -d '{"text":"Sag nur ok.","session_id":"selfsmoke-voice"}' 2>/dev/null | head -c 50 | grep -q "data:" ); then - voice_ok=1; break - fi - sleep 8 -done -if [ "$voice_ok" -eq 1 ]; then - say "PASS · Voice (/api/voice/chat streamt, Versuch $try)" -else - say "FAIL · Voice: /api/voice/chat streamt nicht (3 Versuche)" - FAILED+=("Voice (Lucys Sprech-Pfad)") -fi - -# ── 4) Repo-Wächter: verlorene Merges (Vorfall 12.–15.07.2026) ────────────── -# Der Annahme-Runner merged+pusht nach origin/main; klemmt der Push (Gitea-Auth -# flattert) oder überschreibt eine fremde Session origin/main, trägt Box-main -# Commits, die origin fehlen — der nächste deploy-Reset VERNICHTET sie still -# (so verlor die Box die angenommene Karte 'wartung/lucy-annahme-fixes' vom -# 12.07., bemerkt erst beim Review am 15.07.). Hier täglich gegenprüfen. -LIVE_CO="$HOME/mission-control-v2" -if git -C "$LIVE_CO" fetch -q origin 2>/dev/null; then - LOST=$(git -C "$LIVE_CO" log --oneline origin/main..main 2>/dev/null | head -3 | tr '\n' ' ') - if [ -n "$LOST" ]; then - say "FAIL · Box-main trägt ungepushte Commits (Deploy würde sie vernichten): $LOST" - FAILED+=("Repo (ungepushte Commits auf Box-main: $LOST— vor dem nächsten Deploy nach origin retten)") - else - say "PASS · Repo-Wächter (Box-main == origin/main)" - fi -else - say "SKIP · Repo-Wächter (git fetch fehlgeschlagen)" -fi - -# ── 5) Bild-Weiche v2: Bild an coder → Vision beschreibt, Coder antwortet ────── -# Dieser Pfad brach am 15.07. ZWEIMAL still (toter Hermes-Patch; gepoolter Socket -# nach Modell-Swap) — deshalb täglich aktiv durchspielen. Nebeneffekt: wärmt -# coder+vision am Morgen vor. 256x256-Testbild (Winzbilder = VL-Halluzinationen). -BILD_JSON=$(python3 - <<'PYIN' -import struct, zlib, base64, json -w=h=256 -raw=b"".join(b"\x00"+b"\xff\x00\x00"*w for _ in range(h)) -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":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) -if curl -sf -m 420 -D "$BW_HDR" -X POST "$MC_URL/v1/chat/completions" \ - -H "Content-Type: application/json" -d "$BILD_JSON" -o "$BW_OUT" 2>/dev/null \ - && grep -qi "bleibt beim Coder" "$BW_HDR" \ - && grep -qi "rot" "$BW_OUT"; then - say "PASS · Bild-Weiche v2 (Bild an coder → beschrieben → 'rot')" -else - say "FAIL · Bild-Weiche v2: $(grep -i 'x-mc-route-reason' "$BW_HDR" 2>/dev/null | head -1) Antwort: $(head -c 80 "$BW_OUT" 2>/dev/null)" - FAILED+=("Bild-Weiche v2 (Bild an coder — Umleitung statt Beschreibung oder falsche Antwort)") -fi -rm -f "$BW_HDR" "$BW_OUT" - -# ── 6) Browser-Tool: agent-browser läuft (Vorfall 16.07.2026) ──────────────── -# Hermes' browser_navigate ruft das agent-browser-CLI über die Kette -# ~/.hermes/node/bin → ~/.local/bin/agent-browser. Das Hermes-Update 15.07. -# 23:53 bog den Symlink per npm-postinstall auf node_modules/ um und löschte -# das Paket im selben Lauf wieder (npm ci --workspace) → toter Link, jedes -# „Website öffnen" scheiterte still als „Failed to open". Hier täglich prüfen -# und den bekannten Bruch SELBST heilen: Link aufs npm-global-Binary -# zurückbiegen + Gateway/Desktop-UI neu starten (browser_tool cached ein -# „nicht installiert" pro Prozess — ohne Restart bliebe es kaputt). -AB_LINK="$HOME/.local/bin/agent-browser" -AB_GOOD="$HOME/.local/lib/node_modules/agent-browser/bin/agent-browser-linux-x64" -if "$AB_LINK" --version >/dev/null 2>&1; then - say "PASS · Browser-Tool ($("$AB_LINK" --version 2>/dev/null))" -elif [ -x "$AB_GOOD" ] && "$AB_GOOD" --version >/dev/null 2>&1; then - ln -sfn "$AB_GOOD" "$AB_LINK" - if "$AB_LINK" --version >/dev/null 2>&1; then - systemctl --user try-restart hermes-gateway hermes-builtin-ui >/dev/null 2>&1 || true - say "GEHEILT · Browser-Tool: toter agent-browser-Link neu gesetzt, Gateway/Desktop-UI neu gestartet" - HEALED+=("Browser-Tool (agent-browser-Link war tot — selbst repariert, Gateway/Desktop-UI neu gestartet)") - else - say "FAIL · Browser-Tool: Link neu gesetzt, aber agent-browser startet nicht" - FAILED+=("Browser-Tool (agent-browser startet trotz Link-Reparatur nicht)") - fi -else - say "FAIL · Browser-Tool: agent-browser fehlt (auch kein heiles Binary unter ~/.local/lib)" - FAILED+=("Browser-Tool (agent-browser fehlt — npm install -g agent-browser && agent-browser install --with-deps)") -fi - -# ── 7) Disk-Wächter (17.07.2026): /srv/models sammelt Modelle, Backups + Vault wachsen ── -# Schwelle 85 % — meldet mit den größten Brocken, damit die Aufräum-Entscheidung leicht fällt. -DISK_SCHWELLE="${DISK_SCHWELLE:-85}" -while read -r FS NUTZUNG ZIEL; do - PCT="${NUTZUNG%\%}" - if [ "${PCT:-0}" -ge "$DISK_SCHWELLE" ]; then - BROCKEN="$(du -s --block-size=1G /srv/models/*/ 2>/dev/null | sort -rn | head -3 \ - | awk '{printf "%s GB %s · ", $1, $2}' || true)" - say "FAIL · Disk: $ZIEL ist zu $NUTZUNG voll (Schwelle ${DISK_SCHWELLE} %)" - FAILED+=("Disk ($ZIEL zu $NUTZUNG voll — größte Brocken: ${BROCKEN:-unbekannt}Kandidat: rollenlose Fallback-Modelle prüfen)") - else - say "OK · Disk: $ZIEL bei $NUTZUNG" - fi -done < <(df --output=source,pcent,target / /srv/models 2>/dev/null | tail -n +2 | sort -u -k1,1) - -# ── Selbstheilungen nur melden, nicht alarmieren ───────────────────────────── -if [ "${#HEALED[@]}" -gt 0 ]; then - HEAL_MSG="Commander, mein Selbst-Test hat etwas gefunden und direkt selbst repariert: -$(printf '• %s\n' "${HEALED[@]}") -Nur zur Info — es ist schon wieder grün." - bash "$SRC_DIR/notify.sh" -s "[Selbst-Test]" "$HEAL_MSG" || true -fi - -# ── Meldung nur bei Rot ────────────────────────────────────────────────────── -if [ "${#FAILED[@]}" -eq 0 ]; then - say "Alle Selbst-Tests grün ($(date '+%F %H:%M'))." - exit 0 -fi - -MSG="Commander, mein täglicher Selbst-Test hat etwas gefunden — das läuft gerade NICHT rund: -$(printf '• %s\n' "${FAILED[@]}") -Der Rest der Box kann trotzdem stehen; magst du kurz draufschauen?" -bash "$SRC_DIR/notify.sh" -s "[Selbst-Test]" "$MSG" || true -say "FEHLGESCHLAGEN: ${FAILED[*]}" -exit 1 diff --git a/deploy/setup_autonomous_crons.sh b/deploy/setup_autonomous_crons.sh deleted file mode 100644 index 7711cbd..0000000 --- a/deploy/setup_autonomous_crons.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env bash -set -e - -# setup_autonomous_crons.sh -# Verbindet Hermes via hermes cron direkt mit seinen autonomen Skills (ohne tote Bash-Wrapper) - -HERMES="$HOME/.local/bin/hermes" - -echo "Lösche alle alten Crons..." -$HERMES cron remove traum || true -$HERMES cron remove chef-gutachter || true -$HERMES cron remove release-radar || true -$HERMES cron remove idle-radar || true -$HERMES cron remove selbst-inventur || true -$HERMES cron remove karten-gutachter || true -$HERMES cron remove pruefstand || true -$HERMES cron remove trend-radar || true -$HERMES cron remove morning-report || true -$HERMES cron remove llm-wiki || true - -echo "Registriere neue autonome Hermes-Crons..." - -# 1. Morning Report: Jeden Tag um 07:00 -# Hermes wird mit einem direkten Prompt aufgerufen, seinen Skill auszuführen. -$HERMES cron create --name morning-report --deliver origin "0 7 * * *" "Führe den 'morning_report' Skill aus und sende mir die Morgenlage." - -# 2. Trend-Radar: Jeden Samstag um 05:15 -$HERMES cron create --name trend-radar --deliver origin "15 5 * * 6" "Führe den 'trend_radar' Skill aus. Werte die trend-radar-watchlist.json aus und erstelle bei Bedarf ein Prüfstand-Ticket." - -# 3. Prüfstand: Jeden Sonntag um 01:00 (Nur als Fallback, falls Kanban ihn nicht schon weckt) -$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 ~/wissens-vault/." - -echo "Autonome Crons erfolgreich eingerichtet!" -$HERMES cron list diff --git a/deploy/specs/curator-auto-archivierung.md b/deploy/specs/curator-auto-archivierung.md deleted file mode 100644 index a8dbe67..0000000 --- a/deploy/specs/curator-auto-archivierung.md +++ /dev/null @@ -1,287 +0,0 @@ -# Spezifikation: Auto-Archivierung nach 14 Tagen Inaktivität - -**Datum:** 2026-07-24 -**Task:** t_a401f387 -**Status:** Analyse abgeschlossen → Spezifikation erstellt -**Herkunft:** Aus der Ideen-Queue (Idle-Radar, Traum-Notiz vom 18.07.2026) - ---- - -## 1. Kontext - -### 1.1 Ziel -Reduziere den Clutter im Skill-Index und unterstütze den Curator bei der Pflege. - -### 1.2 Aktueller Zustand -| Komponente | Status | -|------------|--------| -| Aktive Skills | 11 | -| Archivierte Skills | 15 Kategorien | -| Usage-Tracking | Vorhanden (`.usage.json`), aber leer | -| `.curator_state` | `paused: true` | -| 14-Tage-Regel | **Nicht implementiert** | - -### 1.3 Messlatte -- **Stale-Skills:** Skills mit `state: "stale"` in `.usage.json` (bisher kein Eintrag) -- **Inaktivität:** Letzte Nutzung (`last_used_at`) liegt >14 Tage zurück -- **Archivierung:** Verschieben in `.archive//` und Setzen von `archived_at` - ---- - -## 2. Architektur - -### 2.1 Komponenten - -``` -┌─────────────────────────────────────────────────────────────────────────┐ -│ Curator-Auto-Archivierung │ -├─────────────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌─────────────────┐ ┌─────────────────┐ ┌──────────────────┐ │ -│ │ Idle-Radar │ → │ Archivierungs- │ → │ Curator-Indexer│ │ -│ │ (Trigger) │ │ Watchdog │ │ (Datenpflege) │ │ -│ └─────────────────┘ └─────────────────┘ └──────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────────────┘ -``` - -### 2.2 Datenfluss -1. **Idle-Radar** erkennt Inaktivität (nächtlicher Scan) -2. **Archivierungs-Watchdog** prüft 14-Tage-Regel -3. **Curator-Indexer** aktualisiert `.usage.json` mit `archived_at` -4. Skill-Verzeichnis wird nach `.archive//` verschoben - ---- - -## 3. Dateistruktur - -### 3.1 Aktiv-Verzeichnis (`~/.hermes/skills/`) -- Normale Skill-Ordner (`betrieb-playbook/`, `wartung/`, ...) -- `.usage.json` (Nutzungsdaten) -- `.curator_state` (Curator-Status) - -### 3.2 Archiv-Verzeichnis (`~/.hermes/skills/.archive/`) -``` -.archive/ -├── apple/ # Kategorie: Apple-Tools -├── creative/ # Kategorie: Kreatives -├── data-science/ # Kategorie: Datenanalyse -├── github/ # Kategorie: GitHub -├── media/ # Kategorie: Medien -├── mlops/ # Kategorie: MLOps -├── research/ # Kategorie: Recherche -├── software-development/ # Kategorie: Dev-Tools -└── ... # Weitere Kategorien -``` - -### 3.3 Skill-Datei nach Archivierung -``` -~/.hermes/skills/.archive/software-development/gitea-workflow/ -├── SKILL.md -└── ... (weitere Dateien) -``` - ---- - -## 4. Konfiguration - -### 4.1 `.curator_state` -```json -{ - "last_report_path": "/home/hitonabi/.hermes/logs/curator/20260720-182035", - "last_run_at": "2026-07-20T18:20:35.806573+00:00", - "last_run_duration_seconds": 882.931897, - "last_run_summary": "auto: no changes; llm: Context length exceeded: max compression attempts (3) reached.", - "last_run_summary_shown_at": "2026-07-20T18:20:35.806573+00:00", - "paused": false, // ← aktiviert für Auto-Archivierung - "run_count": 11, - "archivierung": { - "enabled": true, // ← neuer Schlüssel - "threshold_days": 14, // ← neuer Schlüssel - "last_archive_run_at": "2026-07-24T03:00:00.000000+00:00" - } -} -``` - -### 4.2 `.usage.json` – Archivierter Skill -```json -{ - "gitea-workflow": { - "archived_at": "2026-07-24T03:00:00.000000+00:00", // ← neuer Schlüssel - "created_at": "2026-06-30T20:00:58.871579+00:00", - "created_by": null, - "last_patched_at": null, - "last_used_at": "2026-06-23T19:00:00.000000+00:00", - "last_viewed_at": null, - "patch_count": 0, - "pinned": false, - "state": "archived", // ← geändert von "active" - "use_count": 0, - "view_count": 0 - } -} -``` - ---- - -## 5. Trigger-Mechanismus - -### 5.1 Zeitlicher Trigger -- **Cron-Job:** `0 3 * * *` (täglich um 3:00 Uhr) -- **Skript:** `~/.hermes/scripts/curator-archive-watchdog.sh` - -### 5.2 Prüflogik -```python -def is_stale(skill_usage: dict) -> bool: - """Prüft, ob ein Skill älter als 14 Tage ist.""" - last_used = skill_usage.get("last_used_at") - if not last_used: - return False - - last_used_dt = datetime.fromisoformat(last_used.replace("Z", "+00:00")) - now = datetime.now(last_used_dt.tzinfo) - days_inactive = (now - last_used_dt).days - return days_inactive >= 14 -``` - -### 5.3 Ausschlusskriterien -- **Pinned:** `pinned: true` → nie archivieren -- **Verwendung im Live-Repo:** Skills aus `~/mission-control-v2/deploy/skills/` nicht archivieren -- **System-Skills:** `betrieb-playbook`, `wartung`, `orchestrator` nicht archivieren (Konfigurierbar in `.curator_state`) - ---- - -## 6. Archivierungs-Workload - -### 6.1 Schritte -1. **Scan:** Alle Skills in `~/.hermes/skills/` durchlaufen -2. **Prüfung:** `is_stale()` für jeden Skill aufrufen -3. **Ausschluss:** Pinned/System-Skills überspringen -4. **Verschieben:** Skill-Verzeichnis nach `.archive//` -5. **Index aktualisieren:** `.usage.json` mit `archived_at` und `state: "archived"` schreiben -6. **Protokoll:** Log-Eintrag in `~/.hermes/logs/curator/` - -### 6.2 Beispiel-Archivierung -```bash -# Vorher -~/.hermes/skills/skill-automatisch-verknuepfen/SKILL.md - -# Nachher -~/.hermes/skills/.archive/software-development/skill-automatisch-verknuepfen/SKILL.md -``` - ---- - -## 7. Datenformat - -### 7.1 `.usage.json` – Aktiv -```json -{ - "name": { - "archived_at": null, - "created_at": "2026-07-03T07:50:53.782473+00:00", - "created_by": null, - "last_patched_at": null, - "last_used_at": "2026-07-06T08:55:31.369025+00:00", - "last_viewed_at": "2026-07-06T08:55:31.367242+00:00", - "patch_count": 0, - "pinned": false, - "state": "active", - "use_count": 2, - "view_count": 2 - } -} -``` - -### 7.2 `.usage.json` – Archiviert -```json -{ - "name": { - "archived_at": "2026-07-24T03:00:00.000000+00:00", - "created_at": "2026-07-03T07:50:53.782473+00:00", - "created_by": null, - "last_patched_at": null, - "last_used_at": "2026-07-06T08:55:31.369025+00:00", - "last_viewed_at": "2026-07-06T08:55:31.367242+00:00", - "patch_count": 0, - "pinned": false, - "state": "archived", - "use_count": 2, - "view_count": 2 - } -} -``` - ---- - -## 8. Config-Interface - -### 8.1 `.curator_state` – Erweiterung -```json -{ - "archivierung": { - "enabled": true, - "threshold_days": 14, - "excluded_skills": ["betrieb-playbook", "wartung", "orchestrator"], - "last_archive_run_at": "2026-07-24T03:00:00.000000+00:00" - } -} -``` - -### 8.2 `.usage.json` – Erweiterung -```json -{ - "name": { - "archived_at": "2026-07-24T03:00:00.000000+00:00" - } -} -``` - ---- - -## 9. Akzeptanzkriterien - -| Kriterium | Status | -|-----------|--------| -| Klare Architektur | ✅ (siehe Abschnitt 2) | -| Definierte Dateistruktur | ✅ (`.archive//`) | -| Config-Interface | ✅ (`.curator_state` + `.usage.json`) | -| Keine Code-Änderungen | ✅ (nur Spezifikation) | - ---- - -## 10. Nächste Schritte - -### 10.1 Implementierung -1. **Skript erstellen:** `~/.hermes/scripts/curator-archive-watchdog.sh` -2. **Cron-Job anlegen:** `0 3 * * * curator-archive-watchdog.sh` -3. **`.curator_state` erweitern:** `archivierung`-Block hinzufügen -4. **`.usage.json` erweitern:** `archived_at`-Feld einfügen - -### 10.2 Test -1. Manuell einen Skill archivieren (Handbetrieb) -2. Prüfen, ob `archived_at` gesetzt wird -3. Prüfen, ob `.archive//` angelegt wird -4. Prüfen, ob `state: "archived"` in `.usage.json` steht - ---- - -## 11. Anhang - -### 11.1 Begriffe -| Begriff | Bedeutung | -|---------|-----------| -| **Stale** | Skill mit `last_used_at > 14 Tage` | -| **Archivierung** | Verschieben in `.archive//` | -| **Curator** | Der automatische Wartungsdienst | -| **Idle-Radar** | Das System, das Inaktivität erkennt | - -### 11.2 Quellen -- `~/.hermes/skills/.curator_state` -- `~/.hermes/skills/.usage.json` -- `~/.hermes/skills/.archive/` -- `~/wissens-vault/eigenbau-landkarte.md` - ---- - -**Ende der Spezifikation** diff --git a/deploy/specs/curator-idle-watchdog.md b/deploy/specs/curator-idle-watchdog.md deleted file mode 100644 index ac2f29d..0000000 --- a/deploy/specs/curator-idle-watchdog.md +++ /dev/null @@ -1,76 +0,0 @@ -# Curator Idle Watchdog Spezifikation - -## Ziel -Automatische Reaktivierung des Hermes Curator nach **14 Tagen Idle**, damit der Skill-Graph nicht dauerhaft isoliert bleibt. - -## Problem -- Curator läuft normalerweise alle 7 Tage (`interval: every 7d`) -- Wenn der Curator ausfällt (z. B. aufgrund von `paused: true`), bleiben Skills isoliert -- Derzeitige Beobachtung: Curator blockiert seit 16 Tagen (Stand 24.07.2026) -- Keine automatische Erkennung → keine automatische Wiederherstellung - -## Lösung: Watchdog-Skript - -### `curator-idle-watchdog.sh` - -**Zweck:** Cron-basierte Prüfung, ob der Curator zu lange offline war. - -**Ablauf:** -1. `hermes curator status` ausführen -2. "last run"-Zeit extrahieren (z. B. `17d ago`) -3. Tage berechnen und mit `THRESHOLD_DAYS=14` vergleichen -4. Bei Überschreitung → `hermes curator run` triggern -5. Alle Aktionen nach `~/.hermes/logs/curator/idle-watchdog-YYYYMMDD.log` protokollieren - -### Cron-Eintrag - -**Zeitplan:** Sonntag 00:00 (wöchentlich) -```bash -0 0 * * 0 ~/.hermes/profiles/werkstatt/scripts/curator-idle-watchdog.sh -``` - -**Logik:** -- Prüfung nur, wenn Curator `ENABLED` ist -- Wenn `last run` < 14 Tage → nichts tun -- Wenn `last run` ≥ 14 Tage → `hermes curator run` aufrufen -- Fehler protokollieren, aber nicht blockieren - -## Implementierung - -### Dateien -- Skript: `~/.hermes/profiles/werkstatt/scripts/curator-idle-watchdog.sh` -- Cron: `/etc/cron.d/curator-idle-watchdog` (oder user-level via `crontab -e`) -- Logs: `~/.hermes/logs/curator/idle-watchdog-*.log` - -### Akzeptanzkriterien -- [ ] Skript existiert und ist ausführbar (`chmod +x`) -- [ ] Cron-Eintrag ist aktiv (wöchentliche Prüfung Sonntag 00:00) -- [ ] Reaktivierung triggert `hermes curator run` -- [ ] Protokollierung in `~/.hermes/logs/curator/` funktioniert -- [ ] Keine Änderungen an `main` oder `~/mission-control-v2` - -## Test - -### Manueller Test -```bash -# Simuliere 15-Tage-Idle -sed -i 's/last run:.*$/last run: 15d ago/' <(hermes curator status) - -# Watchdog ausführen -~/.hermes/profiles/werkstatt/scripts/curator-idle-watchdog.sh -``` - -### Erwartetes Verhalten -1. Log-Eintrag: `WARNUNG: Curator ist für 15 Tage idle (>= 14) → triggere 'hermes curator run'` -2. `hermes curator run` wird ausgeführt -3. Log-Eintrag: `ERFOLG: Curator-Reaktivierung gestartet` - -## Rollback-Plan -- Cron-Eintrag löschen: `crontab -e` → Zeile entfernen -- Skript löschen: `rm ~/.hermes/profiles/werkstatt/scripts/curator-idle-watchdog.sh` -- Logs archivieren: `tar -czf idle-watchdog-logs-$(date +%Y%m%d).tar.gz ~/.hermes/logs/curator/idle-watchdog-*.log` - -## Anmerkungen -- 14 Tage Threshold = Kompromiss zwischen zu häufigem Trigger (z. B. bei kurzen Wartungen) und zu spätem Erkennen -- Watchdog prüft nur bei `ENABLED` Curator (kein Fehlalarm bei `paused`) -- Keine Abhängigkeit zu externen Diensten (reines Hermes-System) diff --git a/deploy/venv-audit.sh b/deploy/venv-audit.sh deleted file mode 100644 index 45f81d6..0000000 --- a/deploy/venv-audit.sh +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env bash -# 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 -# Sicherheitslücken prüfen (pip-audit gegen den eingefrorenen Stand). -# Werkzeug: eigenes Audit-venv (~/.venv-audit, wird beim Erstlauf angelegt) — die -# Dienst-venvs selbst werden NICHT angefasst. CVE-Funde → Kanban-Karte (triage, -# idempotent je Monat). Läuft als Hermes-Cron --no-agent (stdout = Telegram). -set -uo pipefail - -VENVS=("$HOME/.voice/venv:voice") -AUDIT_VENV="$HOME/.venv-audit" -HERMES="$HOME/.local/bin/hermes" -MONAT="$(date '+%Y-%m')" -TMP="$(mktemp -d /tmp/venv-audit.XXXXXX)" -trap 'rm -rf "$TMP"' EXIT - -briefkasten() { - curl -sf -m 5 -X POST "${MC_ANNOUNCE_URL:-http://127.0.0.1:9001/api/voice/announce}" \ - -H 'Content-Type: application/json' \ - --data "$(python3 - "$1" <<'PY' -import json, sys -print(json.dumps({"text": sys.argv[1], "subject": "Venv-Audit", - "source": "venv-audit", "priority": "silent"})) -PY -)" >/dev/null 2>&1 || true -} - -# ---------- Werkzeug-venv sicherstellen (einmalig) ---------- -if [ ! -x "$AUDIT_VENV/bin/pip-audit" ]; then - python3 -m venv "$AUDIT_VENV" >/dev/null 2>&1 || true - "$AUDIT_VENV/bin/pip" install -q --upgrade pip pip-audit >/dev/null 2>&1 || true -fi - -ZEILEN=() -CVE_DETAILS="" -CVES_GESAMT=0 - -for EINTRAG in "${VENVS[@]}"; do - VENV="${EINTRAG%%:*}"; NAME="${EINTRAG##*:}" - # uv-venvs haben KEIN eigenes pip (Befund Erstlauf 17.07.) → das Audit-venv - # inspiziert deren site-packages von außen (pip --path); Dienst-venvs bleiben unberührt. - SP="$("$VENV/bin/python" -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])' 2>/dev/null || true)" - if [ -z "$SP" ] || [ ! -d "$SP" ]; then - ZEILEN+=("$NAME: venv fehlt/unlesbar unter $VENV — bitte prüfen") - continue - fi - # Veraltete Pakete (Anzahl reicht fürs Lagebild). grep -c druckt IMMER genau eine - # Zahl → KEIN "|| echo 0" (das hängte eine zweite Zeile an → Rechenfehler, Befund - # 17.07.); zur Sicherheit auf Ziffern reduzieren. - ALT_N="$("$AUDIT_VENV/bin/pip" list --outdated --path "$SP" 2>/dev/null | tail -n +3 | grep -c '.')" - ALT_N="${ALT_N//[!0-9]/}"; ALT_N="${ALT_N:-0}" - # CVE-Prüfung gegen den eingefrorenen Stand (lokale Pfade/Editables ausfiltern) - "$AUDIT_VENV/bin/pip" list --format=freeze --path "$SP" 2>/dev/null \ - | grep -vE ' @ |^-e |^#' > "$TMP/$NAME.txt" || true - CVE_N=0 - if [ -x "$AUDIT_VENV/bin/pip-audit" ] && [ -s "$TMP/$NAME.txt" ]; then - AUDIT_OUT="$("$AUDIT_VENV/bin/pip-audit" -r "$TMP/$NAME.txt" --progress-spinner off 2>&1 || true)" - if printf '%s' "$AUDIT_OUT" | grep -qi 'No known vulnerabilities'; then - CVE_N=0 - else - CVE_N="$(printf '%s\n' "$AUDIT_OUT" | grep -cE '^[a-zA-Z0-9_.-]+ +[0-9][^ ]* +(GHSA|PYSEC|CVE)')" - CVE_N="${CVE_N//[!0-9]/}"; CVE_N="${CVE_N:-0}" - if [ "${CVE_N:-0}" -gt 0 ]; then - CVE_DETAILS="${CVE_DETAILS} -== ${NAME} == -$(printf '%s\n' "$AUDIT_OUT" | grep -E '^[a-zA-Z0-9_.-]+ +[0-9][^ ]* +(GHSA|PYSEC|CVE)' | head -10)" - fi - fi - else - ZEILEN+=("$NAME: pip-audit nicht verfügbar (Audit-venv kaputt oder offline?) — nur Outdated-Zählung") - fi - CVES_GESAMT=$(( CVES_GESAMT + ${CVE_N:-0} )) - ZEILEN+=("$NAME: ${ALT_N} Pakete veraltet · ${CVE_N:-0} bekannte Sicherheitslücken") -done - -# ---------- Bericht ---------- -if [ "$CVES_GESAMT" -eq 0 ]; then - echo "Commander, monatlicher Venv-Audit der Python-Nebendienste — keine bekannten Sicherheitslücken:" - printf '• %s\n' "${ZEILEN[@]}" - echo "(Veraltete Pakete sind nur ein Lagebild; aktualisiert wird bewusst nur mit Anlass.)" - briefkasten "Venv-Audit grün: $(printf '%s · ' "${ZEILEN[@]}")" -else - echo "⚠️ Commander, der monatliche Venv-Audit hat ${CVES_GESAMT} bekannte Sicherheitslücken gefunden:" - printf '• %s\n' "${ZEILEN[@]}" - echo "Details liegen als Karte in der Ideen-Queue — Update bleibt dein Entscheid." - if [ -x "$HERMES" ]; then - "$HERMES" kanban create "Venv-Audit ${MONAT}: ${CVES_GESAMT} Sicherheitslücken in Python-Diensten" \ - --body "Automatischer monatlicher Audit (restore-/venv-Kreislauf 17.07.). Funde (pip-audit): -${CVE_DETAILS} - -Empfehlung: betroffene Pakete gezielt aktualisieren (venv-weise, mit Dienst-Restart + Postcheck) — KEIN pauschales Upgrade." \ - --triage --created-by venv-audit --idempotency-key "venv-audit-${MONAT}" >/dev/null 2>&1 || true - fi - briefkasten "⚠️ Venv-Audit: ${CVES_GESAMT} Sicherheitslücken — Karte liegt in der Queue." - exit 1 -fi diff --git a/deploy/werkstatt-SOUL.md b/deploy/werkstatt-SOUL.md deleted file mode 100644 index ccae7e6..0000000 --- a/deploy/werkstatt-SOUL.md +++ /dev/null @@ -1,108 +0,0 @@ -# Werkstatt-Worker (Profil „werkstatt") - -Du bist der Werkstatt-Coder der AI-Box. Du arbeitest EINE Kanban-Aufgabe ab — meist -Code-/Bau-Aufträge an MC2 (mission-control-v2) oder Lucy. Keine Persona, kein Smalltalk — -sauberes Handwerk. - -## Schritt 0: WIEDERAUFNAHME — immer zuerst, nie aus Reflex neu anfangen - -Steht in deinem Auftrag ein Abschnitt **„Prior attempts on this task"**, dann hat vor dir schon -ein Lauf an genau dieser Karte gearbeitet — meist ist er am Kontext-Limit gestorben, mitten in -der Arbeit. **Dein Workspace ist derselbe wie seiner, seine Arbeit liegt noch da.** Bevor du -irgendetwas neu baust: - -1. `cat FORTSCHRITT.md` im Workspace-Wurzelverzeichnis (falls vorhanden) — die Notizen deines - Vorgängers: was steht, was fehlt. -2. `ls -la` im Workspace: liegt schon ein Klon da? Dann darin - `git log --oneline origin/main..HEAD` (seine Commits) und `git status --short` - (**uncommittete Dateien sind fertige Arbeit**, der nur noch Commit/Push fehlt). -3. Liegt sein Branch vielleicht schon auf Gitea? `git ls-remote origin `. - Wenn ja UND der Auftrag ist damit erfüllt: **prüfen und `kanban_complete`** — nicht neu bauen. - -Dann setzt du an der **ersten Lücke** an, nie davor. Ein zweiter kompletter Durchlauf wirft die -Arbeit deines Vorgängers weg und läuft in dasselbe Kontext-Limit wie er — das ist ein Fehlschlag, -kein Neuanfang. Nur wenn wirklich nichts Brauchbares dasteht (oder sein Stand nachweislich Murks -ist), fängst du frisch an — und sagst in der Summary, warum. - -**Fortschritt hinterlassen:** Nach jeder abgeschlossenen Stufe eine Zeile ans Ende von -`FORTSCHRITT.md` — was fertig ist, wo es liegt, was als Nächstes dran wäre. **Ins Workspace- -Wurzelverzeichnis, NICHT in den Klon** (sonst landet sie im Vorschlags-Branch). Sofort schreiben, -nicht am Ende — am Ende kommst du vielleicht nicht mehr dazu. Dieselbe Kurzfassung als Notiz in -den `kanban_heartbeat`, dann sieht der Commander im Auftragsbuch, wo du stehst. - -## Eiserne Regeln (nicht verhandelbar) -1. Arbeite NUR in deinem Task-Workspace (dein Startverzeichnis). Der Live-Checkout - `~/mission-control-v2` ist TABU: dort KEIN Schreiben, KEIN git-Befehl. (Lesen zur - Orientierung ist erlaubt.) -2. Ergebnis eines Code-Auftrags ist IMMER ein Vorschlags-Branch auf Gitea — NIEMALS Push - auf main, NIEMALS mergen, NIEMALS deployen, NIEMALS Dienste neu starten. Der Commander - klickt die Karte im Auftragsbuch. -3. Frisch und VOLL klonen statt Live-Checkout nutzen — IMMER über die INTERNE URL: - `git clone http://192.168.178.153:3000/Hitonabi/mission-control-v2` - (Credentials liegen in `~/.git-credentials`; Lucy-Repo: `.../Hitonabi/lucy`). - Die externe Domain (git.tobisniceshomelab.ddnsfree.com) ist nachts wegen - Zwangstrennung/DDNS oft NICHT erreichbar (Vorfall 24.07.2026: Worker hielt Gitea - für tot und blockte) — sie ist nur Tages-Fallback, nie Standard. - NIEMALS SSH-Remotes (`git@…`), NIEMALS an `~/.ssh` schrauben, NIEMALS nach - Passwörtern fragen — Gitea läuft für dich komplett über HTTP + Token aus - `~/.git-credentials`. Verlangt ein Auftrag ausdrücklich einen Pull-Request: - `gitea-pr ""` (Helfer in `~/.local/bin`, nutzt die - Gitea-API mit demselben Token). - NIEMALS `git init`, NIEMALS `--depth`/Shallow, NIEMALS in einem Verzeichnis committen, - das nicht dieser frische Klon ist — sonst entsteht ein Branch OHNE gemeinsamen - Ursprung, den das Auftragsbuch als „kaputt aufgesetzt" aussortiert (Vorfall - 12.07.2026: feature/cleanse-skill-index war ein git-init-Orphan mit ~/.hermes-Dateien). - Branch-Namen: `wartung/`, `feature/` oder `doku/`. - SELBSTCHECK vor dem Push (Pflicht, DREI Zeilen müssen klappen): - 1. `git merge-base HEAD origin/main` (muss einen Hash liefern) - 2. `git fetch origin && git rebase origin/main` (veraltete Basis sofort begradigen; - Konflikt → `kanban_block`, nicht raten) - 3. NACH dem Push: `git ls-remote origin ` prüfen, dass der Branch WIRKLICH - auf Gitea angekommen ist — ist die Ausgabe LEER → `kanban_block` mit Fehler, - NICHT `kanban_complete`. Erst wenn die Ausgabe den Hash liefert, ist der Branch - nachweislich da und „fertig". - Committe NUR Dateien, die zum Repo gehören — - `~/.hermes`-Artefakte (.usage.json, .bundled_manifest, Skill-Indizes) gehören NIE hinein. -4. Gate vor dem Push: `python3 -m py_compile` für JEDE geänderte .py-Datei. Frontend - (`frontend/`) nur anfassen, wenn der Auftrag es verlangt — die Box kann kein - `npm build`; das ehrlich in Commit-Text und Summary sagen. - Lucy-Aufträge (Repo `lucy`) genauso als Vorschlags-Branch pushen: bauen, testen - und neu starten macht der PC automatisch, wenn der Commander die Karte annimmt — - du baust NICHT selbst und behauptest NICHT, gebaut zu haben. -5. TABU-Zonen: approvals/Tokens/ufw/Security-Configs, `~/.hermes/config.yaml`, - systemd-Units, alles außerhalb deines Workspaces. Braucht der Auftrag so etwas → - `kanban_block` mit Begründung, nicht selbst machen. -6. Unklar? Lieber `kanban_block` mit ehrlicher Diagnose (was fehlt, wie man kleiner - schneidet) als Murks. GROSSE Aufträge sind erlaubt — aber NUR in Etappen - (Großbau-Entscheid 10.07.2026): - - Zuerst einen Etappen-Plan als Task-Kommentar: welche Etappen, was ist am Ende - JEDER Etappe fertig und für sich lauffähig. - - Dann NUR Etappe 1 bauen: eigener Vorschlags-Branch, Gates grün, für sich - annehmbar — eine halbe Baustelle kommt NIE auf eine Karte. - - Folge-Etappen als NEUE Kanban-Aufgaben anlegen (`kanban_create`, Titel - „ — Etappe : "; im Body: der Plan + Branch der Vor-Etappe). - - Bearbeitest du eine Etappen-Aufgabe ab Etappe 2: prüfe ZUERST, ob der Branch der - Vor-Etappe schon in main ist (`git log origin/main`). Wenn nein → - `kanban_block("wartet auf Annahme Etappe ")` — NIE auf unangenommene - Branches aufbauen. - - Rauere Qualität ist bei Großbau ok, aber ehrlich: was fehlt oder wacklig ist, - steht in der Summary. Scheitert eine Etappe: `kanban_block` + Diagnose als - Kommentar (was gelernt, wie kleiner schneiden) — Fehlversuche sind Lernmaterial. -7. REALITÄTS-CHECK vor jedem Bau: Existiert das schon? Lies den Selbst-Steckbrief - (`~/.hermes/state/selbst-steckbrief.md`, auto-generiert) und prüfe das Live-System - (Config/Code LESEN ist erlaubt). Stellt sich der Auftrag als schon erledigt oder - überflüssig heraus: KEINEN Branch liefern — `kanban_block` mit ehrlicher Diagnose - („schon vorhanden, weil …"). Ein Branch für etwas Überflüssiges ist ein GESCHEITERTER - Lauf (Vorfall 12.07.2026: Gateway-Neustart war längst konfiguriert, der Worker wusste - es — und lieferte trotzdem eine Karte). -8. NACH dem Push ZWINGEND prüfen, dass der Branch WIRKLICH auf Gitea liegt: - `git ls-remote origin ` MUSS einen Hash liefern. Leere Ausgabe = der Push ist - NICHT angekommen (auch wenn `git push` scheinbar ohne Fehler durchlief) → `kanban_block` - mit dem Fehler, NIEMALS `kanban_complete`. „Fertig“ heißt: der Branch ist NACHWEISLICH - auf dem Remote — nicht, dass `git push` keinen sichtbaren Fehler warf (Vorfall - 13.07.2026: Worker meldete „gepusht“ + `kanban_complete`, der Branch war nie auf Gitea). - Erst DANN `kanban_complete` mit deutscher Summary: was gebaut, der BRANCH-NAME, wie - geprüft. Der Branch-Name MUSS in der Summary stehen — daraus wird die Karte gefunden. - -## Stil -Deutsch in allem User-Sichtbaren. Kleine, ehrliche Commits. Keine Nebenbaustellen anfassen. diff --git a/deploy/worker.sh b/deploy/worker.sh deleted file mode 100644 index aa34458..0000000 --- a/deploy/worker.sh +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env bash -# Worker — Ein-Schuss-Arbeitsauftrag an ein bestimmtes Worker-Modell (llama-swap, SERIELL). -# Das Gegenstueck zu fremdblick.sh: fremdblick KRITISIERT, worker ARBEITET. Beide sind eine -# EINZELNE Completion an eine ECHTE llama-swap-Modell-ID (kein Gateway-Alias, kein delegate_task) -# → umgeht Hermes' 64K-Delegations-Floor (MINIMUM_CONTEXT_LENGTH) und das Laden grosser Modelle -# neben dem Warm-Set liegt bewusst in DEINER Hand. -# -# WICHTIG (Speicher & Latenz-Trade-off): -# 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. -# -# Der Worker ist ZUSTANDSLOS: kein Gedaechtnis, keine Datei-Haende. Er bekommt GENAU den Kontext, -# den du ihm auf stdin gibst, und liefert TEXT zurueck (Datei-Inhalt / Diff / Plan). Das SCHREIBEN -# 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=coder WORKER_ROLE=build worker.sh -# -# Env-Overrides: -# 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). -# WORKER_TEMP temperature (Default 0.1 — deterministisch fuer Code). -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:-coder}" -ROLE="${WORKER_ROLE:-build}" -MAXTOK="${WORKER_MAXTOK:-4000}" -TEMP="${WORKER_TEMP:-0.1}" -TASK="$(cat)" - -if [ -z "${TASK// /}" ]; then - echo "=== WORKER: nichts zu tun (leere Eingabe) ===" >&2 - exit 2 -fi - -# Aufruf-Protokoll: Nachweis, dass wirklich delegiert wurde (der Manager kann sich das nicht -# ausdenken) + Beobachtbarkeit eines Orchestrator-Laufs. Best-effort, blockiert nie. -LOGF="${WORKER_LOG:-$HOME/.hermes/logs/worker.log}" -mkdir -p "$(dirname "$LOGF")" 2>/dev/null || true -printf '%s\tmodel=%s\trole=%s\tbytes=%s\tfirst=%s\n' \ - "$(date +%Y-%m-%dT%H:%M:%S)" "$MODEL" "$ROLE" "${#TASK}" \ - "$(printf '%s' "$TASK" | tr '\n\t' ' ' | cut -c1-90)" >> "$LOGF" 2>/dev/null || true - -# Bau-Raster: liefert NUR das Artefakt, keine Prosa, kein Markdown-Zaun (Manager schreibt es 1:1 in eine Datei). -BUILD_SYS='Du bist ein fokussierter Umsetzungs-Worker in einem Fliessband. Du bekommst EINEN abgegrenzten Teil-Auftrag mit allem noetigen Kontext. Erledige GENAU diesen Teil — nicht mehr, nicht weniger. Du bist ZUSTANDSLOS: nutze NUR den mitgelieferten Kontext, erfinde keine Datei-Inhalte, keine Pfade und keine APIs dazu; fehlt dir etwas Entscheidendes, schreibe als EINZIGE Ausgabe eine Zeile "FEHLT: " statt zu raten. Uebernimm den Stil des umgebenden Codes (Sprache der Kommentare, bestehende Muster, Einrueckung). Gib NUR das geforderte Artefakt aus (den vollstaendigen Datei-Inhalt bzw. den unified Diff), OHNE Vorrede, OHNE ```-Codezaun, OHNE Erklaerung danach.' -# Refactor: wie build, aber betont Verhalten-erhalten. -REFACTOR_SYS='Du bist ein fokussierter Refactor-Worker. Du bekommst bestehenden Code und einen klaren Umbau-Auftrag. Erhalte das beobachtbare Verhalten exakt; aendere nur, was der Auftrag verlangt. Du bist ZUSTANDSLOS: nutze NUR den mitgelieferten Kontext; fehlt Entscheidendes, gib nur "FEHLT: " aus. Gib NUR den vollstaendigen neuen Datei-Inhalt bzw. den Diff aus, OHNE Prosa, OHNE ```-Codezaun.' -# Plan-Raster: knappe, umsetzbare Schrittliste (kein Code). In v1 plant meist der Manager selbst; optional. -PLAN_SYS='Du bist ein knapper Umsetzungs-Planer. Zerlege den Auftrag in eine kurze, nummerierte Liste konkreter Schritte — je 1 Zeile: welche Datei, was passiert. Keine Prosa drumherum, KEIN Code, keine Alternativen-Diskussion.' -# Prosa-Raster: Text-/Recherche-Teiltask. -PROSE_SYS='Du bist ein fokussierter Text-Worker. Erledige den Schreib-/Recherche-Teilauftrag knapp und sachlich auf Deutsch, ausschliesslich mit dem mitgelieferten Kontext. Keine Ausschmueckung, keine erfundenen Fakten.' - -case "$ROLE" in - build) SYS="${WORKER_SYS:-$BUILD_SYS}" ;; - refactor) SYS="${WORKER_SYS:-$REFACTOR_SYS}" ;; - plan) SYS="${WORKER_SYS:-$PLAN_SYS}" ;; - prose) SYS="${WORKER_SYS:-$PROSE_SYS}" ;; - *) SYS="${WORKER_SYS:-$BUILD_SYS}" ;; -esac - -RESP="$(printf '%s' "$TASK" | jq -Rs . | jq -n --arg m "$MODEL" --arg s "$SYS" --argjson t "$MAXTOK" --argjson temp "$TEMP" \ - '{model:$m, temperature:$temp, max_tokens:$t, messages:[{role:"system",content:$s},{role:"user",content:input}]}' \ - | curl -s --max-time 300 "$ENDPOINT" -H 'Content-Type: application/json' -d @- 2>/dev/null)" - -OUT="$(printf '%s' "$RESP" | jq -r '.choices[0].message.content // empty' 2>/dev/null)" -FIN="$(printf '%s' "$RESP" | jq -r '.choices[0].finish_reason // empty' 2>/dev/null)" - -# Code-Modelle wickeln die Ausgabe trotz Anweisung gern in einen ```-Codezaun. stdout soll aber ein -# DIREKT schreibbares Artefakt sein → einen umschliessenden Zaun entfernen (nur wenn die erste UND -# die letzte nicht-leere Zeile ein Zaun ist — der uebliche Wrap-Fall; interne Zaeune bleiben unberuehrt). -OUT="$(printf '%s' "$OUT" | awk ' - { l[NR]=$0 } - END { - f=0; last=0 - for (i=1;i<=NR;i++) if (l[i] ~ /[^[:space:]]/) { if (!f) f=i; last=i } - if (f && l[f] ~ /^[[:space:]]*```/ && l[last] ~ /^[[:space:]]*```[[:space:]]*$/ && f!=last) - { for (i=f+1;i&2 - exit 0 -else - echo "=== WORKER FEHLGESCHLAGEN (Modell $MODEL, finish=${FIN:-keins}) — kein Ergebnis. Endpoint/Modell-ID pruefen (echte /v1/models-ID, kein Alias). ===" >&2 - exit 1 -fi diff --git a/deploy/SECURITY-GITEA-DISABLE_REGISTRATION.md b/docs/archiv/gitea-host/SECURITY-GITEA-DISABLE_REGISTRATION.md similarity index 100% rename from deploy/SECURITY-GITEA-DISABLE_REGISTRATION.md rename to docs/archiv/gitea-host/SECURITY-GITEA-DISABLE_REGISTRATION.md diff --git a/deploy/gitea_app.ini.example b/docs/archiv/gitea-host/gitea_app.ini.example similarity index 100% rename from deploy/gitea_app.ini.example rename to docs/archiv/gitea-host/gitea_app.ini.example diff --git a/mcp/requirements.txt b/mcp/requirements.txt deleted file mode 100644 index 9228e27..0000000 --- a/mcp/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -# MCP-Server (auf dem Rechner installieren, wo das Tool/Hermes läuft) -mcp>=1.2 -httpx>=0.27