Umbau Stufe 1-5: Governor v2 + OpenCode-Plugin + Subagenten-Mannschaft
Die Intelligenz sitzt nicht mehr NEBEN dem Coden, sondern DRIN. Alles auf der Box gemessen, nicht angenommen. Stufe 1 — Speicher-Haushalt (llama-swap-Config, nicht im Repo): Coder dauerwarm statt ttl 600 (Kaltstart 23 s -> 491 ms), Devstral als 'kritiker' verdrahtet, Swap 11 GB -> 0. Zwei Befunde: Kontext 131k->65k spart bei Qwen3-Next 0 GB (Hybrid-Attention), und llama-swap haelt nur EINE Gruppe resident -> alles Ko-Residente in dieselbe Gruppe. `persistent: true` verhindert dabei das Freiraeumen vor grossen Modellen -> ausgeloester Kernel-OOM, behoben durch persistent:false + TTLs (Vision laedt jetzt in 10 s statt 117 s + Absturz). Stufe 2 — Governor v2 (deploy/governor/): Steht jetzt IM Pfad (:8100 -> MC2 :9001) statt daneben. Zaehlt den GANZEN Anfragekoerper inkl. tools/tool_calls und kalibriert sich aus den echten usage.prompt_tokens jeder Antwort nach: Schaetzfehler 200 % -> 0,3 %. Soft-Einschub nur noch, wenn die Nachrichtenkette es erlaubt (kein Dazwischen- funken in offene tool_calls). Neuer Status-Endpunkt + systemd-Unit. Lucys Alltagsmodelle sind vom Schnitt ausgenommen. Stufe 3 — OpenCode-Plugin (deploy/opencode/plugin/mc2-governor.ts): Werkzeug-Zaun (git push, rm -rf, sudo, curl|sh — bewiesen), Pruef-Tor auf session.idle mit Selbstreparatur, Savepoint statt Kompression, Meldungen an Lucys Briefkasten mit eigenem Absender 'loop'. Stufe 4 — Mannschaft (opencode.json): plan+build -> coder (51,5 t/s) · explore -> hermes (69,6 t/s, warm, gratis) · review -> Devstral (15,0 t/s, FREMDE Modellfamilie gegen blinde Flecken). Der 63-GB-Planer faellt aus der Tagesrolle raus. Stufe 5 — ein Regelwerk, zwei Ausloeser: deploy/opencode-lauf.sh faehrt dieselbe Bau-Pruef-Schleife unbeaufsichtigt (die Schleife liegt hier UND im Plugin: bei `opencode run` endet der Prozess, bevor session.idle fertig ist — gemessen). Bricht ab, wenn der Governor fehlt. gitea-repo-create.sh saet jetzt VERIFY neben der CI-Ampel: jedes neue Repo wird mit Innen- UND Aussen-Pruefung geboren. Oberflaeche: Token-Waechter-Kachel im Cockpit (Fuellstand, Marken, Ehrlichkeits-Nachweis), /api/governor als gleichursprüngliches Fenster, Devstral im Modellkatalog, Rollen-Texte auf die neue Mannschaft aktualisiert. .gitattributes: deploy/**/*.py auf LF (deploy/*.py greift nur eine Ebene tief). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env bash
|
||||
# opencode-lauf.sh — EIN begrenzter Agentenlauf auf der Box, mit denselben Regeln wie in Zed.
|
||||
#
|
||||
# Das ist die Nacht-Seite von „ein Regelwerk, zwei Ausloeser": tagsueber tippst du in Zed,
|
||||
# nachts ruft ein Hermes-Cron dieses Skript. Beide Wege benutzen
|
||||
# * dieselbe OpenCode-Version,
|
||||
# * dieselbe Mannschaft (~/.config/opencode/opencode.json: coder/hermes/kritiker),
|
||||
# * dasselbe Plugin (~/.config/opencode/plugin/mc2-governor.ts: Zaun + Pruef-Tor),
|
||||
# * denselben Token-Waechter (:8100).
|
||||
#
|
||||
# Unterschied zu deploy/worker.sh: worker.sh ist EIN zustandsloser Completion-Aufruf
|
||||
# (Hermes schreibt die Dateien selbst). Hier laeuft ein ECHTER Agent mit Datei-Haenden,
|
||||
# Subagenten und Pruef-Tor — fuer ganze Karten statt fuer Schnipsel.
|
||||
#
|
||||
# Nutzung:
|
||||
# opencode-lauf.sh <repo-pfad> "<auftrag>"
|
||||
# opencode-lauf.sh ~/projekte/foo "Baue X. Halte dich an AGENTS.md."
|
||||
#
|
||||
# Env:
|
||||
# LAUF_TIMEOUT Sekunden Hoechstdauer (Default 3600)
|
||||
# LAUF_LAUT 1 = Lucy spricht mit (Default 0 = still, Nachtbetrieb)
|
||||
# LAUF_AGENT OpenCode-Agent (Default build)
|
||||
set -uo pipefail
|
||||
|
||||
REPO="${1:-}"
|
||||
AUFTRAG="${2:-}"
|
||||
TIMEOUT="${LAUF_TIMEOUT:-3600}"
|
||||
AGENT="${LAUF_AGENT:-build}"
|
||||
OC="$HOME/.opencode/bin/opencode"
|
||||
ANNOUNCE="${MC_ANNOUNCE_URL:-http://127.0.0.1:9001/api/voice/announce}"
|
||||
|
||||
melde () { # melde <betreff> <text> [prioritaet]
|
||||
curl -sf -m 5 -X POST "$ANNOUNCE" -H 'Content-Type: application/json' \
|
||||
--data "$(python3 -c 'import json,sys; print(json.dumps({"subject":sys.argv[1],"text":sys.argv[2],"source":"loop","priority":sys.argv[3]}))' \
|
||||
"$1" "$2" "${3:-silent}")" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
if [ -z "$REPO" ] || [ -z "$AUFTRAG" ]; then
|
||||
echo "Nutzung: $0 <repo-pfad> \"<auftrag>\"" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [ ! -d "$REPO" ]; then
|
||||
echo "FEHLER: '$REPO' ist kein Verzeichnis." >&2
|
||||
exit 2
|
||||
fi
|
||||
if [ ! -x "$OC" ]; then
|
||||
echo "FEHLER: OpenCode nicht gefunden ($OC)." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Der Governor MUSS stehen — ohne ihn liefe der Lauf ohne Sitzungs-Bremse.
|
||||
if ! curl -sf -m 5 -o /dev/null "http://127.0.0.1:8100/governor/status"; then
|
||||
echo "FEHLER: Governor (:8100) antwortet nicht — Lauf abgebrochen (keine Sitzungs-Bremse)." >&2
|
||||
melde "[Lauf]" "Ich habe einen Nachtlauf abgebrochen: der Token-Waechter antwortet nicht." "normal"
|
||||
exit 3
|
||||
fi
|
||||
|
||||
# Nachts still: Meldungen landen im Briefkasten, Lucy spricht sie aber nicht aus.
|
||||
# Das Plugin liest diese Variable; die Morgen-Zusammenfassung kommt vom Daily-Briefing.
|
||||
if [ "${LAUF_LAUT:-0}" = "1" ]; then export MC2_LOOP_SILENT=0; else export MC2_LOOP_SILENT=1; fi
|
||||
|
||||
LOGDIR="$HOME/.hermes/logs"; mkdir -p "$LOGDIR"
|
||||
STAMP="$(date +%Y%m%d-%H%M%S)"
|
||||
LOG="$LOGDIR/opencode-lauf-$STAMP.log"
|
||||
MAXRUNDEN="${LAUF_MAX_RUNDEN:-3}"
|
||||
|
||||
cd "$REPO" || exit 2
|
||||
NAME="$(basename "$REPO")"
|
||||
melde "[Lauf]" "Ich fange an zu bauen: $NAME." "silent"
|
||||
echo "=== Lauf $STAMP · Repo $REPO · Agent $AGENT · Timeout ${TIMEOUT}s ===" | tee "$LOG"
|
||||
|
||||
# Verify-Befehl des Projekts lesen (gleiche Datei und gleiche Regeln wie im Plugin).
|
||||
verify_cmd () {
|
||||
[ -r "$REPO/VERIFY" ] || return 1
|
||||
grep -vE '^\s*(#|$)' "$REPO/VERIFY" | paste -sd' && ' -
|
||||
}
|
||||
|
||||
START=$(date +%s)
|
||||
CODE=0
|
||||
RUNDE=0
|
||||
AUFGABE="$AUFTRAG"
|
||||
|
||||
# ── Bau-Schleife ────────────────────────────────────────────────────────────
|
||||
# Warum hier UND im Plugin? Das Plugin haengt am Ereignis `session.idle` — in Zed
|
||||
# laeuft der Prozess weiter und alles ist gut. Bei `opencode run` beendet sich der
|
||||
# Prozess aber, bevor das Pruef-Tor fertig ist (gemessen 25.07.). Fuer unbeaufsichtigte
|
||||
# Laeufe muss die Schleife deshalb HIER liegen, wo sie den Prozess ueberlebt.
|
||||
while :; do
|
||||
RUNDE=$((RUNDE + 1))
|
||||
echo "--- Runde $RUNDE/$MAXRUNDEN ---" | tee -a "$LOG"
|
||||
timeout "$TIMEOUT" "$OC" run --agent "$AGENT" "$AUFGABE" >>"$LOG" 2>&1
|
||||
CODE=$?
|
||||
[ "$CODE" -eq 124 ] && { echo "ZEITUEBERSCHREITUNG" | tee -a "$LOG"; break; }
|
||||
|
||||
VCMD="$(verify_cmd)" || { echo "Kein VERIFY — Pruef-Tor aus, Lauf endet." | tee -a "$LOG"; break; }
|
||||
|
||||
echo "--- Pruef-Tor: $VCMD ---" | tee -a "$LOG"
|
||||
VOUT="$(cd "$REPO" && eval "$VCMD" 2>&1)"; VCODE=$?
|
||||
printf '%s\n' "$VOUT" | tail -20 >> "$LOG"
|
||||
|
||||
if [ "$VCODE" -eq 0 ]; then
|
||||
echo "PRUEF-TOR GRUEN" | tee -a "$LOG"
|
||||
melde "[Pruefung]" "$NAME: Tests gruen nach $RUNDE Runde(n)." "normal"
|
||||
CODE=0
|
||||
break
|
||||
fi
|
||||
|
||||
if [ "$RUNDE" -ge "$MAXRUNDEN" ]; then
|
||||
echo "PRUEF-TOR ROT — Reparaturrunden aufgebraucht." | tee -a "$LOG"
|
||||
melde "[Pruefung]" "$NAME: Tests bleiben rot nach $RUNDE Runden. Hier komme ich allein nicht weiter." "normal"
|
||||
CODE=1
|
||||
break
|
||||
fi
|
||||
|
||||
echo "PRUEF-TOR ROT — Runde $((RUNDE + 1)) folgt." | tee -a "$LOG"
|
||||
melde "[Pruefung]" "$NAME: Tests rot, ich repariere selbst weiter (Runde $((RUNDE + 1))/$MAXRUNDEN)." "silent"
|
||||
AUFGABE="[MC2-PRUEFTOR] Der Verify-Befehl des Projekts ist fehlgeschlagen.
|
||||
|
||||
Befehl: $VCMD
|
||||
|
||||
Ausgabe (Ende):
|
||||
$(printf '%s' "$VOUT" | tail -c 3000)
|
||||
|
||||
Behebe die URSACHE — nicht das Symptom. Schalte keinen Test ab und aendere keine Tests.
|
||||
Urspruenglicher Auftrag war: $AUFTRAG"
|
||||
done
|
||||
|
||||
DAUER=$(( $(date +%s) - START ))
|
||||
# Nachweis statt Behauptung: was hat der Lauf im Arbeitsbaum tatsaechlich veraendert?
|
||||
GEAENDERT="$(git -C "$REPO" status --porcelain 2>/dev/null | wc -l | tr -d ' ')"
|
||||
|
||||
case "$CODE" in
|
||||
0) ERG="fertig, Pruefung bestanden" ;;
|
||||
124) ERG="ZEITUEBERSCHREITUNG nach ${TIMEOUT}s" ;;
|
||||
*) ERG="Pruefung NICHT bestanden (Exitcode $CODE)" ;;
|
||||
esac
|
||||
|
||||
echo "=== Ergebnis: $ERG · ${DAUER}s · $RUNDE Runde(n) · $GEAENDERT geaenderte Dateien · Log $LOG ===" | tee -a "$LOG"
|
||||
melde "[Lauf]" "$NAME: $ERG nach $((DAUER/60)) Minuten, $RUNDE Runde(n), $GEAENDERT Dateien angefasst." "silent"
|
||||
exit "$CODE"
|
||||
Reference in New Issue
Block a user