Files

250 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 <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.<domain>)
`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:
```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.<domain>)
`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).
```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://<site>/<datum>.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":"<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.
```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-*`.