feat: complete MC2 Kahlschlag (remove governor, memory, eigenleben, obsolete deploy scripts and frontend widgets)
Ampel / ampel (push) Successful in 24s

This commit is contained in:
Hitonabi
2026-08-07 10:59:39 +02:00
parent 5504918a61
commit 1e68f62499
109 changed files with 192 additions and 6392 deletions
-259
View File
@@ -1,259 +0,0 @@
#!/usr/bin/env python3
"""Hermes-Runtime-Patch-Traeger — haelt MC2-eigene Fixes im Hermes-Quellcode
(``~/.hermes/hermes-agent``), damit sie ein ``hermes update`` ueberleben.
Warum: Der Hermes-Agent wird aus git aktualisiert; ein Direkt-Edit der Laufzeit
wuerde beim naechsten Update still ueberschrieben. Dieses Skript wird von
``deploy/deploy.sh`` UND ``deploy/hermes-postcheck.sh`` aufgerufen und traegt die
Patches (re-)ein.
Mechanik: JEDER Patch ist eine idempotente String-Ersetzung (robust gegen
Zeilenverschiebungen durch Updates, anders als ``git apply``):
* ``marker`` schon im Quelltext -> Patch bereits drin, ueberspringen.
* ``old`` genau 1x vorhanden -> ersetzen, dann ``py_compile`` pruefen.
* ``old`` 0x oder >1x -> Kontext hat sich durch ein Update
geaendert -> LAUT scheitern (Exit 1), damit
der Postcheck rot wird und niemand denkt,
der Fix sei noch aktiv.
Bei Fehlschlag: Exit 1 (der Deploy/Postcheck behandelt das als Fehler).
"""
import os
import py_compile
import shutil
import sys
from pathlib import Path
HA = Path(os.environ.get("HERMES_AGENT_DIR", str(Path.home() / ".hermes" / "hermes-agent")))
HERE = Path(__file__).resolve().parent
# --- Zusatzmodule, die in die Hermes-Laufzeit kopiert werden (update-fest) ---
# (source relativ zu diesem Skript, dest relativ zum Hermes-Agent-Dir)
COPIES = [
("mc2_kanban_reaper.py", "hermes_cli/mc2_kanban_reaper.py"),
]
# --- Patch-Definitionen (chronologisch, Fan-out-/Muenz-Schleifen-Fixes 20.07.2026) ---
PATCHES = [
{
"name": "0001-needs-input-wait",
"file": "hermes_cli/kanban_db.py",
"marker": 'kind not in ("needs_input", "capability")',
"old": " if recurrences >= BLOCK_RECURRENCE_LIMIT:\n",
"new": (
" # MC2-Patch (20.07.2026): needs_input/capability sind genuin\n"
" # human-only — sie muessen in ``blocked`` auf den Commander warten\n"
" # (Antwort via Auftragsbuch), NICHT nach triage eskalieren. Sonst\n"
" # zerlegt der Auto-Decomposer sie neu = Duplikat-Karten-Schleife\n"
' # ("Muenz-Schleife", Fan-out-Vorfall 20.07.). Der Loop-Breaker bleibt\n'
" # fuer transient/untyped Blocks aktiv.\n"
' if recurrences >= BLOCK_RECURRENCE_LIMIT and kind not in ("needs_input", "capability"):\n'
),
},
{
"name": "0002-decompose-at-readiness",
"file": "hermes_cli/kanban_decompose.py",
"marker": "Startreife-Gate",
"old": (
" cfg = _load_config()\n"
" orchestrator = _resolve_orchestrator_profile(cfg)\n"
),
"new": (
" # MC2-Patch (20.07.2026): Startreife-Gate. Eine triage-Karte mit noch\n"
" # OFFENEN Vorgaengern NICHT zerlegen — sonst raet der Decomposer die\n"
" # Schnittstellen der Vor-Etappe ins Blaue, bevor die gebaut hat\n"
" # (Fan-out-Vorfall 20.07.). Billiger Parent-Check VOR dem teuren\n"
" # LLM-Aufruf; erst zerlegen, wenn alle Eltern done/archived.\n"
" with kb.connect_closing() as _pconn:\n"
" _parents = [r[0] for r in _pconn.execute(\n"
' "SELECT parent_id FROM task_links WHERE child_id = ?", (task_id,))]\n'
" if _parents:\n"
' _ph = ",".join("?" * len(_parents))\n'
" _open = _pconn.execute(\n"
' "SELECT COUNT(*) FROM tasks WHERE id IN (%s) "\n'
" \"AND status NOT IN ('done', 'archived')\" % _ph, _parents\n"
" ).fetchone()[0]\n"
" if _open:\n"
" return DecomposeOutcome(\n"
" task_id, False,\n"
' f"defer: {_open} unfinished parent(s) — decompose when start-ready",\n'
" )\n"
" cfg = _load_config()\n"
" orchestrator = _resolve_orchestrator_profile(cfg)\n"
),
},
{
"name": "0003-orphan-guard",
"file": "gateway/kanban_watchers.py",
"marker": "mc2_kanban_reaper",
"old": (
" pids = await asyncio.to_thread(_kb.reap_worker_zombies)\n"
" if pids:\n"
" logger.info(\n"
' "kanban dispatcher: reaped %d zombie worker(s), pids=%s",\n'
" len(pids),\n"
" pids,\n"
" )\n"
),
"new": (
" pids = await asyncio.to_thread(_kb.reap_worker_zombies)\n"
" if pids:\n"
" logger.info(\n"
' "kanban dispatcher: reaped %d zombie worker(s), pids=%s",\n'
" len(pids),\n"
" pids,\n"
" )\n"
" # MC2-Patch (20.07.2026): Orphan-Guard. Beende lebende\n"
" # Worker, deren Task nicht mehr 'running' ist (extern\n"
" # blockiert/reset) — sonst unterlaufen sie max_in_progress\n"
" # und verbrennen Rechenzeit.\n"
" try:\n"
" from hermes_cli import mc2_kanban_reaper as _mc2reaper\n"
" _orph = await asyncio.to_thread(\n"
" _mc2reaper.reap_orphaned_workers, _kb.connect_closing\n"
" )\n"
" if _orph:\n"
" logger.info(\n"
' "kanban dispatcher: reaped %d orphaned worker(s): %s",\n'
" len(_orph), _orph,\n"
" )\n"
" except Exception:\n"
' logger.exception("kanban dispatcher: orphan reaper failed")\n'
),
},
{
"name": "0004a-context-inherit-compute",
"file": "hermes_cli/kanban_decompose.py",
"marker": '_parent_ctx = ""',
"old": (
" children: list[dict] = []\n"
" for idx, entry in enumerate(raw_tasks):\n"
),
"new": (
" children: list[dict] = []\n"
" # MC2-Patch (20.07.2026): Projekt-/Repo-Kontext vererben. Der Parent-Body\n"
' # traegt oft harte Randbedingungen (Ziel-Repo, Tabus, "eigenes Projekt"),\n'
" # die bei der Zerlegung sonst verloren gehen (Worker klonte das falsche\n"
" # Repo, Fan-out-Vorfall 20.07.). Jeder Kind-Body bekommt den voran.\n"
' _parent_ctx = ""\n'
' if getattr(task, "body", None) and task.body.strip():\n'
" _parent_ctx = (\n"
' "## PROJEKT-KONTEXT (von der uebergeordneten Aufgabe — gilt fuer dich)\\n"\n'
" + task.body.strip()[:1500]\n"
' + "\\n\\n## DEIN TEILAUFTRAG\\n"\n'
" )\n"
" for idx, entry in enumerate(raw_tasks):\n"
),
},
{
"name": "0004b-context-inherit-apply",
"file": "hermes_cli/kanban_decompose.py",
"marker": "_parent_ctx + body",
"old": (
' body = entry.get("body")\n'
" if not isinstance(body, str):\n"
' body = ""\n'
),
"new": (
' body = entry.get("body")\n'
" if not isinstance(body, str):\n"
' body = ""\n'
" body = _parent_ctx + body # MC2 (20.07.): Projekt-Kontext vererben\n"
),
},
{
"name": "0005-create-chain-guard",
"file": "tools/kanban_tools.py",
"marker": "_MC2_RECENT_ROOTS",
"old": (
" new_task = kb.get_task(conn, new_tid)\n"
" subscribed = _maybe_auto_subscribe(conn, new_tid)\n"
),
"new": (
" # MC2-Patch (20.07.2026): Etappen-Auto-Kette. Legt dieselbe\n"
" # Session mehrere ROOT-triage-Karten (ohne parents) im selben\n"
" # 120s-Fenster an, verkette sie — so greift das Startreife-Gate\n"
" # (Karte 2 wartet auf Karte 1) statt alle sofort zu zerlegen\n"
" # (Fan-out 20.07.). Unter max_in_progress:1 ist eine Fehl-\n"
" # Verkettung harmlos (laeuft eh seriell).\n"
" try:\n"
" if triage and not parents and session_id:\n"
" import time as _mc2t\n"
' _roots = globals().setdefault("_MC2_RECENT_ROOTS", {})\n'
" _prev = _roots.get(session_id)\n"
" _now = _mc2t.monotonic()\n"
" if _prev and (_now - _prev[1]) <= 120 and _prev[0] != new_tid:\n"
" try:\n"
" kb.link_tasks(conn, _prev[0], new_tid)\n"
" except Exception:\n"
" pass\n"
" _roots[session_id] = (new_tid, _now)\n"
" if len(_roots) > 64:\n"
" _roots.clear()\n"
" except Exception:\n"
" pass\n"
" new_task = kb.get_task(conn, new_tid)\n"
" subscribed = _maybe_auto_subscribe(conn, new_tid)\n"
),
},
]
def main() -> int:
if not HA.is_dir():
print(f"hermes-patches: Hermes-Agent-Dir fehlt ({HA}) — nichts zu tun.")
return 0
applied, skipped, failed = [], [], []
# --- Zusatzmodule kopieren (idempotent: nur wenn Inhalt abweicht) ---
for src_rel, dst_rel in COPIES:
src, dst = HERE / src_rel, HA / dst_rel
if not src.is_file():
failed.append((src_rel, f"Quelle fehlt: {src}"))
continue
try:
same = dst.is_file() and dst.read_bytes() == src.read_bytes()
if same:
skipped.append(f"copy:{dst_rel}")
continue
dst.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(src, dst)
if dst.suffix == ".py":
py_compile.compile(str(dst), doraise=True)
applied.append(f"copy:{dst_rel}")
except Exception as exc:
failed.append((src_rel, f"Kopie/Compile fehlgeschlagen: {exc}"))
for p in PATCHES:
f = HA / p["file"]
if not f.is_file():
failed.append((p["name"], f"Datei fehlt: {f}"))
continue
src = f.read_text(encoding="utf-8")
if p["marker"] in src:
skipped.append(p["name"])
continue
n = src.count(p["old"])
if n != 1:
failed.append((p["name"], (f"erwartete 1 Vorkommen von old, fand {n} "
"(Update hat den Kontext geaendert?)")))
continue
f.write_text(src.replace(p["old"], p["new"]), encoding="utf-8")
try:
py_compile.compile(str(f), doraise=True)
except Exception as exc:
failed.append((p["name"], f"py_compile fehlgeschlagen: {exc}"))
continue
applied.append(p["name"])
print(f"hermes-patches: applied={applied} skipped={skipped} failed={[n for n, _ in failed]}")
for name, why in failed:
print(f" FEHLER {name}: {why}", file=sys.stderr)
if failed:
return 1 # Fehler → Deploy/Postcheck rot
if applied:
return 10 # etwas geaendert → Aufrufer sollte hermes-gateway neu starten
return 0 # nichts zu tun (alles schon drin)
if __name__ == "__main__":
sys.exit(main())