--- name: operational-dashboard-archive description: "Betriebsdoku: Live-Panel + Tages-Archiv per Cron." version: 1.0.0 author: hermes-curator platforms: [linux] models: 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 [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 `.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 | sum kosten_eur`) - Render statisches HTML in `/var/www//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: ```bash 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 `.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). ```bash 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:///.html` ## Ereignis-Log-Konvention Jede Aktion via `mesh-log.sh` loggen, **bevor** das Panel regeneriert wird: ```bash /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: ```json {"aktion":"task_fertig_agent","agent":"","kosten_eur":N,"detail":"Task 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. ```python 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-*`.