98 lines
6.3 KiB
Bash
98 lines
6.3 KiB
Bash
#!/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 (Speicher & Latenz-Trade-off):
|
|
# Der Standard-Worker ist Qwen3-Coder-Next (laedt klein und schnell neben dem Warm-Set).
|
|
# Fuer initiale Planung kann "gpt-oss-120b" gerufen werden. Das Modell passt knapp in den
|
|
# Speicher (124 GB RAM), aber das on-demand Laden unterbricht Lucys 1-s-Latenz fuer ca.
|
|
# 15-30 Sekunden. Dieser Latenz-Hit wird fuer die Orchestrator-Planung in Kauf genommen.
|
|
#
|
|
# 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: <was genau>" 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: <was>" 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<last;i++) print l[i] }
|
|
else
|
|
{ for (i=1;i<=NR;i++) print l[i] }
|
|
}')"
|
|
|
|
if [ -n "$OUT" ]; then
|
|
printf '%s\n' "$OUT"
|
|
# Abschneide-Warnung auf stderr (stdout bleibt sauberes Artefakt).
|
|
[ "$FIN" = "length" ] && echo "=== WORKER-HINWEIS: Ausgabe bei max_tokens ($MAXTOK) ABGESCHNITTEN — WORKER_MAXTOK erhoehen ODER den Teil-Task kleiner schneiden. ===" >&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
|