From faf25bf76c29e834dd385fcee17b5db26627ff01 Mon Sep 17 00:00:00 2001 From: Hitonabi Date: Mon, 13 Jul 2026 22:51:47 +0200 Subject: [PATCH] Faden 8+5-Fix: Worker-Scope korrigieren + Profil-Hooks reproduzierbar Zwei zusammenhaengende Bugs, beim echten Worker-Test entdeckt: 1) HOOKS FEUERTEN FUER WORKER NIE. Kanban-Worker laufen unter IHREM Profil (HERMES_HOME=~/.hermes/profiles/) und lesen dessen config.yaml, NICHT die Top-Level ~/.hermes/config.yaml. Die neuen Hooks waren nur oben registriert -> fuer Worker inaktiv. FIX: ensure-profile-hooks.py registriert pre_llm_call + pre_tool_call in jeder Worker-Profil-config (idempotent, validiert, Backup); deploy.sh ruft es je Profil auf -> reproduzierbar. 2) FALSCHER SCOPE. Beide Hooks scopeten auf task_id == t_. Aber Worker tragen als effective_task_id den SESSION-Zeitstempel (z.B. 20260713_224206_267304), NICHT die Kanban-t_-ID -> der Check schlug immer fehl -> box-steckbrief injizierte nie, tabu-guard liess alles durch. FIX: - tabu-pfade-guard.py: Schutz jetzt UNBEDINGT (kein Task-Scope) - kein legitimer Hermes-Agent-Flow schreibt je in Live-Checkout/Hermes-Quelle (Werkstatt arbeitet im Workspace-Klon, Wartung ist Shell nicht Agent-Tool). - box-steckbrief-inject.sh: Scope jetzt cwd unter ~/.hermes/kanban/workspaces/ (zuverlaessiger Worker-Signal; Lucy/Voice/CLI laufen nie dort). Verifiziert: echter betrieb-Worker versuchte in den Live-Checkout zu schreiben -> GEBLOCKT, Datei nicht erstellt, TABU-Meldung im Log. 26 Guard-Unit-Faelle gruen. Co-Authored-By: Claude Opus 4.8 --- .gitattributes | 3 +- deploy/agent-hooks/box-steckbrief-inject.sh | 30 ++++----- deploy/agent-hooks/tabu-pfade-guard.py | 32 +++++---- deploy/deploy.sh | 6 ++ deploy/ensure-profile-hooks.py | 72 +++++++++++++++++++++ 5 files changed, 109 insertions(+), 34 deletions(-) create mode 100644 deploy/ensure-profile-hooks.py diff --git a/.gitattributes b/.gitattributes index 619520b..1653461 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,8 +8,9 @@ *.timer text eol=lf # Agent-Hooks laufen auf der Box (Shebang!) — .py-Hooks MÜSSEN LF behalten, -# sonst bricht `#!/usr/bin/env python3\r`. (Nur die Hooks, nicht der ganze Baum.) +# sonst bricht `#!/usr/bin/env python3\r`. (Nur die Hooks + Deploy-Helfer, nicht der ganze Baum.) deploy/agent-hooks/*.py text eol=lf +deploy/*.py text eol=lf # Windows-Batch-Wrapper bleiben CRLF. *.cmd text eol=crlf diff --git a/deploy/agent-hooks/box-steckbrief-inject.sh b/deploy/agent-hooks/box-steckbrief-inject.sh index 4bac8ad..bfc9987 100644 --- a/deploy/agent-hooks/box-steckbrief-inject.sh +++ b/deploy/agent-hooks/box-steckbrief-inject.sh @@ -1,25 +1,23 @@ #!/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, default=Lucys Persona als Worker, -# kuenftige betrieb-/Ops-Profile). Der Kontext wird ephemer pro Turn an die User-Message -# angehaengt (agent/turn_context.py), NUR wenn die task_id ein echtes Kanban-`t_` -# ist. Lucys interaktiver/Voice-Chat laeuft mit einer UUID-Ersatz-task_id (nicht `t_...`) -# → sofortiger No-op, kein Persona- oder Latenz-Eingriff. Loest Fallstrick #1 (Ops/Mess/Verify-Worker landen auf der am -# schlechtesten geruesteten Persona und flailen, weil sie llama-swap :8080 nicht kennen). +# 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. ACHTUNG (agent/turn_context.py:216): -# Hermes setzt effective_task_id = task_id ODER — fuer Lucy-Chat/Voice/CLI/Telegram — -# eine zufaellige UUID. Ein blosser "nicht leer"-Check wuerde also auch Lucy treffen -# (Persona-/Latenz-Schaden). Kanban-IDs sind IMMER `t_`, UUIDs enthalten Bindestriche -# und beginnen nie mit t_ → das Muster trennt sauber. Alles andere: sofortiger No-op -# (ein jq, im Voice-Pfad vernachlaessigbar). -task_id=$(printf %s "$payload" | jq -r '.extra.task_id // .task_id // ""' 2>/dev/null || echo "") -case "$task_id" in - t_[0-9a-f][0-9a-f][0-9a-f][0-9a-f]*) : ;; # Kanban-Worker → Orientierung anhaengen - *) printf '{}'; exit 0 ;; # Lucy / UUID / leer → No-op +# 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). diff --git a/deploy/agent-hooks/tabu-pfade-guard.py b/deploy/agent-hooks/tabu-pfade-guard.py index 49b01e9..105e7cd 100644 --- a/deploy/agent-hooks/tabu-pfade-guard.py +++ b/deploy/agent-hooks/tabu-pfade-guard.py @@ -1,15 +1,20 @@ #!/usr/bin/env python3 -"""pre_tool_call-Guard (Faden 8, 13.07.2026): sperrt Schreibzugriffe von Kanban-Workern -auf den LIVE-Checkout ~/mission-control-v2 und die Hermes-QUELLE ~/.hermes/hermes-agent -HART - egal welches Profil. Nur fuer echte Worker (task_id = t_); Lucy/Wartung/ -interaktiv/CLI (UUID-task_id) = sofortiger No-op. 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) -> dieser Guard schliesst die Luecke. +"""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. -WICHTIG: Der Worker KLONT MC2 in seinen Workspace (/mission-control-v2) und -arbeitet DORT legitim. Gesperrt wird deshalb NUR der home-verankerte Live-Checkout -($HOME/mission-control-v2), NIE der blosse Name "mission-control-v2". Lesen ist erlaubt - -geblockt werden nur Schreib-Operationen. +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 NUR der home-verankerte Live-Checkout ($HOME/mission-control-v2), NIE +der blosse Name "mission-control-v2" (der Workspace-Klon enthaelt ihn) — sonst waere die +legitime Worker-Arbeit blockiert. Lesen ist erlaubt; geblockt werden nur Schreib-Operationen. Ausgabe {"action":"block","message":...} = Tool geblockt; {} = durchlassen. """ @@ -26,13 +31,6 @@ try: except Exception: out({}) -extra = payload.get("extra") or {} -task_id = str(extra.get("task_id") or payload.get("task_id") or "") -# Scope: NUR echte Kanban-Worker. Hermes setzt fuer Lucy/CLI eine UUID-task_id -# (agent/turn_context.py) -> nur das t_-Muster ist ein Worker. -if not re.match(r"^t_[0-9a-f]{4,}$", task_id): - out({}) - tool = payload.get("tool_name") or "" ti = payload.get("tool_input") or {} home = os.path.realpath(os.path.expanduser("~")) diff --git a/deploy/deploy.sh b/deploy/deploy.sh index 40e7d12..cb3caf3 100644 --- a/deploy/deploy.sh +++ b/deploy/deploy.sh @@ -136,6 +136,12 @@ for _h in box-steckbrief-inject.sh pre-verify-gates.sh tabu-pfade-guard.py; do _src="$SRC/deploy/agent-hooks/$_h"; _dst="$HOME/.hermes/agent-hooks/$_h" cmp -s "$_src" "$_dst" 2>/dev/null || { cp "$_src" "$_dst"; chmod +x "$_dst"; } done +# Worker-Profile lesen IHRE config.yaml, nicht die Top-Level — die worker-Hooks +# (box-steckbrief = Box-Wissen, tabu-pfade-guard = Schreibschutz) muessen dort registriert +# sein, sonst feuern sie fuer Worker nie (Bug-Fund 13.07.). Idempotent + Backup je Profil. +for _prof in "$HOME"/.hermes/profiles/*/config.yaml; do + [ -f "$_prof" ] && python3 "$SRC/deploy/ensure-profile-hooks.py" "$_prof" || true +done # Eingebaute Hermes-Web-GUI (`hermes serve`) für die Einbettung in MC2 (routers/hermes_ui.py → # same-origin /hermes-ui/) vorbereiten: das SPA-Bundle mit Vite-base=/hermes-ui/ (neu) bauen, sonst # kollidieren seine absoluten Pfade (/assets, /api) mit MC2s eigenen. Läuft auch nach jedem diff --git a/deploy/ensure-profile-hooks.py b/deploy/ensure-profile-hooks.py new file mode 100644 index 0000000..ad9d0db --- /dev/null +++ b/deploy/ensure-profile-hooks.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +"""Stellt sicher, dass ein Worker-Profil die Agent-Hooks kennt (Faden 8, 13.07.2026). + +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 +(box-steckbrief-inject = Box-Wissen, tabu-pfade-guard = Schreibschutz) muessen also in JEDER +Worker-Profil-config.yaml stehen, sonst feuern sie fuer Worker nie (Bug-Fund 13.07.). + +Idempotent + validiert + Backup. Setzt einen bestehenden `pre_verify`-Hook-Block als Anker +voraus (haben alle von default/werkstatt geklonten Profile); fehlt er, wird sauber +uebersprungen (exit 0), damit deploy.sh nicht bricht. Arg: Pfad zur Profil-config.yaml. +""" +import sys +import os +import datetime +import shutil + +try: + import yaml +except Exception: + print(" (PyYAML fehlt — ensure-profile-hooks uebersprungen)") + sys.exit(0) + +AH = os.path.expanduser("~/.hermes/agent-hooks") +WANT = { + "pre_llm_call": f"{AH}/box-steckbrief-inject.sh", + "pre_tool_call": f"{AH}/tabu-pfade-guard.py", +} + +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) + +add = "" +for event, cmd in WANT.items(): + if cmd not in txt: + add += f" {event}:\n - command: {cmd}\n timeout: 10\n" + +if not add: + print(f" [{os.path.basename(os.path.dirname(p))}] Hooks schon registriert.") + sys.exit(0) + +new = txt.replace(anchor, anchor + add, 1) +try: + d = yaml.safe_load(new) + h = d["hooks"] + for event, cmd in WANT.items(): + assert any(cmd in x.get("command", "") for x in h.get(event, [])) + assert any("pre-verify-gates.sh" in x.get("command", "") for x in h["pre_verify"]) +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)}).")