#!/usr/bin/env bash # Worker — Ein-Schuss-Arbeitsauftrag an ein bestimmtes Worker-Modell (llama-swap, SERIELL). # Das Gegenstueck zu fremdblick.sh: fremdblick KRITISIERT, worker ARBEITET. Beide sind eine # EINZELNE Completion an eine ECHTE llama-swap-Modell-ID (kein Gateway-Alias, kein delegate_task) # → umgeht Hermes' 64K-Delegations-Floor (MINIMUM_CONTEXT_LENGTH) und das Laden grosser Modelle # neben dem Warm-Set liegt bewusst in DEINER Hand. # # WICHTIG (harte Speicher-Falle): Waehle nur Worker, die KLEIN neben dem Warm-Set laden. # v1-Palette = Qwen3-Coder-Next (128k, code-stark, laedt klein). NIEMALS "heavy"/gpt-oss-120b # als Worker: 60 GB stirbt beim Laden neben dem VL-30B-Warm-Set (Health-Check-Timeout) und # gefaehrdet Lucys ~1-s-Sprech-Latenz. # # Der Worker ist ZUSTANDSLOS: kein Gedaechtnis, keine Datei-Haende. Er bekommt GENAU den Kontext, # den du ihm auf stdin gibst, und liefert TEXT zurueck (Datei-Inhalt / Diff / Plan). Das SCHREIBEN # in Dateien macht der Manager (Hermes) danach mit seinen eigenen Tools. # # Nutzung: printf '%s' "$teiltask_mit_kontext" | WORKER_ROLE=build worker.sh > /tmp/out.py # printf '%s' "$spec" | WORKER_MODEL=Qwen3-Coder-Next WORKER_ROLE=build worker.sh # # Env-Overrides: # WORKER_MODEL echte llama-swap-ID (Default: Qwen3-Coder-Next). NICHT gpt-oss/heavy (siehe oben). # WORKER_ROLE build (Default) | refactor | plan | prose — waehlt das System-Raster. # WORKER_SYS eigenes System-Raster (uebersteuert WORKER_ROLE). # WORKER_MAXTOK max_tokens (Default 4000 — Code braucht Platz). # WORKER_TEMP temperature (Default 0.1 — deterministisch fuer Code). set -uo pipefail # Achtung: llama-swap listet unter /v1/models die ECHTEN Modell-IDs, nicht die Gateway-Aliase # (kein "heavy"/"coder"/"scout" hier — die echte ID nehmen). ENDPOINT="${WORKER_ENDPOINT:-http://127.0.0.1:8080/v1/chat/completions}" MODEL="${WORKER_MODEL:-Qwen3-Coder-Next}" ROLE="${WORKER_ROLE:-build}" MAXTOK="${WORKER_MAXTOK:-4000}" TEMP="${WORKER_TEMP:-0.1}" TASK="$(cat)" if [ -z "${TASK// /}" ]; then echo "=== WORKER: nichts zu tun (leere Eingabe) ===" >&2 exit 2 fi # Aufruf-Protokoll: Nachweis, dass wirklich delegiert wurde (der Manager kann sich das nicht # ausdenken) + Beobachtbarkeit eines Orchestrator-Laufs. Best-effort, blockiert nie. LOGF="${WORKER_LOG:-$HOME/.hermes/logs/worker.log}" mkdir -p "$(dirname "$LOGF")" 2>/dev/null || true printf '%s\tmodel=%s\trole=%s\tbytes=%s\tfirst=%s\n' \ "$(date +%Y-%m-%dT%H:%M:%S)" "$MODEL" "$ROLE" "${#TASK}" \ "$(printf '%s' "$TASK" | tr '\n\t' ' ' | cut -c1-90)" >> "$LOGF" 2>/dev/null || true # Bau-Raster: liefert NUR das Artefakt, keine Prosa, kein Markdown-Zaun (Manager schreibt es 1:1 in eine Datei). BUILD_SYS='Du bist ein fokussierter Umsetzungs-Worker in einem Fliessband. Du bekommst EINEN abgegrenzten Teil-Auftrag mit allem noetigen Kontext. Erledige GENAU diesen Teil — nicht mehr, nicht weniger. Du bist ZUSTANDSLOS: nutze NUR den mitgelieferten Kontext, erfinde keine Datei-Inhalte, keine Pfade und keine APIs dazu; fehlt dir etwas Entscheidendes, schreibe als EINZIGE Ausgabe eine Zeile "FEHLT: " statt zu raten. Uebernimm den Stil des umgebenden Codes (Sprache der Kommentare, bestehende Muster, Einrueckung). Gib NUR das geforderte Artefakt aus (den vollstaendigen Datei-Inhalt bzw. den unified Diff), OHNE Vorrede, OHNE ```-Codezaun, OHNE Erklaerung danach.' # Refactor: wie build, aber betont Verhalten-erhalten. REFACTOR_SYS='Du bist ein fokussierter Refactor-Worker. Du bekommst bestehenden Code und einen klaren Umbau-Auftrag. Erhalte das beobachtbare Verhalten exakt; aendere nur, was der Auftrag verlangt. Du bist ZUSTANDSLOS: nutze NUR den mitgelieferten Kontext; fehlt Entscheidendes, gib nur "FEHLT: " aus. Gib NUR den vollstaendigen neuen Datei-Inhalt bzw. den Diff aus, OHNE Prosa, OHNE ```-Codezaun.' # Plan-Raster: knappe, umsetzbare Schrittliste (kein Code). In v1 plant meist der Manager selbst; optional. PLAN_SYS='Du bist ein knapper Umsetzungs-Planer. Zerlege den Auftrag in eine kurze, nummerierte Liste konkreter Schritte — je 1 Zeile: welche Datei, was passiert. Keine Prosa drumherum, KEIN Code, keine Alternativen-Diskussion.' # Prosa-Raster: Text-/Recherche-Teiltask. PROSE_SYS='Du bist ein fokussierter Text-Worker. Erledige den Schreib-/Recherche-Teilauftrag knapp und sachlich auf Deutsch, ausschliesslich mit dem mitgelieferten Kontext. Keine Ausschmueckung, keine erfundenen Fakten.' case "$ROLE" in build) SYS="${WORKER_SYS:-$BUILD_SYS}" ;; refactor) SYS="${WORKER_SYS:-$REFACTOR_SYS}" ;; plan) SYS="${WORKER_SYS:-$PLAN_SYS}" ;; prose) SYS="${WORKER_SYS:-$PROSE_SYS}" ;; *) SYS="${WORKER_SYS:-$BUILD_SYS}" ;; esac RESP="$(jq -n --arg m "$MODEL" --arg s "$SYS" --arg u "$TASK" --argjson t "$MAXTOK" --argjson temp "$TEMP" \ '{model:$m, temperature:$temp, max_tokens:$t, messages:[{role:"system",content:$s},{role:"user",content:$u}]}' \ | curl -s --max-time 300 "$ENDPOINT" -H 'Content-Type: application/json' -d @- 2>/dev/null)" OUT="$(printf '%s' "$RESP" | jq -r '.choices[0].message.content // empty' 2>/dev/null)" FIN="$(printf '%s' "$RESP" | jq -r '.choices[0].finish_reason // empty' 2>/dev/null)" # Code-Modelle wickeln die Ausgabe trotz Anweisung gern in einen ```-Codezaun. stdout soll aber ein # DIREKT schreibbares Artefakt sein → einen umschliessenden Zaun entfernen (nur wenn die erste UND # die letzte nicht-leere Zeile ein Zaun ist — der uebliche Wrap-Fall; interne Zaeune bleiben unberuehrt). OUT="$(printf '%s' "$OUT" | awk ' { l[NR]=$0 } END { f=0; last=0 for (i=1;i<=NR;i++) if (l[i] ~ /[^[:space:]]/) { if (!f) f=i; last=i } if (f && l[f] ~ /^[[:space:]]*```/ && l[last] ~ /^[[:space:]]*```[[:space:]]*$/ && f!=last) { for (i=f+1;i&2 exit 0 else echo "=== WORKER FEHLGESCHLAGEN (Modell $MODEL, finish=${FIN:-keins}) — kein Ergebnis. Endpoint/Modell-ID pruefen (echte /v1/models-ID, kein Alias). ===" >&2 exit 1 fi