Files
mesh-configs/skills/devops/operational-dashboard-archive/SKILL.md
T

14 KiB
Raw Blame History

name, description, version, author, platforms, models
name description version author platforms models
operational-dashboard-archive Betriebsdoku: Live-Panel + Tages-Archiv per Cron. 1.0.0 hermes-curator
linux
default
mimo-v2-flash

Operational Dashboard & Archive — autonome Betriebsdokumentation

Nutzeranforderung (2026-08-03): "Alles dokumentieren in Zukunft. Das will ich immer sehen können." + "Tägliche Zusammenfassung als Archiv perfekt." Format: kompakt + informativ, NICHT zu detailliert. Wochenzahlen + Kernereignisse + Ausblick.

Zweck: Aus einem laufenden, dateibasierten Arbeitsordner (Tasks/Agenten/Server) automatisch zwei Websites generieren, ohne manuellen Eingriff:

  1. Live-Panel — Echtzeit-Zustand (Agenten, Tasks, Kosten, Ereignis-Protokoll)
  2. Tages-Archiv — chronologische Tageszusammenfassungen (dauerhaft, pro Datum)

Bausteine

/srv/work/
├── tasks/{offen,in_arbeit,fertig,bugfix,archiv}   # Task-Verzeichnisse (Quelle)
├── log/events.jsonl                                # Ereignis-Log (durchgängig append)
├── wissensbasis.md                                 # verteilte Wissensbasis (ohne Secrets)
└── registry.json                                   # Agenten-/hierarchie-Registry

Kernscripts (in /usr/local/bin/)

Script Funktion
mesh-log.sh <aktion> <detail> [agent] [kosten] Append eine Ereigniszeile (JSONL) — JEDE Aktion loggen
mesh-master-report.sh Regeneriert Live-Panel-HTML aus Tasks + Log (kein PHP nötig, statisch)
mesh-daily-archive.sh Erzeugt Tages-Summary <datum>.html + aktualisiert index

⚠️ Aktueller Präfix: mesh- (seit 2026-08-03). Frühere Referenzen auf duo-* (duo-master-report.sh, duo-daily-archive.sh, duo-task-create.sh, duo-log.sh) wurden auf mesh-* umbenannt — alte Namen existieren nicht mehr. Beim Aufruf IMMER mesh- verwenden.

Live-Panel (dokumentationen.)

mesh-master-report.sh liest:

  • Task-Ordner → Tabelle (Task-ID, Ziel, Typ, Status, Bearbeiter, Kosten, Dauer)
  • events.jsonl → letzte 40 Ereignisse
  • Summiert heutige Kosten (grep <heute> | sum kosten_eur)
  • Render statisches HTML in /var/www/<panel-site>/index.html

Kein PHP/Build nötig — rein Shell+python3-inline. Regenerate per Cron alle 60s (versetzt, damit nicht kollidierend). Für „mehr Action" 30s-Takt (2 Cron-Zeilen mit sleep 30).

🔑 Pitfall: Ereignis-Tabelle bleibt LEER (pipe-in-subshell + set -e)

Symptom: Log hat Dutzende Einträge, aber die Panel-Ereignistabelle zeigt „Noch keine Einträge." Ursache: LOGROWS=$(tail -40 "$LOG" | while ... done) — die while-Loop wird in einer Subshell der Variablen-Zuweisung ausgeführt; unter set -euo pipefail bleibt die Variable leer. Fix: Parsing über ein Tempfile statt pipe-in-subshell:

TMPEV=$(mktemp)
python3 - "$LOG" "$TMPEV" << 'PYEOF'   # liest JSONL, schreibt rows[-40:] nach tempfile
PYEOF
LOGROWS=$(cat "$TMPEV"); rm -f "$TMPEV"

Gleiches Muster für die Kosten-Tabelle (pro-Agent-Summe via Tempfile).

Live-Online-Status (Agentenliste NICHT hartkodieren)

Agenten-Status in der Tabelle sollte bei jeder Regenerierung per ping -c1 geprüft werden (oder SSH). Statisch „offline" eingetragen → falsch, sobald ein gerade onboardeter Agent online ist. Baue die Zeilen dynamisch: ${AGENT_STATUS} pro Agent mit ping → badge online/offline.

Tages-Archiv (archiv.)

