4 Commits

Author SHA1 Message Date
Hitonabi abc8a365d2 Review-Haertung R1/R2/R4 + Repo-Waechter (Vorfall verlorener Merge 12.07.)
R1: Steckbrief-Hook erinnert Worker an kanban_complete/kanban_block-Pflicht
    (Nacht-Karte 14.07. blockierte trotz fertiger Arbeit am fehlenden Aufruf).
R2: tabu-pfade-guard sperrt ~/.config/systemd/user fuer Worker — Unit-/
    Override-Dateien nur noch ueber angenommene Karte.
R4: Orchestrator-/Wartungs-Worktrees von /tmp nach ~/.hermes/worktrees
    (ueberleben Reboot, keine kaputten Registrierungen).
Neu: self-smoke Check 4 'Repo-Waechter' — Box-main mit Commits, die origin
    fehlen, loest Alarm aus (so ging die angenommene Karte
    wartung/lucy-annahme-fixes am 12.07. still verloren; am 15.07. als
    rescue/-Branch gerettet und wieder gemergt).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 10:04:16 +02:00
Hitonabi 1cd492eb33 Merge branches 'wartung/bild-weiche-v2' and 'doku/umbauplan-abloesung' 2026-07-15 10:03:18 +02:00
Hitonabi 3abc133118 Umbauplan Abschluss-Review 15.07.: IST-Befund, Konfig-Urteil, priorisierte Arbeitsliste
Komplettes Projekt-Review (Repo+Box live+Recherche). Enthaelt Sofort-Paket
(Klicks+sudo-Runde), Robustheit/Aufraeumen/Features/Tempo-Karten und die
Uebergabe-Logik fuer die Zeit nach Claude.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 09:52:11 +02:00
Hitonabi 0354ce999f Bild-Weiche v2: Coder-Ziele bekommen Vision-BESCHREIBUNG statt Umleitung
Idee aus verwaistem (nie verdrahtetem) Patch in der Hermes-Quelle
api_server.py - regelkonform in den MC2-Gateway umgezogen, Original
als docs/archiv/hermes-api_server-vision-patch-verwaist.diff archiviert.
Request mit Bild an coder: Bilder werden vorab von VL-30B beschrieben
und als Text injiziert, die Code-Frage bleibt beim Spezialisten.
Fallback bei Analyse-Fehler: bisherige Vision-Umleitung.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 09:46:36 +02:00
9 changed files with 385 additions and 30 deletions
+69 -8
View File
@@ -5,7 +5,7 @@ from fastapi.responses import JSONResponse, StreamingResponse
from config import LLAMA_SWAP_URL from config import LLAMA_SWAP_URL
from services.gateway_stream import record_stream_chunk, record_usage from services.gateway_stream import record_stream_chunk, record_usage
from services.router_logic import VISION_CAPABLE, choose_for_lane, has_image from services.router_logic import IMAGE_PART_TYPES, VISION_CAPABLE, choose_for_lane, has_image
from services.routing_policy import load_policy from services.routing_policy import load_policy
router = APIRouter(prefix="/v1") router = APIRouter(prefix="/v1")
@@ -19,6 +19,61 @@ _LANG_DIRECTIVE = os.environ.get(
"Quellcode, Bezeichner und Shell-Befehle bleiben unverändert.") "Quellcode, Bezeichner und Shell-Befehle bleiben unverändert.")
# Bild-Beschreibung für Coder-Ziele: Prompt bewusst auf wörtliche Wiedergabe von
# Code/Fehlermeldungen getrimmt — der Coder arbeitet nur mit diesem Text weiter.
_BILD_BESCHREIB_PROMPT = os.environ.get(
"MC_CODER_IMAGE_PROMPT",
"Beschreibe dieses Bild vollstaendig und praezise auf Deutsch: sichtbarer Text, "
"Code, Zahlen/Daten, UI-Elemente, Layout, Farben und alles Auffaellige. "
"Sichtbaren Code und Fehlermeldungen gib WOERTLICH wieder.")
def _ist_coder_alias(alias: str) -> bool:
"""Coder-Ziele (coder, rohe Qwen-Coder-IDs) bekommen Bild-BESCHREIBUNGEN statt der
stumpfen Vision-Umleitung — die Code-Frage bleibt beim Spezialisten."""
return "coder" in (alias or "").lower()
async def _bilder_fuer_coder_beschreiben(body: dict, client, vision_alias: str) -> bool:
"""Ersetzt jeden Bild-Part durch eine Text-Beschreibung vom Vision-Modell.
Idee aus einem verwaisten, nie verdrahteten Patch in der Hermes-Quelle
(gateway/platforms/api_server.py, 07/2026) — bei der Projekt-Review 15.07.
regelkonform hierher umgezogen (Archiv: docs/archiv/). True = alle Bilder
ersetzt; False = eine Analyse scheiterte, Aufrufer nutzt die normale
Vision-Umleitung als Fallback.
"""
fundstellen: list[tuple[dict, int, dict]] = []
for m in body.get("messages") or []:
if not isinstance(m, dict) or not isinstance(m.get("content"), list):
continue
for i, part in enumerate(m["content"]):
if isinstance(part, dict) and part.get("type") in IMAGE_PART_TYPES:
fundstellen.append((m, i, part))
for nr, (msg, idx, part) in enumerate(fundstellen, start=1):
frage = {
"model": vision_alias,
"stream": False,
"max_tokens": 700,
"messages": [{"role": "user",
"content": [part, {"type": "text", "text": _BILD_BESCHREIB_PROMPT}]}],
}
try:
# Grosszuegiger Timeout: VL-30B ist on-demand (Kaltladen ~30 s) + Beschreibung ~25 s.
r = await client.post(f"{LLAMA_SWAP_URL}/v1/chat/completions", json=frage, timeout=240.0)
text = ""
if r.status_code == 200:
text = ((r.json().get("choices") or [{}])[0].get("message") or {}).get("content") or ""
if not text.strip():
return False
except Exception:
return False
msg["content"][idx] = {"type": "text", "text": (
f"[Bild {nr}: dem Request lag ein Bild bei — {vision_alias} beschreibt es so:\n"
f"{text.strip()}]")}
return True
def _inject_language(body: dict, alias: str) -> None: def _inject_language(body: dict, alias: str) -> None:
"""Deutsch-Direktive anhängen. An die ERSTE System-Message (viele Chat-Templates """Deutsch-Direktive anhängen. An die ERSTE System-Message (viele Chat-Templates
erwarten nur eine), sonst als neue System-Message. `hermes` ausgenommen — Lucys erwarten nur eine), sonst als neue System-Message. `hermes` ausgenommen — Lucys
@@ -101,23 +156,29 @@ async def _proxy(path: str, request: Request):
routed = {"x-mc-routed-to": requested} routed = {"x-mc-routed-to": requested}
pol = load_policy() pol = load_policy()
client = request.app.state.gw_client # geteilter Keep-Alive-Client (siehe app.py lifespan)
# Bild-Weiche (Faden 11): Requests mit Bild-Anhang automatisch ans Vision-Modell umleiten, # Bild-Weiche (Faden 11): Requests mit Bild-Anhang automatisch ans Vision-Modell umleiten,
# sofern das Ziel nicht ohnehin bildfähig ist. Das MTP-Hirn (fast/hermes) kann keine Bilder — # sofern das Ziel nicht ohnehin bildfähig ist. Das MTP-Hirn (fast/hermes) kann keine Bilder —
# so bleibt Lucy schnell, ohne dass jemand manuell das Modell wechselt (Entscheid Weg A). # so bleibt Lucy schnell, ohne dass jemand manuell das Modell wechselt (Entscheid Weg A).
# Coder-Sonderweg (15.07.): Coder-Ziele behalten den Request — die Bilder werden vorab vom
# Vision-Modell BESCHRIEBEN und als Text injiziert (Screenshot-Debugging bleibt beim Coder).
vision_alias = pol.get("vision") vision_alias = pol.get("vision")
if vision_alias and alias not in VISION_CAPABLE and has_image(body): if vision_alias and alias not in VISION_CAPABLE and has_image(body):
alias = vision_alias if _ist_coder_alias(alias) and await _bilder_fuer_coder_beschreiben(body, client, vision_alias):
body["model"] = alias routed = {"x-mc-routed-to": alias,
# HTTP-Header-Werte muessen latin-1 sein — kein '→' o.ae. (sonst 500, 13.07.). "x-mc-route-reason": "Bild beschrieben (Vision) -> bleibt beim Coder",
routed = {"x-mc-routed-to": alias, "x-mc-route-reason": "Bild erkannt -> Vision-Modell", "x-mc-lane": routed.get("x-mc-lane", "-")}
"x-mc-lane": routed.get("x-mc-lane", "-")} else:
alias = vision_alias
body["model"] = alias
# HTTP-Header-Werte muessen latin-1 sein — kein '→' o.ae. (sonst 500, 13.07.).
routed = {"x-mc-routed-to": alias, "x-mc-route-reason": "Bild erkannt -> Vision-Modell",
"x-mc-lane": routed.get("x-mc-lane", "-")}
# fast-Spur: Thinking aus für flotte Antworten (sofern Client es nicht selbst setzt). # fast-Spur: Thinking aus für flotte Antworten (sofern Client es nicht selbst setzt).
if pol["fast_no_think"] and alias == pol["fast"] and "chat_template_kwargs" not in body: if pol["fast_no_think"] and alias == pol["fast"] and "chat_template_kwargs" not in body:
body["chat_template_kwargs"] = {"enable_thinking": False} body["chat_template_kwargs"] = {"enable_thinking": False}
_inject_language(body, alias) _inject_language(body, alias)
url = f"{LLAMA_SWAP_URL}{path}" url = f"{LLAMA_SWAP_URL}{path}"
client = request.app.state.gw_client # geteilter Keep-Alive-Client (siehe app.py lifespan)
if body.get("stream"): if body.get("stream"):
req = client.build_request("POST", url, json=body, timeout=None) req = client.build_request("POST", url, json=body, timeout=None)
r = await client.send(req, stream=True) r = await client.send(req, stream=True)
+2 -2
View File
@@ -52,7 +52,7 @@ _CODE_HINT = re.compile(
# Bild-Weiche (Faden 11): Aliase, die selbst Bilder koennen — die werden NIE umgeroutet. # Bild-Weiche (Faden 11): Aliase, die selbst Bilder koennen — die werden NIE umgeroutet.
VISION_CAPABLE = {"vision", "scout"} VISION_CAPABLE = {"vision", "scout"}
_IMAGE_PART_TYPES = {"image_url", "input_image", "image"} IMAGE_PART_TYPES = {"image_url", "input_image", "image"}
def has_image(body: dict) -> bool: def has_image(body: dict) -> bool:
@@ -64,7 +64,7 @@ def has_image(body: dict) -> bool:
content = m.get("content") content = m.get("content")
if isinstance(content, list): if isinstance(content, list):
for part in content: for part in content:
if isinstance(part, dict) and part.get("type") in _IMAGE_PART_TYPES: if isinstance(part, dict) and part.get("type") in IMAGE_PART_TYPES:
return True return True
return False return False
@@ -29,6 +29,7 @@ hint=""
ctx="$(cat <<EOF ctx="$(cat <<EOF
[BOX-ORIENTIERUNG — du bist ein Hintergrund-Worker auf der lokalen AI-Box (Linux, Zeit UTC / lokal Europe/Berlin, locale de_DE), NICHT Lucy und kein Dev-Laptop. Du arbeitest EINE Kanban-Aufgabe ab.] [BOX-ORIENTIERUNG — du bist ein Hintergrund-Worker auf der lokalen AI-Box (Linux, Zeit UTC / lokal Europe/Berlin, locale de_DE), NICHT Lucy und kein Dev-Laptop. Du arbeitest EINE Kanban-Aufgabe ab.]
Die Modelle / das "Hirn" laufen ausschliesslich ueber den Router llama-swap auf 127.0.0.1:8080 (startet je Modell einen llama-server) bzw. das OpenAI-kompatible Gateway von MC2 auf 127.0.0.1:9001/v1 — es gibt KEINEN anderen Modell-Server, also nicht danach suchen. Weitere Dienste: hermes-gateway :8642, mem0-Gedaechtnis :8765. Der MC2-Live-Checkout ~/mission-control-v2 ist nur zum LESEN da (Schreiben/committen TABU — Code-Arbeit immer im frischen Klon deines Task-Workspaces). Grenzen (was gehoert wohin): MC2 = Steuerpult (verwalten/klicken/zusehen, KEIN Chat-UI), Lucy = Chat & Stimme (eigenes Repo), Hermes-Quelle ist TABU (Agenten-Verhalten nur ueber Config/SOUL/Skill/Hook/MCP aendern). Feste Entscheide/Verbote + volle Grenzen-Landkarte: ~/mission-control-v2/docs/wissen/ (GRENZEN/VERDIKTE/FALLEN/STACK) + ~/wissens-vault/eigenbau-landkarte.md.${hint} Die Modelle / das "Hirn" laufen ausschliesslich ueber den Router llama-swap auf 127.0.0.1:8080 (startet je Modell einen llama-server) bzw. das OpenAI-kompatible Gateway von MC2 auf 127.0.0.1:9001/v1 — es gibt KEINEN anderen Modell-Server, also nicht danach suchen. Weitere Dienste: hermes-gateway :8642, mem0-Gedaechtnis :8765. Der MC2-Live-Checkout ~/mission-control-v2 ist nur zum LESEN da (Schreiben/committen TABU — Code-Arbeit immer im frischen Klon deines Task-Workspaces). Grenzen (was gehoert wohin): MC2 = Steuerpult (verwalten/klicken/zusehen, KEIN Chat-UI), Lucy = Chat & Stimme (eigenes Repo), Hermes-Quelle ist TABU (Agenten-Verhalten nur ueber Config/SOUL/Skill/Hook/MCP aendern). Feste Entscheide/Verbote + volle Grenzen-Landkarte: ~/mission-control-v2/docs/wissen/ (GRENZEN/VERDIKTE/FALLEN/STACK) + ~/wissens-vault/eigenbau-landkarte.md.${hint}
PROTOKOLL-PFLICHT (15.07.): Beende deine Aufgabe IMMER mit dem Tool-Aufruf kanban_complete (fertig) oder kanban_block (Frage/Sackgasse). Eine Text-Zusammenfassung OHNE diesen Aufruf zaehlt als Absturz (Protokollverstoss) und blockiert die Karte — genau so ging am 14.07. eine fertige Arbeit verloren.
EOF EOF
)" )"
+12 -6
View File
@@ -36,13 +36,18 @@ ti = payload.get("tool_input") or {}
home = os.path.realpath(os.path.expanduser("~")) home = os.path.realpath(os.path.expanduser("~"))
LIVE = os.path.join(home, "mission-control-v2") LIVE = os.path.join(home, "mission-control-v2")
SRC = os.path.join(home, ".hermes", "hermes-agent") 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 = ( MSG = (
"TABU (Faden 8): Schreiben an dieser Stelle ist fuer Worker gesperrt. Der Live-Checkout " "TABU (Faden 8/R2): Schreiben an dieser Stelle ist fuer Worker gesperrt. Der Live-Checkout "
"~/mission-control-v2 und die Hermes-Quelle ~/.hermes/hermes-agent duerfen NIE veraendert " "~/mission-control-v2, die Hermes-Quelle ~/.hermes/hermes-agent und die systemd-User-Units "
"werden. Arbeite im FRISCHEN Klon deines Task-Workspaces; MC2-Aenderungen kommen als " "~/.config/systemd/user duerfen NIE direkt veraendert werden. Arbeite im FRISCHEN Klon "
"Vorschlags-Branch (nie am Live-Checkout), Hermes-Verhalten nur ueber Config/SOUL/Skill/" "deines Task-Workspaces; MC2-Aenderungen kommen als Vorschlags-Branch, systemd-Unit-/"
"Hook/MCP. Siehe docs/wissen/GRENZEN.md." "Override-Dateien werden nur ueber eine angenommene Karte installiert, Hermes-Verhalten "
"nur ueber Config/SOUL/Skill/Hook/MCP. Siehe docs/wissen/GRENZEN.md."
) )
@@ -52,7 +57,7 @@ def block():
def under_tabu(path): def under_tabu(path):
rp = os.path.realpath(path) rp = os.path.realpath(path)
return rp == LIVE or rp == SRC or rp.startswith(LIVE + os.sep) or rp.startswith(SRC + os.sep) return any(rp == t or rp.startswith(t + os.sep) for t in (LIVE, SRC, SYSD))
# --- Datei-Tools: Zielpfad kanonisch pruefen (zuverlaessig) ------------------------- # --- Datei-Tools: Zielpfad kanonisch pruefen (zuverlaessig) -------------------------
@@ -78,6 +83,7 @@ if tool == "terminal":
tabu_forms = [ tabu_forms = [
LIVE, "~/mission-control-v2", "$HOME/mission-control-v2", "${HOME}/mission-control-v2", LIVE, "~/mission-control-v2", "$HOME/mission-control-v2", "${HOME}/mission-control-v2",
SRC, "~/.hermes/hermes-agent", "$HOME/.hermes/hermes-agent", "${HOME}/.hermes/hermes-agent", SRC, "~/.hermes/hermes-agent", "$HOME/.hermes/hermes-agent", "${HOME}/.hermes/hermes-agent",
SYSD, "~/.config/systemd/user", "$HOME/.config/systemd/user", "${HOME}/.config/systemd/user",
] ]
tabu_alt = "(?:" + "|".join(re.escape(t) for t in tabu_forms) + ")" tabu_alt = "(?:" + "|".join(re.escape(t) for t in tabu_forms) + ")"
if not re.search(tabu_alt, cmd): if not re.search(tabu_alt, cmd):
+19
View File
@@ -83,6 +83,25 @@ else
FAILED+=("Voice (Lucys Sprech-Pfad)") FAILED+=("Voice (Lucys Sprech-Pfad)")
fi 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
# ── Meldung nur bei Rot ────────────────────────────────────────────────────── # ── Meldung nur bei Rot ──────────────────────────────────────────────────────
if [ "${#FAILED[@]}" -eq 0 ]; then if [ "${#FAILED[@]}" -eq 0 ]; then
say "Alle Selbst-Tests grün ($(date '+%F %H:%M'))." say "Alle Selbst-Tests grün ($(date '+%F %H:%M'))."
+13 -11
View File
@@ -86,10 +86,12 @@ Bevor du selbst Tasks aufteilst, befragst du zwingend **`gpt-oss-120b`** nach ei
3. Fuer jeden Teil-Task entscheide: (a) Artefakt, (b) Worker (`Qwen3-Coder-Next` + `build`/`refactor`), (c) Kontext. 3. Fuer jeden Teil-Task entscheide: (a) Artefakt, (b) Worker (`Qwen3-Coder-Next` + `build`/`refactor`), (c) Kontext.
### 2. Worktree anlegen (Slug = kurzer Kebab-Case-Name des Auftrags) ### 2. Worktree anlegen (Slug = kurzer Kebab-Case-Name des Auftrags)
Worktrees liegen unter `~/.hermes/worktrees/` (NICHT /tmp — ueberlebt keinen Reboot und
hinterlaesst kaputte Worktree-Registrierungen, Review-Befund 15.07.):
``` ```
cd ~/mission-control-v2 && git fetch -q origin \ mkdir -p ~/.hermes/worktrees && cd ~/mission-control-v2 && git fetch -q origin \
&& git worktree add /tmp/orch-<slug> -b orchestrator/<slug> origin/main && git worktree add ~/.hermes/worktrees/orch-<slug> -b orchestrator/<slug> origin/main
mkdir -p /tmp/orch-<slug>-work # Ablage fuer rohe Worker-Ausgaben mkdir -p ~/.hermes/worktrees/orch-<slug>-work # Ablage fuer rohe Worker-Ausgaben
``` ```
### 3. Routen — je Teil-Task ein Worker-Ein-Schuss (seriell) ### 3. Routen — je Teil-Task ein Worker-Ein-Schuss (seriell)
@@ -98,19 +100,19 @@ Fuer jeden Bau-Teil-Task:
fertige Artefakte aus frueheren Schritten als Kontext mitgeben, wenn der Worker sie braucht. fertige Artefakte aus frueheren Schritten als Kontext mitgeben, wenn der Worker sie braucht.
2. Ruf den Worker auf und fang die Ausgabe roh ab: 2. Ruf den Worker auf und fang die Ausgabe roh ab:
``` ```
printf '%s' "$eingabe" | WORKER_ROLE=build ~/.hermes/scripts/worker.sh > /tmp/orch-<slug>-work/<n>.out printf '%s' "$eingabe" | WORKER_ROLE=build ~/.hermes/scripts/worker.sh > ~/.hermes/worktrees/orch-<slug>-work/<n>.out
``` ```
(Default-Modell ist `Qwen3-Coder-Next`. Fuer einen bewusst anderen Worker `WORKER_MODEL=<echte-id>`.) (Default-Modell ist `Qwen3-Coder-Next`. Fuer einen bewusst anderen Worker `WORKER_MODEL=<echte-id>`.)
3. **Pruefe die Ausgabe, bevor du sie verwendest:** Beginnt sie mit `FEHLT:`, hast du zu wenig 3. **Pruefe die Ausgabe, bevor du sie verwendest:** Beginnt sie mit `FEHLT:`, hast du zu wenig
Kontext gegeben → nachliefern und erneut rufen. Hat der Worker versehentlich einen ```-Codezaun Kontext gegeben → nachliefern und erneut rufen. Hat der Worker versehentlich einen ```-Codezaun
oder Prosa drumherum gesetzt, entferne ihn. Dann schreibe das saubere Artefakt mit deinen oder Prosa drumherum gesetzt, entferne ihn. Dann schreibe das saubere Artefakt mit deinen
Datei-Tools an seinen Platz im Worktree (`/tmp/orch-<slug>/...`). Der Worker hat KEINE Datei-Tools an seinen Platz im Worktree (`~/.hermes/worktrees/orch-<slug>/...`). Der Worker hat KEINE
Datei-Haende — das Schreiben machst DU. Datei-Haende — das Schreiben machst DU.
4. **Schreibziel-Pflicht (Vorfall 09.07.2026):** JEDES write_file/patch-Ziel MUSS mit 4. **Schreibziel-Pflicht (Vorfall 09.07.2026):** JEDES write_file/patch-Ziel MUSS mit
`/tmp/orch-<slug>/` beginnen — vor dem ersten Schreiben einmal laut pruefen. NIEMALS nach `~/.hermes/worktrees/orch-<slug>/` beginnen — vor dem ersten Schreiben einmal laut pruefen. NIEMALS nach
`~/mission-control-v2/...` schreiben (Live-Checkout!), NIEMALS Work-Dirs/Worktrees FREMDER `~/mission-control-v2/...` schreiben (Live-Checkout!), NIEMALS Work-Dirs/Worktrees FREMDER
Slugs wiederverwenden (beim Doku-Lauf landete ein Artefakt im Live-Checkout, weil das alte Slugs wiederverwenden (beim Doku-Lauf landete ein Artefakt im Live-Checkout, weil das alte
`/tmp/orch-hermes-desktop-work` recycelt wurde). Existiert dein `/tmp/orch-<slug>` noch nicht, `/tmp/orch-hermes-desktop-work` recycelt wurde). Existiert dein `~/.hermes/worktrees/orch-<slug>` noch nicht,
ist das der Beweis, dass Schritt 2 fehlt — erst Worktree anlegen. ist das der Beweis, dass Schritt 2 fehlt — erst Worktree anlegen.
5. **Grosse Artefakte NIE im Klartext in deine Antwort** — sie gehoeren in Dateien im Worktree. 5. **Grosse Artefakte NIE im Klartext in deine Antwort** — sie gehoeren in Dateien im Worktree.
Wer Dateiinhalte in die Antwort kippt, stirbt am Output-Limit mitten im Lauf (Doku-Lauf Wer Dateiinhalte in die Antwort kippt, stirbt am Output-Limit mitten im Lauf (Doku-Lauf
@@ -148,7 +150,7 @@ Grund wie in der Werkstatt („wer seine eigenen Hausaufgaben benotet, stimmt si
**Zuerst ALLES stagen** — sonst fehlen NEUE Dateien im Diff (haeufigste Falle: `git diff` ignoriert **Zuerst ALLES stagen** — sonst fehlen NEUE Dateien im Diff (haeufigste Falle: `git diff` ignoriert
untracked Dateien, die Kritiker bekaemen einen LEEREN Diff und wuerden alles blind ablehnen): untracked Dateien, die Kritiker bekaemen einen LEEREN Diff und wuerden alles blind ablehnen):
``` ```
git -C /tmp/orch-<slug> add -A git -C ~/.hermes/worktrees/orch-<slug> add -A
``` ```
Dann die Eingabe EINMAL bauen (mit `--cached`, damit die neuen Dateien drin sind): Dann die Eingabe EINMAL bauen (mit `--cached`, damit die neuen Dateien drin sind):
``` ```
@@ -159,7 +161,7 @@ ZERLEGUNG (deine Teil-Tasks, je 1 Zeile):
<1..n> <1..n>
GIT-DIFF des Worktrees: GIT-DIFF des Worktrees:
$(git -C /tmp/orch-<slug> diff --cached origin/main)" $(git -C ~/.hermes/worktrees/orch-<slug> diff --cached origin/main)"
``` ```
Ist dieser Diff LEER, hast du nicht gestaged (oder nichts gebaut) → NICHT weiter, erst beheben. Ist dieser Diff LEER, hast du nicht gestaged (oder nichts gebaut) → NICHT weiter, erst beheben.
**Kritik A — Kompetenz** (`Qwen3-Coder-Next`, starker Coder, vom Bauen noch geladen → KEIN Swap): **Kritik A — Kompetenz** (`Qwen3-Coder-Next`, starker Coder, vom Bauen noch geladen → KEIN Swap):
@@ -212,7 +214,7 @@ Zugriff aufs Repo. Er sieht NUR, was du ihm auf stdin gibst. Deshalb:
den Fokus. Ziel: das kleinste, das den Teil-Task eindeutig macht. den Fokus. Ziel: das kleinste, das den Teil-Task eindeutig macht.
## Nach dem Commander-Entscheid (kommt als neuer Auftrag) ## Nach dem Commander-Entscheid (kommt als neuer Auftrag)
- „verwerfen" → `git worktree remove /tmp/orch-<slug> --force && git branch -D orchestrator/<slug>` - „verwerfen" → `git worktree remove ~/.hermes/worktrees/orch-<slug> --force && git branch -D orchestrator/<slug>`
(+ `rm -rf /tmp/orch-<slug>-work`; Remote-Branch loeschen, falls gepusht). (+ `rm -rf ~/.hermes/worktrees/orch-<slug>-work`; Remote-Branch loeschen, falls gepusht).
- „merge" → das Mergen nach main + Deploy macht der PC/Claude (Frontend-Builds gibt es nur dort). - „merge" → das Mergen nach main + Deploy macht der PC/Claude (Frontend-Builds gibt es nur dort).
Du pushst NIE nach main. Du pushst NIE nach main.
+4 -3
View File
@@ -42,7 +42,8 @@ unangenommene Branches aufbauen — erst wenn die Vor-Etappe in main ist, kommt
## Ablauf ## Ablauf
1. **Worktree anlegen** (Slug = kurzer Kebab-Case-Name des Auftrags): 1. **Worktree anlegen** (Slug = kurzer Kebab-Case-Name des Auftrags):
`cd ~/mission-control-v2 && git fetch origin && git worktree add /tmp/wartung-<slug> -b wartung/<slug> origin/main` `mkdir -p ~/.hermes/worktrees && cd ~/mission-control-v2 && git fetch origin && git worktree add ~/.hermes/worktrees/wartung-<slug> -b wartung/<slug> origin/main`
(Worktrees NIE unter /tmp — ueberlebt keinen Reboot, Review-Befund 15.07.)
2. **Patch** nur im Worktree. Minimal-invasiv, Stil der umgebenden Datei übernehmen 2. **Patch** nur im Worktree. Minimal-invasiv, Stil der umgebenden Datei übernehmen
(deutsche Kommentare, bestehende Muster). (deutsche Kommentare, bestehende Muster).
3. **Selbst-Gate** (was zutrifft): 3. **Selbst-Gate** (was zutrifft):
@@ -76,7 +77,7 @@ unangenommene Branches aufbauen — erst wenn die Vor-Etappe in main ist, kommt
<der Auftrag> <der Auftrag>
GIT-DIFF des Worktrees: GIT-DIFF des Worktrees:
<git -C /tmp/wartung-<slug> diff origin/main>" | FREMDBLICK_MODE=code ~/.hermes/scripts/fremdblick.sh <git -C ~/.hermes/worktrees/wartung-<slug> diff origin/main>" | FREMDBLICK_MODE=code ~/.hermes/scripts/fremdblick.sh
``` ```
Das läuft auf `Qwen3-Coder-Next` (128k, code-stark, anderes Modell als der Qwen3.6-Worker, Das läuft auf `Qwen3-Coder-Next` (128k, code-stark, anderes Modell als der Qwen3.6-Worker,
@@ -113,7 +114,7 @@ unangenommene Branches aufbauen — erst wenn die Vor-Etappe in main ist, kommt
## Nach dem User-Entscheid (kommt als neuer Auftrag) ## Nach dem User-Entscheid (kommt als neuer Auftrag)
- „verwerfen" → `git worktree remove /tmp/wartung-<slug> --force && git branch -D wartung/<slug>` - „verwerfen" → `git worktree remove ~/.hermes/worktrees/wartung-<slug> --force && git branch -D wartung/<slug>`
(+ Remote-Branch löschen, falls gepusht: `git push origin --delete wartung/<slug>`) (+ Remote-Branch löschen, falls gepusht: `git push origin --delete wartung/<slug>`)
- „merge" → das Mergen nach main + Deploy macht der PC/Claude (Frontend-Builds gibt es - „merge" → das Mergen nach main + Deploy macht der PC/Claude (Frontend-Builds gibt es
nur dort). Du pushst NIE nach main — auch nicht mit Token. nur dort). Du pushst NIE nach main — auch nicht mit Token.
@@ -0,0 +1,164 @@
diff --git a/gateway/platforms/api_server.py b/gateway/platforms/api_server.py
index 628713224..415f3b2dc 100644
--- a/gateway/platforms/api_server.py
+++ b/gateway/platforms/api_server.py
@@ -362,6 +362,159 @@ def _multimodal_validation_error(exc: ValueError, *, param: str) -> "web.Respons
)
+def _is_coder_model(model_name: Optional[str]) -> bool:
+ """Return True if model_name looks like a coder model (case-insensitive 'coder' in name)."""
+ if not model_name:
+ return False
+ return "coder" in str(model_name).lower()
+
+
+def _extract_image_urls_from_messages(messages: List[Dict[str, Any]]) -> List[str]:
+ """Extract all image URLs from OpenAI-style messages payload."""
+ image_urls: List[str] = []
+ for msg in messages:
+ if not isinstance(msg, dict):
+ continue
+ content = msg.get("content")
+ if not content:
+ continue
+ # Normalize multimodal content to list of parts
+ parts: Any = content
+ if isinstance(content, str):
+ continue # text-only message has no images
+ if not isinstance(parts, list):
+ continue
+ for part in parts:
+ if not isinstance(part, dict):
+ continue
+ part_type = str(part.get("type", "")).strip().lower()
+ if part_type in {"image_url", "input_image"}:
+ img_ref = part.get("image_url")
+ if isinstance(img_ref, dict):
+ url = img_ref.get("url")
+ else:
+ url = img_ref
+ if isinstance(url, str) and url.strip():
+ image_urls.append(url.strip())
+ return image_urls
+
+
+async def _maybe_enrich_with_vision_if_coder(
+ body: Dict[str, Any],
+) -> Dict[str, Any]:
+ """
+ For coder model requests with image attachments, pre-analyze images
+ with the vision fallback model and replace image parts with descriptive text.
+
+ Returns a modified copy of body with image URLs replaced by text
+ descriptions in the messages array.
+ """
+ model_name = body.get("model")
+ if not _is_coder_model(model_name):
+ return body
+
+ messages = body.get("messages")
+ if not messages or not isinstance(messages, list):
+ return body
+
+ image_urls = _extract_image_urls_from_messages(messages)
+ if not image_urls:
+ return body
+
+ # Import here to avoid circular dependency issues
+ try:
+ from tools.vision_tools import vision_analyze_tool
+ except ImportError:
+ logger.warning(
+ "Vision tool not available for coder model enrichment. "
+ "Install hermes-agent tools to enable this feature."
+ )
+ return body
+
+ # Use the configured vision model or fall back to VL-30B
+ # We'll let vision_analyze_tool use its own default (auxiliary.vision)
+ # but we can optionally override by passing model parameter
+ # For now, let it auto-resolve via config
+ analysis_prompt = (
+ "Describe everything visible in this image in thorough detail. "
+ "Include any text, code, data, objects, people, layout, colors, "
+ "and any other notable visual information."
+ )
+
+ # Build a mapping of URL -> description
+ url_to_desc: Dict[str, str] = {}
+ for img_url in image_urls:
+ try:
+ result_json = await vision_analyze_tool(
+ image_url=img_url,
+ user_prompt=analysis_prompt,
+ )
+ result = json.loads(result_json)
+ if result.get("success"):
+ desc = result.get("analysis", "")
+ url_to_desc[img_url] = (
+ f"[The user sent an image~ Here's what I can see:\n{desc}]\n"
+ f"[If you need a closer look, use vision_analyze with image_url: {img_url} ~]"
+ )
+ else:
+ url_to_desc[img_url] = (
+ "[The user sent an image but I couldn't quite see it this time (>_<) "
+ f"You can try looking at it yourself with vision_analyze using image_url: {img_url}]"
+ )
+ except Exception as e:
+ logger.error("Vision auto-analysis error for image %s: %s", img_url, e)
+ url_to_desc[img_url] = (
+ f"[The user sent an image but something went wrong when I tried to look at it~ "
+ f"You can try examining it yourself with vision_analyze using image_url: {img_url}]"
+ )
+
+ # Now replace image parts with text in a copy of messages
+ enriched_messages: List[Dict[str, Any]] = []
+ for msg in messages:
+ msg_copy = dict(msg)
+ content = msg_copy.get("content")
+ if isinstance(content, str):
+ enriched_messages.append(msg_copy)
+ continue
+
+ if not isinstance(content, list):
+ enriched_messages.append(msg_copy)
+ continue
+
+ enriched_parts: List[Any] = []
+ for part in content:
+ if not isinstance(part, dict):
+ enriched_parts.append(part)
+ continue
+
+ part_type = str(part.get("type", "")).strip().lower()
+ if part_type in {"image_url", "input_image"}:
+ img_ref = part.get("image_url")
+ if isinstance(img_ref, dict):
+ img_url = img_ref.get("url")
+ else:
+ img_url = img_ref
+ if isinstance(img_url, str) and img_url in url_to_desc:
+ # Replace image part with text part
+ enriched_parts.append({
+ "type": "text",
+ "text": url_to_desc[img_url],
+ })
+ else:
+ # Keep original if URL not found (shouldn't happen)
+ enriched_parts.append(part)
+ else:
+ enriched_parts.append(part)
+
+ msg_copy["content"] = enriched_parts
+ enriched_messages.append(msg_copy)
+
+ # Return modified body
+ body_copy = dict(body)
+ body_copy["messages"] = enriched_messages
+ return body_copy
+
+
def _session_chat_user_message(body: Dict[str, Any], *, param: str = "message") -> tuple[Any, Optional["web.Response"]]:
"""Parse and normalize session chat ``message`` / ``input`` like chat completions."""
user_message = body.get("message") or body.get("input")
+101
View File
@@ -0,0 +1,101 @@
# UMBAUPLAN — Abschluss-Review 15.07.2026
**Was das ist:** Ergebnis des kompletten Projekt-Reviews (Repo + Box live + tagesaktuelle
Recherche). DIE Arbeitsliste für die Zeit nach Claude. Leser: der User, die Box-Worker
(über Queue-Karten) und Gemini/Antigravity (liest F:\-Checkout).
**Regel:** Erledigtes hier streichen. Neue Erkenntnisse einarbeiten, nicht daneben legen.
---
## 0 · IST-Befund (live verifiziert 15.07., 09:2011:00)
**Die Box ist gesund und die Autonomie-Kette ARBEITET nachweislich.**
- Dienste: alle aktiv (mission-control-2, hermes-gateway, hermes-builtin-ui, mem0, voice, box-console, llama-swap). 8 Tage Uptime, 68 GB frei.
- Nacht-Crons 15.07. alle grün: Traum 03:15 · Selbst-Inventur 03:35 · Idle-Radar 03:45 · Karten-Gutachter 04:00 · Bagatell 04:10 · Chef-Gutachter 04:30 · Daily-Briefing 08:00 · Release-Radar Mo 05:15. Backups: mc2-backup 03:32 + PBS 03:50 gelaufen.
- Versionen: llama.cpp **b9994** (Vulkan) · llama-swap **v239** · Hermes **0.18.2** (+1 legitimer carried commit) · MC2 2.0.0-w8.
- Warm-Set schlank: Hirn 17,5 GB + embed 2 GB + reranker 2,2 GB ≈ 22 GB.
- Repo: lokal = Gitea = Box @ main. Live-Checkout wieder sauber auf `main`.
- **Autonomie-Beweis der Nacht 14.07.:** Idle-Radar erhob Journal-Befund (stop-sigterm-Timeout) → Decomposer zerlegte → werkstatt baute Restart-Skript+systemd-Override → betrieb testete live (~6 s Recovery) → done. Ohne Menschen, ohne Claude.
## 1 · Heute im Review erledigt (15.07.)
-**Hermes-Quelle wieder jungfräulich:** verwaister ~160-Zeilen-Patch in `gateway/platforms/api_server.py` entfernt (Bild-Beschreibung für Coder — war NIE aufgerufen = toter Code, wurde aber von jedem `hermes update` mitgeschleppt = Konflikt-Risiko fürs kommende v0.19). Original archiviert: `docs/archiv/hermes-api_server-vision-patch-verwaist.diff`.
-**Idee daraus regelkonform portiert → Bild-Weiche v2** (MC2-Gateway): Bild-Request an Coder-Ziel bekommt Vision-BESCHREIBUNG injiziert und bleibt beim Coder (Screenshot-Debugging!); Fallback = alte Umleitung. **Branch `wartung/bild-weiche-v2` → wartet als Karte auf Klick.**
-**VL-8B-Leiche gefixt:** `agents.vision.model: Qwen3-VL-8B-Instruct` (Modell existiert seit 07.07. nicht mehr!) → `vision`-Alias, in Top-Level- UND beiden Profil-Configs (Backups `*.bak-review-20260715`). Gateway neu, `hermes doctor` grün.
-**Live-Checkout aufgeräumt:** zurück auf `main` (stand auf verwaistem Branch), 2 stale Branches gelöscht, leere `state.db` + alter `backend/memory.py` entfernt.
-**2 Mess-Karten in der Queue:** `t_5ada48ea` (Gemma-4-Hirn-Bench) + `t_64a384b9` (heavy-64k-Messung).
- ✅ Blockierte Karte `t_8231d6b8` analysiert: **Arbeit ist fertig+live** — Worker vergaß nur den `kanban_complete`-Aufruf. Abschluss = Sofort-Paket a).
## 2 · Konfig-Bewertung: Das ist GUT so (nicht anfassen)
| Bereich | Urteil |
|---|---|
| Modell-Kader (8 Modelle, klare Rollen, schlankes Warm-Set) | ✅ passt zur 122-GB-Box; Recherche 15.07.: **kein** Qwen-Coder-Nachfolger draußen, Coder-Verdikt hält |
| Vulkan/RADV statt ROCm | ✅ hält (ROCm auf gfx1151 weiter „Preview", llama.cpp-Issue #21284 offen) |
| Fundament-Fixes (concurrency 1, auto_decompose true, Specifier-Pins, 3 Hooks je Profil) | ✅ live korrekt verifiziert |
| Verdikte Mem0 / Telegram / Electron / pocket-Default / Hermes-Prompts englisch | ✅ bleiben |
| DeepSeek V4-Flash als Kandidat | ❌ zu groß (103142 GB Quant) — passt nie neben das Warm-Set |
| systemd-Override Restart=always für MC2 | ✅ behalten (User-Entscheid 15.07.) — gute Appliance-Praxis |
## 3 · SOFORT-Paket (User-Klicks + eine sudo-Runde)
a) **Karte `wartung/bild-weiche-v2` annehmen** (Auftragsbuch). Danach prüft man so:
Bild an `model:"coder"` schicken → Antwort-Header `x-mc-route-reason: Bild beschrieben (Vision) -> bleibt beim Coder`.
b) **Karte `t_8231d6b8` abschließen** (Arbeit war fertig, nur Protokollverstoß):
`ssh hitonabi@192.168.178.151``cd ~/.hermes/hermes-agent && venv/bin/hermes kanban complete t_8231d6b8 --comment "war fertig+verifiziert, Worker vergass complete-Call"`
c) **sudo-Runde (einmal Passwort):**
1. Stale v1-Unit weg: `sudo systemctl disable mission-control.service; sudo rm /etc/systemd/system/mission-control.service; sudo rm -rf /opt/mission-control` (Faden C14)
2. Root-Warmup-Kopie aktualisieren: `sudo install -m 0755 ~/mission-control-v2/deploy/warmup.sh /usr/local/bin/llama-swap-warmup.sh` (stale seit 07.07. — sonst wärmt ein llama-swap-Restart falsch)
d) **Daily-Briefing-Cron** (dein eigener, 08:00): steht auf `Repeat: 9/41`**läuft nach 41 Läufen aus**. Wenn dauerhaft gewollt: auf ∞ stellen (`hermes cron edit 095d53c1e99e`).
## 4 · ROBUSTHEIT (Priorität 1 — Karten für die Queue)
- **R1 · Worker-Protokoll-Erinnerung** (klein): `box-steckbrief-inject.sh` um einen Satz ergänzen: „Am Ende IMMER kanban_complete oder kanban_block aufrufen — sauberes Text-Ende zählt nicht." Genau daran blockierte die Nacht-Karte.
- **R2 · Guard-Erweiterung systemd** (klein, braucht User-Ja): `tabu-pfade-guard.py` um `~/.config/systemd/user/` erweitern. Die Nacht-Werkstatt hat den Override direkt scharf geschaltet — ging gut, widerspricht aber „jede Code-Zeile ist Klick-pflichtig". Installation künftig nur über angenommene Karte.
- **R3 · Hermes v0.19 kommt** (Sammel-Release, ~660 PRs seit v0.18.0): Fangnetz existiert (autoupdate → doctor → postcheck → Auto-Rollback+Pin+Telegram). Quelle ist seit heute konfliktfrei. **Nichts zu tun — nur wissen, dass die Meldung kommen wird.**
- **R4 · Orchestrator-Worktrees raus aus /tmp** (klein): `/tmp/orch-kanten` überlebt keinen Reboot; Worktree-Basis nach `~/.hermes/orch-worktrees/` o. ä.
- **R5 · Runbook C13** (größter offener Robustheits-Batzen): 1-Seiter „Box tot → was drücken" + Bare-Metal-Bootstrap (docs/DISASTER_RECOVERY.md existiert als Konzept). Karten-Serie.
- **R6 · Backup-Restposten:** QNAP-/pve-root-Passwörter in Keepass verifizieren; Restore-Probe einmal fahren (PBS läuft, aber ungeprobt zurückgespielt).
## 5 · AUFRÄUMEN (Priorität 2)
- **A1 · Alt-Docs** (Bagatell-Klasse, Box darf selbst): README/STATUS/CUTOVER/HERMES_SETUP tragen Vor-Trennung-Stand (Faden C15).
- **A2 · Lucy-Repo-Hygiene** (PC-Karte über Lucy-Pipeline): 4 unkommittierte Dateien (Hybrid-TTS-Routing in App.tsx/useVoiceAgent/voice-core/kokoro_server) committen oder verwerfen — vorher prüfen, was live läuft; Saber-Altlasten löschen (XTTS-Datensatz-Verdikt: unbrauchbar); untracked venvs/Datasets ignorieren oder weg.
- **A3 · Hermes-Home-Kleinkram:** `hermes_cli/web_dist.bak-rootbuild/` + `.install_method` (harmlos, bei Gelegenheit).
- **A4 · D16-Restposten:** c) System-Logs ausbauen (Fehler-Filter) · e) Mem0-Dubletten automatisch statt Knopf · f) Lucy-Reste aus Web-UI + Voice-Sidecar-Diät. Alles einzelne Queue-Karten.
## 6 · FEATURES (Priorität 3)
- **F1 · Bild-Weiche v2:** gebaut, wartet auf Klick (Sofort-Paket a).
- **F2 · heavy auf 64k** (nach Messkarte `t_64a384b9` + User-Ja): dann `delegation`-Floor erfüllt → heavy als delegate_task-Ziel freischalten → Konzept-Fließband/Lucy nutzen heavy nativ statt worker.sh-Umweg.
- **F3 · Mini-Stimme v2 (AKTIV, User-Entscheid 15.07.):**
1. User: Colab-Volllauf (~3 h, Notebook `Lucy_Stimme_Training_v2.ipynb` auf Desktop, Volltraining-Modus ist schon eingestellt)
2. Danach: Modell-Datei tauschen + Whisper-Rücktranskriptions-Test (der EINZIGE ehrliche Richter) — kann PC-Hermes über die Lucy-Pipeline
3. A/B gegen pocket im Live-Gespräch → Default-Entscheid
4. **Zukunftsfaden v3:** Qwen3-TTS-**Instruct** kann inzwischen Stil/Emotion bei geklonten Stimmen steuern — genau das „alles klingt gleich"-Problem. Neuer Datensatz mit Emotions-Instruktionen, sonst gleiches Rezept.
- **F4 · Gemma 4 26B-A4B als Hirn-Kandidat** (nach Bench-Karte `t_5ada48ea`): Community meldet ~110 t/s mit MTP-Head (vs. ~66 heute), multimodal (könnte Hirn+Augen VEREINEN), 256k Kontext. ABER: gemma-Vorgeschichte (Cache-Bug #21468, „Fix" unbestätigt), KV nur f16 (RAM!), mmproj×MTP ungeklärt. **Nur mit Messbeweis + User-Ja — Hirn-Swap ist ein Groß-Ereignis mit Rollback-Plan.**
## 7 · TEMPO (Priorität 4)
- **T1 · ROCm-Recheck 20.25.07.** (Box-Reminder #3 existiert): Stand 15.07. hält Vulkan (tg-Führung bestätigt, #21284 weiter offen). Falls ROCm 7.13+ liefert: NUR Hybrid für heavy/coder-Prefill erwägen, **Hirn bleibt Vulkan**.
- **T2 · hipEngine-Watch** (Reminder #4 existiert): weiterhin praktisch Qwen3.6-only, gfx1151 nur „initial pass" — beobachten, nicht adoptieren.
- **T3 · Faden 10** (spec-draft-Sweep): bleibt bewusst übersprungen (User 14.07.).
## 8 · Übergabe-Logik (wenn Claude weg ist)
1. **Neue Ideen** → eine der drei Türen (Telegram / Lucy / MC2-Ideenfeld) → Queue → Karte → dein Klick. Die Kette ist E2E bewiesen.
2. **Wissen**`docs/wissen/` (dieser Plan, STACK, FALLEN, GRENZEN, VERDIKTE) — Box liest `~/mission-control-v2`, Gemini liest `F:\Coding Stuff\mission-control-2`. GEMINI_BRIEFING.md existiert.
3. **Pflege** → Auto-Update-Timer mit Rollback+Pin; Selfsmoke täglich; Alarmkette Telegram.
4. **Evolution** → Release-Radar (Mo) + Chef-Gutachter (täglich) + Traum (täglich) finden Chancen; Bench-Harnesses (brain-bench, model-bench) messen sie; du entscheidest per Klick.
5. **Notfall** → Gemini/Antigravity als Review-Partner (RUNBOOK „Wann Gemini rufen").
## 9 · Recherche-Quellen (15.07.2026)
- Strix-Halo-Benchmarks/Grid: [strix-halo-guide](https://github.com/hogeheer499-commits/strix-halo-guide) · [llm-tracker Strix Halo](https://llm-tracker.info/_TOORG/Strix-Halo)
- ROCm-Prefill-Issue: [llama.cpp #21284](https://github.com/ggml-org/llama.cpp/issues/21284) (offen)
- Gemma 4: [26B-A4B auf Strix Halo](https://akehir.com/blog/strix-halo-kubernetes-llm-gemma-4) · [Cache-Bug #21468](https://github.com/ggml-org/llama.cpp/issues/21468) · [unsloth GGUF](https://huggingface.co/unsloth/gemma-4-26B-A4B-it-GGUF) · [AMD Day-0](https://www.amd.com/en/developer/resources/technical-articles/2026/day-0-support-for-gemma-4-on-amd-processors-and-gpus.html)
- DeepSeek V4: [unsloth Doku](https://unsloth.ai/docs/models/deepseek-v4) (103142 GB → zu groß)
- Hermes v0.19: [Releases](https://github.com/NousResearch/hermes-agent/releases)
- hipEngine: [shisa-ai/hipEngine](https://github.com/shisa-ai/hipEngine)
- Qwen3-TTS (Instruct/Emotion): [Qwen-Blog](https://qwen.ai/blog?id=qwen3tts-0115) · [Emotion-Diskussion](https://github.com/QwenLM/Qwen3-TTS/discussions/218)