#!/usr/bin/env bash # Hermes-Release-Radar (woechentlich, Mo 05:15) — "mehr Hermes nativ, weniger Eigenkreationen": # haelt neue hermes-agent-Releases gegen die Eigenbau-Landkarte im Wissens-Vault und meldet # NUR echte Treffer auf Abloese-Kriterien/Wiedervorlage-Bedingungen. Muster wie # chef-gutachter-feed.sh: liegt in ~/.hermes/scripts/ (deploy.sh kopiert ihn), stdout wird # dem Cron-Agenten als Prompt eingespeist. Der ABGLEICH laeuft als Ein-Schuss-Completion # direkt gegen llama-swap (:8080, fremdblick-Architektur): Analyst gpt-oss-120b (um 05:15 # vom Chef-Gutachter 04:30 oft noch geladen), Vertretung GLM-4.7-Flash. Danach gatet # fremdblick.sh (Prosa-Raster, Fremd-Vendor) jede Treffer-Behauptung gegen die # Original-Changelogs. # Grundsaetze (stehen auch in der Landkarte): nur getaggte Releases (kein main-Churn bei # ~660 PRs/Woche) · Behauptung nur mit woertlichem Changelog-Zitat · Treffer => Probe- # Vorschlag, NIE Direkt-Adoption · der Radar informiert, er updatet nicht (Entscheid 04.07.). # State: ~/.hermes/state/release-radar-last.txt (zuletzt gemeldeter Tag). Verlust harmlos — # ohne State dient die installierte Version als Basis. set -uo pipefail MC="$HOME/mission-control-v2" LANDKARTE="$HOME/wissens-vault/eigenbau-landkarte.md" STATE_DIR="$HOME/.hermes/state" STATE="$STATE_DIR/release-radar-last.txt" API="${RADAR_RELEASES_URL:-https://api.github.com/repos/NousResearch/hermes-agent/releases?per_page=10}" ENDPOINT="${RADAR_ENDPOINT:-http://127.0.0.1:8080/v1/chat/completions}" ANALYST="${RADAR_MODEL:-gpt-oss-120b}" VERTRETUNG="${RADAR_FALLBACK:-GLM-4.7-Flash}" FREMDBLICK="$HOME/.hermes/scripts/fremdblick.sh" mkdir -p "$STATE_DIR" # Briefkasten-Spiegel (still, wie die Morgenlage): Cockpit/Chronik lesen source=release-radar. briefkasten() { # $1=Betreff $2=Text curl -sf -m 5 -X POST "${MC_ANNOUNCE_URL:-http://127.0.0.1:9001/api/voice/announce}" \ -H 'Content-Type: application/json' \ --data "$(python3 - "$1" "$2" <<'PY' import json, sys print(json.dumps({"text": sys.argv[2], "subject": sys.argv[1], "source": "release-radar", "priority": "silent"})) PY )" >/dev/null 2>&1 || true } # ---------- Versionierter Auftrag fuer den Boten-Agenten ---------- cat "$MC/deploy/release-radar-prompt.md" 2>/dev/null || cat <<'EOF' (Prompt-Datei fehlt noch — Kurzform:) Du bist der Bote des woechentlichen Release-Radars. Gib den Befund unten in Lucys Stimme wieder: bei "KEIN NEUES RELEASE" oder "TREFFER: 0" genau EIN kurzer Satz; bei Treffern maximal 5 Saetze. Nichts installieren oder aendern. EOF echo INSTALLED_LINE="$(bash -lc 'hermes --version' 2>/dev/null | head -1)" INSTALLED_TAG="$(printf '%s' "$INSTALLED_LINE" | grep -oE '\([0-9][0-9.]*\)' | head -1 | tr -d '()')" [ -n "$INSTALLED_TAG" ] && INSTALLED_TAG="v$INSTALLED_TAG" LAST_SEEN="$(cat "$STATE" 2>/dev/null | tr -d '[:space:]' || true)" BASELINE="${LAST_SEEN:-$INSTALLED_TAG}" RELEASES_JSON="$(curl -sf --max-time 30 "$API" 2>/dev/null || true)" if [ -z "$RELEASES_JSON" ]; then echo "## RADAR-BEFUND: AUSGEFALLEN" echo "GitHub-Releases-API nicht erreichbar. Dem Commander EINEN ehrlichen Satz melden; naechste Woche neuer Versuch." exit 0 fi # Neue getaggte Releases seit BASELINE (Drafts/Prereleases raus, max. 6, Bodies gekuerzt). # Baseline unbekannt (aelter als die letzten 10 oder Erstlauf ohne Version) -> juengste 6. # JSON via Temp-Datei, NICHT via Pipe: das Heredoc belegt stdin schon fuer den Python-Code # selbst (Pipe+Heredoc gleichzeitig -> json.load(stdin) liest ins Leere; Probelauf 10.07.). TMPJSON="$(mktemp /tmp/release-radar.XXXXXX.json)" printf '%s' "$RELEASES_JSON" > "$TMPJSON" PARSED="$(python3 - "$BASELINE" "$TMPJSON" <<'PY' import json, sys baseline = sys.argv[1] try: data = json.load(open(sys.argv[2])) # Rate-Limit/Fehler antwortet als Dict statt Liste -> wie "keine Daten" behandeln. rels = [r for r in data if not r.get("draft") and not r.get("prerelease")] if isinstance(data, list) else [] except Exception: rels = [] rels.sort(key=lambda r: r.get("published_at") or "", reverse=True) if not rels: # API hat geantwortet, aber unbrauchbar (Rate-Limit-Dict, kaputtes JSON, leere Liste): # ehrlich als "blind" markieren statt faelschlich "kein neues Release" zu melden. print(baseline) print(-1) raise SystemExit base = next((r.get("published_at") for r in rels if r.get("tag_name") == baseline), None) new = rels[:6] if base is None else [r for r in rels if (r.get("published_at") or "") > base][:6] print((new or rels)[0].get("tag_name") or baseline) print(len(new)) for r in new: body = (r.get("body") or "").strip() if len(body) > 3500: body = body[:3500] + "\n[... gekuerzt ...]" print(f"\n===== RELEASE {r.get('tag_name')} — {(r.get('name') or '').strip()} ({(r.get('published_at') or '')[:10]}) =====") print(body) PY )" rm -f "$TMPJSON" NEWEST="$(printf '%s\n' "$PARSED" | sed -n 1p)" COUNT="$(printf '%s\n' "$PARSED" | sed -n 2p)" NOTES="$(printf '%s\n' "$PARSED" | tail -n +3)" if [ "${COUNT:-0}" = "-1" ]; then echo "## RADAR-BEFUND: AUSGEFALLEN" echo "GitHub-API hat geantwortet, aber unbrauchbar (Rate-Limit oder kaputtes JSON). Dem Commander EINEN ehrlichen Satz melden; State bleibt, naechste Woche neuer Versuch." exit 0 fi echo "## RADAR-BEFUND (erhoben am $(date '+%d.%m.%Y %H:%M'))" echo "Box installiert: ${INSTALLED_LINE:-unbekannt}" echo "Zuletzt gemeldet: ${BASELINE:-unbekannt} · neuestes Release: $NEWEST · neue Releases: ${COUNT:-0}" echo "(Updates bleiben manuell/bestaetigt — der Radar informiert nur.)" echo if [ "${COUNT:-0}" = "0" ]; then echo "KEIN NEUES RELEASE seit $BASELINE." [ -n "$NEWEST" ] && printf '%s\n' "$NEWEST" > "$STATE" briefkasten "Release-Radar" "Kein neues hermes-agent-Release seit $BASELINE (Box: ${INSTALLED_TAG:-?})." exit 0 fi if [ ! -s "$LANDKARTE" ]; then echo "### ABGLEICH NICHT MOEGLICH" echo "$COUNT neue Releases, aber die Eigenbau-Landkarte fehlt ($LANDKARTE)." echo "Dem Commander melden: Landkarte im Wissens-Vault wiederherstellen. State bleibt unveraendert (naechste Woche neuer Versuch)." briefkasten "Release-Radar" "$COUNT neue hermes-agent-Releases, aber die Eigenbau-Landkarte fehlt — Abgleich uebersprungen." exit 0 fi RASTER='Du bist der RELEASE-RADAR einer lokalen KI-Box. Du bekommst (1) die EIGENBAU-LANDKARTE (selbstgebaute Bausteine mit Abloese-Kriterien und Wiedervorlage-Bedingungen) und (2) CHANGELOGS neuer hermes-agent-Releases. Pruefe AUSSCHLIESSLICH: Trifft eine Changelog-Zeile ein Abloese-Kriterium oder eine Wiedervorlage-Bedingung der Landkarte? REGELN: Jede Treffer-Behauptung MUSS die Changelog-Zeile WOERTLICH zitieren. Marketing-Prosa ohne konkretes Feature ist KEIN Treffer. Vage Aehnlichkeit ist KEIN Treffer. Eintraege unter "Bewusst NICHT adoptiert" nur melden, wenn exakt die Wiedervorlage-Bedingung erfuellt ist. Erfinde nichts. Antworte auf DEUTSCH, exakt in dieser Struktur: erste Zeile "TREFFER: ". Danach je Treffer ein Block: "EIGENBAU: " / "RELEASE: " / "ZITAT: " / "WARUM: " / "PROBE: ". Bei TREFFER: 0 stattdessen EIN Satz, was die Releases fuer uns Unwichtiges bringen.' EVID="EIGENBAU-LANDKARTE: $(cat "$LANDKARTE") CHANGELOGS DER NEUEN RELEASES: $NOTES" # Ein-Schuss-Analyst: $1=Modell $2=curl-Timeout $3=max_tokens (Muster chef-gutachter-feed.sh; # max_tokens grosszuegig, weil Reasoning-Modelle Budget im reasoning_content verbrauchen). judge() { local req req="$(jq -n --arg m "$1" --arg sys "$RASTER" --arg usr "$EVID" --argjson mt "$3" \ '{model:$m, messages:[{role:"system",content:$sys},{role:"user",content:$usr}], max_tokens:$mt, temperature:0.2}')" curl -s -m "$2" "$ENDPOINT" -H 'Content-Type: application/json' --data-binary "$req" \ | python3 -c ' import json, sys try: d = json.load(sys.stdin) msg = d["choices"][0]["message"] print((msg.get("content") or msg.get("reasoning_content") or "").strip()) except Exception: pass' } RICHTER="$ANALYST" ANALYSE="$(judge "$ANALYST" 420 2600)" if [ -z "${ANALYSE// /}" ]; then RICHTER="$VERTRETUNG (Vertretung — $ANALYST hat nicht geantwortet)" ANALYSE="$(judge "$VERTRETUNG" 240 1800)" fi if [ -z "${ANALYSE// /}" ]; then echo "### ABGLEICH AUSGEFALLEN" echo "Beide Analysten ($ANALYST, $VERTRETUNG) haben nicht geantwortet. State bleibt unveraendert — naechste Woche neuer Versuch. Dem Commander EINEN ehrlichen Satz melden." briefkasten "Release-Radar" "$COUNT neue hermes-agent-Releases, aber der Abgleich fiel aus (kein Analyst erreichbar)." exit 0 fi echo "### Neue Releases:" printf '%s\n' "$NOTES" | grep '^===== RELEASE' | sed 's/^===== //; s/ =====$//' echo echo "### ABGLEICH gegen die Eigenbau-Landkarte (Analyst: $RICHTER)" echo "$ANALYSE" echo # Fremdblick-Gate nur bei Treffern (Zitate MIT Quellmaterial gegenpruefbar machen). TREFFER="$(printf '%s' "$ANALYSE" | grep -oE 'TREFFER: *[0-9]+' | head -1 | grep -oE '[0-9]+' || true)" GEGEN="" if [ "${TREFFER:-1}" != "0" ] && [ -x "$FREMDBLICK" ]; then # Grosses Budget: GLM (Reasoning-Modell) denkt erst ueber das ganze Quellmaterial nach, # bevor das Urteil in content landet — 2200 (fremdblick-Default) war im Probelauf 10.07. # zu knapp (ABGESCHNITTEN, kein Urteil). GEGEN="$(printf '%s\n\nQUELLMATERIAL (Original-Changelogs zum Gegenpruefen der Zitate):\n%s' "$ANALYSE" "$NOTES" \ | FREMDBLICK_MAXTOK="${RADAR_FREMDBLICK_MAXTOK:-4500}" "$FREMDBLICK")" echo "$GEGEN" echo fi # Treffer fuettern die Ideen-Queue ("Lucy kennt sich", 12.07.2026): aus einem echten # Radar-Treffer wird EINE rohe Idee (triage) — Specifier + Commander entscheiden, ob eine # Probe gebaut wird. Idempotent je Release-Tag; der Radar selbst bleibt reiner Melder. if [ -n "${TREFFER:-}" ] && [ "$TREFFER" != "0" ] && [ -x "$HOME/.local/bin/hermes" ]; then IDEE_ID="$("$HOME/.local/bin/hermes" kanban create \ "Hermes ${NEWEST}: Release-Radar-Treffer pruefen (Probe bauen?)" \ --body "Automatisch vom Release-Radar am $(date '+%d.%m.%Y'). ABGLEICH (Analyst: ${RICHTER}): ${ANALYSE} FREMDBLICK: ${GEGEN:-(keiner)} Rohe Idee: nur pruefen bzw. eine risikofreie Probe vorschlagen — Updates bleiben manuell (Entscheid 04.07.)." \ --triage --created-by release-radar --idempotency-key "release-radar-${NEWEST}" --json 2>/dev/null \ | python3 -c ' import json, sys t = sys.stdin.read(); i = t.find("{") try: print(json.loads(t[i:]).get("id", "") if i >= 0 else "") except Exception: print("") ' 2>/dev/null || true)" if [ -n "${IDEE_ID// /}" ]; then echo "### IN DIE IDEEN-QUEUE GELEGT: ${IDEE_ID} — Probe-Pruefung fuer ${NEWEST} (der Specifier uebernimmt)." echo fi fi printf '%s\n' "$NEWEST" > "$STATE" briefkasten "Release-Radar" "Neue hermes-agent-Releases bis $NEWEST (Box: ${INSTALLED_TAG:-?}). ABGLEICH (Analyst: $RICHTER): $ANALYSE ${GEGEN:-(Kein Fremdblick noetig: TREFFER 0)}"