mesh-daily-archive.sh (Cron 23:50 täglich):

  1. Filtert events.jsonl auf heute
  2. Statistikkarten: Aktionen, Kosten, fertig/offen/bugfix
  3. Schreibt <YYYY-MM-DD>.html + aktualisiert index.html (Linkliste aller Tage)
  4. Retrospektive Einträge möglich (z.B. Start-Eintrag mit Gesamtüberblick)

🔒 Archiv NUR bei Aktivität + Leertage zusammenfassen (NUTZERWUNSCH 2026-08-07)

Nutzerregel: "Nur bei Aktivität. Wenn ihr als Team was macht und oder ich was anstoße." — und: "Fasse leere Tage einfach zusammen um das Archiv nicht mit zu vielen Leerstand zu füllen."

Umsetzung im mesh-daily-archive.sh (Guard nach ANZAHL-Berechnung, vor dem HTML):

  • Wenn ANZAHL=0 (keine heute-Log-Events) UND FERTIG=0 (keine fertigen Tasks heute) → keine Einzelseite erzeugen, stattdessen Datum in Sammelliste leerstand.json aufnehmen und exit 0.
  • leerstand.json = Liste der Leertage. Der Archiv-Index zeigt sie als EINE kompakte Zeile: "1 Tag(e) ohne Aktivität: 2026-08-02".
  • Sobald ein Tag Aktivität bekommt, bekommt er seine normale Seite (und verschwindet aus der Leerstand-Notiz).
LEERSTAND_FILE="/srv/work/costs/leerstand.json"
if [ "$ANZAHL" = "0" ] && [ "$FERTIG" = "0" ]; then
  python3 - "$LEERSTAND_FILE" "$WERTE_DATUM" << 'PYEOF'
import json, sys
f, heute = sys.argv[1], sys.argv[2]
try: tags = json.load(open(f))
except Exception: tags = []
if heute not in tags: tags.append(heute)
json.dump(sorted(tags), open(f,"w"), ensure_ascii=False, indent=2)
PYEOF
  echo "ℹ️ $HEUTE: keine Aktivität - als Leertag zusammengefasst"; rm -f "$TAGES_EVENTS"; exit 0
fi

Index-Notiz (vor "Alle Tage"): ${LEERSTAND_NOTE}, aus leerstand.json erzeugt — filtert Tage aus, die bereits eine echte Seite haben.

