7fac17ed9a
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>
141 lines
5.6 KiB
Bash
141 lines
5.6 KiB
Bash
#!/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"
|