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:
+2
-1
@@ -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
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|||||||
@@ -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("~"))
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)}).")
|
||||||
Reference in New Issue
Block a user