Pitfalls (erlebt):

  • ls /dir/*.json ohne Match → Exit 2 → bricht unter set -euo pipefail ab. Fix: shopt -s nullglob um den Glob legen.
  • ARCHIV_LINKS=$(ls "$WEB"/*.html ... | while read...) bei leerem Verzeichnis: nullglob um die Glob-Expansion, sonst Exit 2.
  • Rohes $(date '+%d.%m.%Y %H:%M:%S') im heredoc eskapt/ersetzt (sed), sonst bleibt der Literal-String im HTML.
  • Nach Erzeugung immer verifizieren: curl -s -o /dev/null -w "%{http_code}" https://<site>/<datum>.html

Ereignis-Log-Konvention

Jede Aktion via mesh-log.sh loggen, bevor das Panel regeneriert wird:

/usr/local/bin/mesh-log.sh "security" "Firewall gehärtet: nur bekannte IPs" agent-a 0
/usr/local/bin/mesh-master-report.sh

Kontext (WAS und WARUM) gehört ins Log — so ist jedes Panel eine lückenlose Historie.

Agent-Selbstdokumentation (NUTZERWUNSCH 2026-08-03)

Jeder Agent soll seine fertigen Tasks selbst dokumentieren — nicht nur der Master. Der Agent-Poller pusht nach ✅ Fertig eine Ereigniszeile auf den Master:

{"aktion":"task_fertig_agent","agent":"<AGENT-ID>","kosten_eur":N,"detail":"Task <id> abgeschlossen"}

→ Panel zeigt Aktionen namentlich + Kosten, auch wenn der Agent unabhängig arbeitete. mesh-log.sh bleibt für Master-Aktionen; Agenten appenden direkt ans events.jsonl (via $SSH_CMD "echo '...' >> /srv/work/log/events.jsonl").

Kostenanzeige (Budget-Kontrolle)

  • Hartes Tagesbudget 1€/Agent
  • Panel summiert heutige Kosten aus events.jsonl (Feld kosten_eur)
  • Anzeige als Prominent-Karte: 💰 Heutige Kosten: X € (Budget 1€/Agent/Tag)
  • Kosten pro Agent als eigene kleine Tabelle (pro-Agent-Summe für heute)
  • hält den budgetbewussten Nutzer informiert, ohne Logs lesen zu müssen

⚠️ Pitfall: Panel zeigt 0€ obwohl OpenRouter-Kosten laufen

events.jsonl-Feld kosten_eur bleibt meist 0, weil die echten OpenRouter-Kosten nicht automatisch erfasst werden. Der Nutzer-Test wird "0.000€" im Panel sehen, während real Budget verbraucht wird. Fix = separates mesh-cost-System (getestet 08-07), das die Nutzer-Dashboard-Referenz (USD) + pro-Agent-Usage abbildet.

Echte Kosten-Governance: mesh-cost-System (getestet 2026-08-07)

Zentraler Master pollt die OpenRouter-usage ALLER 4 Agenten (Keys bleiben lokal), schreibt Zeitreihe und erzeugt einen Budget-Bremsen-Mechanismus (kill_switches).

Datei Zweck
/usr/local/bin/mesh-cost-track.sh stündl. OpenRouter-usage je Agent → costs.jsonl
/srv/work/costs/costs.jsonl Zeitreihe {ts, master, unraid, switch, laptop}
/srv/work/costs/budget.yaml Deckel je Agent + Modell + Gesamt
/usr/local/bin/mesh-cost-break.py Usage vs. budget.yaml → kill_switches.json
/srv/work/costs/kill_switches.json zentrale Bremse (0666), von allen Agents gelesen
/usr/local/bin/mesh-cost-report.sh HTML-Fragment Kosten+Bremse-Status (in Panel)

Bremsen-Ablauf: Master vergleicht → setzt PAUSED je Agent → jeder Sub-Agent-Poller liest kill_switches.json (SSH) und übernimmt keine Tasks wenn sein Name PAUSED (eine der Kernregeln: keinen Task bearbeiten → keine Kosten). Guard in mesh-agent-poller.sh (Schritt "0. KILL-SWITCH-BREMSE"), identische Datei auf allen 3 Agents, nur KEY-Pfad je anders.

Panele-Einbindung: mesh-master-report.sh ruft $(bash /usr/local/bin/mesh-cost-report.sh) vor "Betriebsregeln" → zeigt Gesamt-USD + pro-Agent-Usage + grünen/roten Bremse-Status.

Fallstricke:

  • usage ist kumulativ (seit Key-Start), nicht pro Tag → für Tagesdeckel das Tages-Delta rechnen (letzter − erster Wert desselben Kalendertags), sonst wird fälschlich ALLES als über Limit pausiert.
  • usage ≠ USD (OpenRouter-Credits). Verbindlicher Referenzwert = Nutzer-Dashboard (openrouter.ai Activity). Beide anzeigen.
  • Detail-API /api/v1/generation gibt 400 (abgeschaltet) — nur Gesamt-usage je Key ist zuverlässig abrufbar.
  • Kronen-Guard: Agent-Namen im JSON = "UnraidHermes"/"SwitchHermes"/"LaptopHermes" (exakte Namen, nicht IP), Vergleich gegen $MEINE_ID.

✅ Nachgezogen 2026-08-08 — echte USD-Quelle + Monats-Delta + harter Gang

Echte USD-Quelle (besser als usage): GET https://openrouter.ai/api/v1/credits liefert {total_credits, total_usage} — total_usage ist der verbindliche USD-Verbrauch (account-weit). Alle 4 Agenten-Keys melden denselben account-weiten total_usage; usage aus /api/v1/auth/key unterscheidet noch die grobe pro-Key-Relation.

⚠️ KRITISCH: total_usage ist KUMULATIV über das ganze Konto-Leben, NICHT pro Monat. Ein Monats-Cap darf NICHT total_usage > limit prüfen (altes Konto → dauerhaft gebremst, niemand kann mehr arbeiten). Stattdessen Monats-Delta = letzter Wert − Basis, wobei die Basis am robustesten der letzte Eintrag des VORMONATS ist. Die zunächst getestete Basis rows[0] ist FEHLERHAFT: bei fehlendem Vormonats-Eintrag würde rows[0] = Kontostart genommen → Delta = Gesamtverbrauch → sofortige Falschbremse. Dieser Bug wurde nur durch Team-Review (SwitchHermes basix-danger) gefunden — Beleg dafür, dass Kostenlogik IMMER gegenprüfen lassen, nie solo absegnen.

monat    = today[:7]
vormonat = monat[:5] + str(int(monat[5:7]) - 1).zfill(2) if int(monat[5:7]) > 1 else ""
basis = None
if vormonat:
    vm = [r for r in rows if r["ts"][:7] == vormonat]
    basis = vm[-1]                      # letzter Vormonats-Eintrag = stabile Basis
if basis is None:
    ma = [r for r in rows if r["ts"][:7] == monat]
    basis = ma[0] if ma else rows[0]
monats_delta = max(0.0, rows[-1]["total_usage_usd"] - basis["total_usage_usd"])

NUTZER-BUDGET-MODELL (FINAL, 2026-08-08 — ersetzt früheres 20$/Monat-Modell): Die einzige harte Grenze ist 1$ pro Tag und Agent. Solange OpenRouter-Credits (Guthaben) vorhanden sind, wird weitergearbeitet — KEIN Team-Monats-Stopp, KEIN hartes 20$-Cap als Abrisskriterium. Der Nutzer lädt am Monatsanfang ~20$ auf (evtl. am 15. nochmal ~20$, nicht garantiert); ist das Guthaben real aufgebraucht, fällt Arbeit automatisch auf Free-Modelle oder lokales VPS-Ollama (deepseek-r1:7b). Buchführung: total_credits − total_usage (aus /api/v1/credits) = Rest. In der Bremse wird der Master (VPSHermes) ausgenommen (continue) — er muss sein System und den Nutzer weiter betreuen; nur Sub-Agenten hart pausieren.

Modell hart blockieren (schnellster Kostenspar-Hebel): hermes config set model.providers.openrouter.blocked_models '["modell/id"]'

  • ACHTUNG: Key ist model. (Singular), nicht models. — sonst wird er nur als eigener Top-Level-Key gespeichert, den Hermes nicht liest.
  • Danach ist das Modell aus hermes models list verschwunden, Fallback greift aufs nächste.
  • Noch robuster: Allowlist (allowed_models) mit nur günstigen Modellen — neue teure Modelle automatisch geblockt. (LaptopHermes-Empfehlung)

Ausführliche Mechanik (Endpoints, kill_switch-Guard-Code, Test-Muster) in references/openrouter-cost-governance.md.

Team-Review-Gegenprüfung ist PFLICHT für Kosten/Bremse-Logik (NUTZERWUNSCH, mehrfach betont 08.08): nie solo bauen/absegnen — via mesh-task-create.sh an die Rechen-Agenten verteilen (Unraid/Switch/Laptop), die gegeneinander prüfen. Konkrete Belege: Unraid fand die Tages-Delta-Mitternachtslücke + Counter-Reset; Switch fand die fehlerhafte rows[0]-Basis (das wäre solo übersehen worden). Pitfall beim Verteilen: Review-Prompts, die den Agenten zum Laden von Dateien/Skills anregen, laufen in dessen Iterationslimit (3) → nur Reasoning, kein # Ergebnis-Block, Task landet als "kein Ergebnis". Fix: fehlgeschlagene Tasks → bugfix/, neu stellen mit "antworte NUR aus deinem Wissen, LADE KEINE Dateien/Skills, antworte sofort" — dann kommt auch bei kleinen Agente-Modellen ein verwertbares Ergebnis.

Test-Muster (verifiziert): kill_switches → {"UnraidHermes":{"status":"PAUSED"}}; Test-Task für Unraid bleibt in offen/; Poller-Log ⏸️ Budget-BREMSE aktiv; Reset {} → nimmt wieder an.

Verifikationsschleife (immer nach Änderung)

  1. curl auf Panel + Tagesseite → HTTP 200
  2. Aus dem HTML per grep -oP den erwarteten Sektions-Titel prüfen

Referenzen

  • Session 2026-08-03 (Erstaufbau: Doku-System + Archiv). Mesh-/Task-spezifische Details (IP-Schema, Polling, NFS, Firewall) liegen im multi-agent-mesh-Skill.
  • references/task-share-polling-and-rename.md — Validierter Polling-Ablauf (NFS + SSH/scp-Variante, 0666-Fix, systemd user service), + Script-Rename duo-*→mesh-*.