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/<name>) 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_<hex>. 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 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-07-13 22:51:47 +02:00
parent 5d02e7af91
commit faf25bf76c
5 changed files with 109 additions and 34 deletions
+2 -1
View File
@@ -8,8 +8,9 @@
*.timer text eol=lf *.timer text eol=lf
# Agent-Hooks laufen auf der Box (Shebang!) — .py-Hooks MÜSSEN LF behalten, # 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/agent-hooks/*.py text eol=lf
deploy/*.py text eol=lf
# Windows-Batch-Wrapper bleiben CRLF. # Windows-Batch-Wrapper bleiben CRLF.
*.cmd text eol=crlf *.cmd text eol=crlf
+14 -16
View File
@@ -1,25 +1,23 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# pre_llm_call-Hook (Faden 5, 13.07.2026): verdrahtet Box-Selbstwissen MECHANISCH in # 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, # JEDEN Kanban-Worker — egal welches Profil (werkstatt, betrieb, default-als-Worker).
# kuenftige betrieb-/Ops-Profile). Der Kontext wird ephemer pro Turn an die User-Message # Der Kontext wird ephemer pro Turn an die User-Message angehaengt (agent/turn_context.py),
# angehaengt (agent/turn_context.py), NUR wenn die task_id ein echtes Kanban-`t_<hex>` # NUR wenn die Session in einem Kanban-Task-Workspace laeuft. Lucys interaktiver/Voice-Chat
# ist. Lucys interaktiver/Voice-Chat laeuft mit einer UUID-Ersatz-task_id (nicht `t_...`) # laeuft NICHT dort → sofortiger No-op, kein Persona- oder Latenz-Eingriff. Loest
# → sofortiger No-op, kein Persona- oder Latenz-Eingriff. Loest Fallstrick #1 (Ops/Mess/Verify-Worker landen auf der am # Fallstrick #1 (Ops/Mess/Verify-Worker flailen, weil sie llama-swap :8080 nicht kennen).
# schlechtesten geruesteten Persona und flailen, weil sie llama-swap :8080 nicht kennen).
# Ausgabe {"context":"..."} = anhaengen; {} = durchlassen (No-op). # Ausgabe {"context":"..."} = anhaengen; {} = durchlassen (No-op).
set -u set -u
payload="$(cat -)" payload="$(cat -)"
# Scope-Gate: NUR echte Kanban-Worker. ACHTUNG (agent/turn_context.py:216): # Scope-Gate: NUR echte Kanban-Worker — erkannt an der cwd im Task-Workspace.
# Hermes setzt effective_task_id = task_id ODER — fuer Lucy-Chat/Voice/CLI/Telegram — # ACHTUNG (Bug-Fund Faden 8, 13.07.): Worker tragen als task_id den SESSION-Zeitstempel
# eine zufaellige UUID. Ein blosser "nicht leer"-Check wuerde also auch Lucy treffen # (z.B. 20260713_224206_267304), NICHT die Kanban-t_-ID — ein "nur t_"-Check feuerte also
# (Persona-/Latenz-Schaden). Kanban-IDs sind IMMER `t_<hex>`, UUIDs enthalten Bindestriche # NIE fuer Worker. Der zuverlaessige Signal ist die cwd unter kanban/workspaces/; Lucy/
# und beginnen nie mit t_ → das Muster trennt sauber. Alles andere: sofortiger No-op # Voice/CLI laufen nie dort.
# (ein jq, im Voice-Pfad vernachlaessigbar). cwd=$(printf %s "$payload" | jq -r '.cwd // ""' 2>/dev/null || echo "")
task_id=$(printf %s "$payload" | jq -r '.extra.task_id // .task_id // ""' 2>/dev/null || echo "") case "$cwd" in
case "$task_id" in "$HOME/.hermes/kanban/workspaces/"*) : ;; # Kanban-Worker → Orientierung anhaengen
t_[0-9a-f][0-9a-f][0-9a-f][0-9a-f]*) : ;; # Kanban-Worker → Orientierung anhaengen *) printf '{}'; exit 0 ;; # Lucy / Voice / CLI / interaktiv → No-op
*) printf '{}'; exit 0 ;; # Lucy / UUID / leer → No-op
esac esac
# Zeiger auf den vollen, naechtlich generierten Selbst-Steckbrief (nur wenn er existiert). # Zeiger auf den vollen, naechtlich generierten Selbst-Steckbrief (nur wenn er existiert).
+15 -17
View File
@@ -1,15 +1,20 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""pre_tool_call-Guard (Faden 8, 13.07.2026): sperrt Schreibzugriffe von Kanban-Workern """pre_tool_call-Guard (Faden 8, 13.07.2026): sperrt Schreibzugriffe auf den LIVE-Checkout
auf den LIVE-Checkout ~/mission-control-v2 und die Hermes-QUELLE ~/.hermes/hermes-agent ~/mission-control-v2 und die Hermes-QUELLE ~/.hermes/hermes-agent HART. Loest Fallstrick #2
HART - egal welches Profil. Nur fuer echte Worker (task_id = t_<hex>); Lucy/Wartung/ (Worker wandert in die falsche Ebene / lag Patches im Live-Checkout ab). Die native
interaktiv/CLI (UUID-task_id) = sofortiger No-op. Loest Fallstrick #2 (Worker wandert in is_write_denied deckt diese Pfade NICHT ab (nur Credentials/System) -> diese Luecke.
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.
WICHTIG: Der Worker KLONT MC2 in seinen Workspace (<workspace>/mission-control-v2) und UNBEDINGT (kein Task-/Profil-Scope): KEIN legitimer Hermes-Agent-Flow schreibt je in diese
arbeitet DORT legitim. Gesperrt wird deshalb NUR der home-verankerte Live-Checkout zwei Pfade — der Werkstatt-Worker arbeitet im WORKSPACE-Klon (<workspace>/mission-control-v2,
($HOME/mission-control-v2), NIE der blosse Name "mission-control-v2". Lesen ist erlaubt - NICHT home-verankert -> erlaubt), Lucy fasst MC2-Quelle nie an, und die Wartungs-Pipeline
geblockt werden nur Schreib-Operationen. (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. Ausgabe {"action":"block","message":...} = Tool geblockt; {} = durchlassen.
""" """
@@ -26,13 +31,6 @@ try:
except Exception: except Exception:
out({}) 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_<hex>-Muster ist ein Worker.
if not re.match(r"^t_[0-9a-f]{4,}$", task_id):
out({})
tool = payload.get("tool_name") or "" tool = payload.get("tool_name") or ""
ti = payload.get("tool_input") or {} ti = payload.get("tool_input") or {}
home = os.path.realpath(os.path.expanduser("~")) home = os.path.realpath(os.path.expanduser("~"))
+6
View File
@@ -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" _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"; } cmp -s "$_src" "$_dst" 2>/dev/null || { cp "$_src" "$_dst"; chmod +x "$_dst"; }
done 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 → # 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 # 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 # kollidieren seine absoluten Pfade (/assets, /api) mit MC2s eigenen. Läuft auch nach jedem
+72
View File
@@ -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/<name>) 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 <config.yaml>")
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)}).")