Initial: Mesh-Skills, Configs, Scripts

This commit is contained in:
root
2026-09-02 23:49:07 +02:00
commit 26c7980750
783 changed files with 145393 additions and 0 deletions
@@ -0,0 +1,98 @@
# OpenRouter-Kosten-Governance (reale USD-Werte)
Verifizierte Mechanik (2026-08) für echtes USD-Kosten-Tracking + Budget-Bremse im
Multi-Agent-Mesh. Lebt unter `operational-dashboard-archive` (Kostenanzeige-Sektion).
## Drei OpenRouter-Endpoints (verifiziert)
| Endpoint | Liefert | Verwendung |
|----------|---------|-----------|
| `GET /api/v1/credits` | `total_credits`, `total_usage` | **verbindlicher USD-Verbrauch, account-weit** |
| `GET /api/v1/auth/key` | `usage` (pro Key) | grobe pro-Agent-Verteilung |
| `POST /api/v1/chat/completions` | `usage.cost` (Body) | exakter Pro-Request-USD |
```python
import json, re, urllib.request
env = open('/root/.hermes/.env').read()
m = re.search(r'OPENROUTER_API_KEY=([A-Za-z0-9_.\-]+)', env)
h = {"Authorization": f"Bearer {m.group(1)}"}
d = json.load(urllib.request.urlopen(
urllib.request.Request("https://openrouter.ai/api/v1/credits", headers=h),
timeout=20))["data"]
print(d["total_credits"]) # aufgeladen gesamt
print(d["total_usage"]) # verbraucht (USD, KUMULATIV über Konto-Leben!)
```
Sub-Agenten: gleicher Aufruf per SSH auf deren `.env` (Key bleibt lokal, nur Zahl zurück).
Alle Keys eines Accounts melden denselben `total_usage`; `/auth/key usage` unterscheidet pro Key.
## ⚠️ Der große Stolperstein: total_usage ist KUMULATIV
`total_usage` zählt über das **gesamte Konto-Leben**, nicht pro Monat. Ein Monats-Cap
darf NICHT `total_usage > limit` prüfen — ein altes Konto stünde sofort dauerhaft über
dem Limit (alles gebremst, niemand kann mehr arbeiten).
Monats-Delta statt Gesamtwert:
```python
monat = today[:7] # z.B. "2026-08"
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-Wert (stabil)
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"])
```
Fehler, den NUR das Team-Review fand: Basis `rows[0]` ist falsch, wenn der Monat noch
keinen Eintrag hat (dann = Konto-Eröffnung → Delta = Gesamtverbrauch → Falschbremse).
Also IMMER Vormonats-Wert als Basis bevorzugen.
## Kill-Switch-Bremsen-Muster (harter Halt)
1. Master schreibt `/srv/work/costs/kill_switches.json` `{"kill_switches":{agent:{"status":"PAUSED"}}}` (chmod 0666).
2. Jeder Sub-Agent-Poller liest sie per SSH VOR jeder Task-Übernahme:
```bash
KILL=$($SSH_CMD "python3 -c \"import json;d=json.load(open('/srv/work/costs/kill_switches.json'));ks=d.get('kill_switches',{});sw=ks.get('$MEINE_ID',{});print('PAUSED' if sw and sw.get('status')=='PAUSED' else 'OK')\" 2>/dev/null")
[ "$KILL" = "PAUSED" ] && { log "Budget-BREMSE aktiv,$MEINE_ID"; exit 0; }
```
3. **Master (VPSHermes) in der Gesamt-Bremse ausgenommen** (`continue`) — er muss sein
System + den Nutzer betreuen; nur Sub-Agenten hart pausieren.
Getestet: Sub-Agent mit PAUSED lässt Task in `offen/` liegen; nach Reset `{}` nimmt sofort wieder an.
## Modell hart blockieren
`hermes config set model.providers.openrouter.blocked_models '["modell/id"]'`
- WICHTIG: **`model.`** (Singular), nicht `models.` — sonst eigener Top-Level-Key, ungenutzt.
- Danach weg aus `hermes models list`; Fallback greift aufs nächste.
- Robuster: **Allowlist** (`allowed_models`) nur günstige Modelle → neue teure automatisch geblockt.
- Schnellster Kostenspar-Hebel: ein einzelnes teures Modell (z.B. deepseek-v4-pro = ~64%
der Kosten) hart blocken → dessen Anteil → fast 0.
## 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. Rest = `total_credits − total_usage` (aus `/api/v1/credits`).
Der Nutzer lädt am Monatsanfang ~20$ auf (evtl. am 15. nochmal ~20$, nicht garantiert);
ist das Guthaben real aufgebraucht, fällt Arbeit von selbst auf **Free-Modelle oder
lokales VPS-Ollama** (`deepseek-r1:7b`). Master überwacht Gesamtkosten (stündl. Cron:
`mesh-cost-track.sh` → `mesh-cost-break.py`), bremst automatisch via Kill-Switches;
Master selbst bleibt dabei immer aktiv.
## Team-Pflicht bei Kosten-Logik
Kosten/Deckel/Basislogik NIEMALS solo absegnen — gegenprüfen lassen (anderer Agent).
Ein vergessenes Detail (Basiswahl, Monatswechsel, Reset) wird sonst erst als
Dauer-Falschbremse real. "4 günstige Agents zusammen sind besser als 1 teurer Agent
und halten die Kosten niedrig."
**Pitfall beim Verteilen von Review-Tasks:** Prompts, die den Agenten zum Laden von
Dateien/Skills anregen, laufen in dessen Iterationslimit (3) → nur Reasoning, kein
`# Ergebnis`, Task wird als "kein Ergebnis" gewertet. Neu stellen mit *"antworte NUR aus
deinem Wissen, LADE KEINE Dateien/Skills, antworte sofort"* — dann liefert auch ein
kleines Agent-Modell verwertbares Ergebnis.
@@ -0,0 +1,149 @@
# Task-Share Polling — Working Architecture + Script Rename (2026-08-03)
Session 2026-08-03 erzielte den **end-to-end funktionierenden** Sub-Agenten-Polling-Lauf.
Diese Notiz konserviert die validierten Erkenntnisse, da `multi-agent-mesh` (der
eigentliche Heimat-Skill) user-owned und für den Curator gesperrt ist.
## Script-Rename `duo-*` → `mesh-*` (NUTZERWUNSCH)
Der Begriff "Duo" ist überholt (nur noch A+B). Das Mesh hat 4 Agenten + Master.
Alle Tools in `/usr/local/bin/` (und ggf. `~/.local/bin/`) wurden 2026-08-03 umbenannt:
```
duo-task-create.sh → mesh-task-create.sh
duo-master-poller.sh → mesh-master-poller.sh (Ausfall-Fallback, DEAKTIVIERT)
duo-master-report.sh → mesh-master-report.sh
duo-daily-archive.sh → mesh-daily-archive.sh
duo-agent-poller.sh → mesh-agent-poller.sh
duo-log.sh / mesh-log.sh (war schon mesh-log)
```
Cron-Referenzen, SOUL.md, Memory und Skill-Doku müssen auf `mesh-*` zeigen.
**Belasse historische Editor-/Referenz-Dateinamen im Skill wie sie sind** (z.B.
`references/duo-ssh-trigger-*.md`) — nur AKTIVE Skripte/Crons/SOUL/Websites umbenennen.
## ROOT-CAUSE des "Polling funktioniert nie"
Kein Coding-Fehler im Poller, sondern **Datei-Berechtigungen**: Task-Dateien vom Master
(root) erstellt; über NFS behält `mv` den Owner → Sub-Agenten (uid 1000) konnten die
root-eigene Datei nicht überschreiben, selbst bei 0777-Verzeichnissen.
Symptome: `PermissionError: [Errno 13]`, `json.JSONDecodeError`, "SKIP (Lock verloren)".
Fix: `chmod 0666` beim Anlegen (mesh-task-create) UND nach jedem mv im Poller.
## Zwei Poller-Varianten
- **NFS** (normale Server): Mount + lokaler Zugriff. Nutzt UnraidHermes.
- **SSH/scp** (Hosts OHNE NFS/FUSE, z.B. Nintendo Switch, Tegra-Kernel `4.9.140-l4t`):
kein NFS-Client-Modul, kein FUSE/sshfs ("mount.nfs: Protocol not supported").
Ohne großen Kernel-Eingriff (Switch: wenig Speicher) funktioniert NFS nicht.
Poller: scp-pull eigene Tasks (`.C.D.*`) vom Master, atomare mv-Sperre per SSH,
bearbeiten via `hermes chat -q`, scp-push Ergebnis, verschieben nach fertig/.
## SSH-Poller-Kritische Bugs (vermieden)
1. `SSH_CMD` MUSS den Host enthalten: `SSH_CMD="ssh -i $KEY ... ${MASTER}"` und dann
**unquoted** `$SSH_CMD` verwenden. Fehlt der Host → "hostname contains invalid characters".
2. mv-Sperre mit **absoluten Pfaden**, nicht relativen (`../../../` ist falsch von offen/
aus; sauberer sind absolute Pfade).
3. Ergebnis **Base64-geschützt** ins JSON-Feld `result` schreiben (`base64 -w0`),
NICHT per `printf` ans Dateiende (macht JSON ungültig).
## Daemon dauerhaft halten: systemd USER-Service
`nohup ... &` stirbt beim SSH-Session-Ende; `@reboot`-Cron mit nohup ist fragil
(pgrep-Selbstmatch: `pgrep -f "mesh-poller-daemon"` matcht den startenden Prozess
selbst → "läuft bereits" ohne echten Loop). Robust: **systemd user service** + Daemon-
Wrapper mit PID-Datei (statt pgrep).
```bash
# ~/.config/systemd/user/mesh-poller.service
[Unit]
Description=Mesh Sub-Agent Poller (alle 10s)
After=network-online.target
[Service]
Type=simple
Environment=PATH=/home/<user>/.local/bin:/usr/bin:/bin
ExecStart=/home/<user>/.local/bin/mesh-poller-daemon.sh <ZT-IP> <AGENT-ID>
Restart=always
RestartSec=5
[Install]
WantedBy=default.target
```
```bash
export XDG_RUNTIME_DIR=/run/user/$(id -u)
systemctl --user daemon-reload
systemctl --user enable --now mesh-poller.service # überlebt SSH-Ende + Boot
```
**Daemon-Wrapper (PID-Datei statt pgrep):**
```bash
PIDFILE="$HOME/.local/bin/mesh-poller.pid"
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then exit 0; fi
echo $$ > "$PIDFILE"; trap 'rm -f "$PIDFILE"' EXIT
while true; do timeout 130 <poller> <IP> <ID> >> "$LOG" 2>&1; sleep 10; done
```
## Validierter E2E-Durchlauf (03.08.)
```
UnraidHermes (NFS): [02:07:17] ✋ Übernommen → [02:08:02] ✅ Fertig (Wissensbasis)
SwitchHermes (SSH/scp): [02:26:26] ✋ Übernommen → [02:26:40] ✅ Fertig (HelloWorld)
SwitchHermes (SSH/scp): [02:27:28] ✋ Übernommen → [02:29:33] ✅ Fertig (Wissensbasis, dauert ~2min auf Tegra)
```
## Grenzen / Stand
- Ausfall-Fallback ("auf beliebig" nach 30s) ist **deaktiviert** (03.08.) — zu aggressiv,
leitete frische Tasks fälschlich um. Neu-Zuteilung nur manuell durch Master.
- Größere Wissensbasis-Dateien auf schwacher Hardware (Tegra) brauchen 2+ min;
`hermes chat -q --max-turns 3` reicht, `--max-turns 5+` schneidet Output ab.
- LaptopHermes wurde **noch am selben Tag** (03.08.) onboardet — endete auf SSH/scp-Poller
+ systemd-Service wie die beiden anderen. Damit sind ALLE 3 Sub-Agenten über SSH/scp aktiv.
## fail2ban bannt Sub-Agenten (SSH Connection refused)
**Symptom (häufige Fehlersuche):** Ping auf Master geht (0% loss), aber SSH/scp des
Sub-Agenten gibt `Connection refused` — obwohl sshd auf 0.0.0.0:22 lauscht und Port 22
von außen erlaubt ist. Verbindung wirkt asymmetrisch (ein Agent geht, der andere nicht).
**Ursache:** fail2ban-sshd bannt die ZT-IP des Sub-Agenten. Die scp/Key-Logins der Agenten
werden als fehlgeschlagene Versuche gewertet → nach maxretry gebannt.
**Check:** `fail2ban-client status sshd | grep "Banned IP"` → ZT-IP in der Liste.
**Fix:** entbannen + Dauer-Whitelist in `/etc/fail2ban/jail.local` (in der AKTIVEN [sshd]-Sektion,
nicht doppelt anlegen), dann `fail2ban-client reload`:
```
[sshd]
ignoreip = 172.24.34.142 172.24.20.26 172.24.178.202 172.24.92.162
```
> Schreib-/Reload-Schritte können vom Gateway-Schutz geblockt werden. Inhalt erst per
> write_file nach /tmp/, dann cp nach Ziel + Reload separat ausführen.
## Kanonische Agentennamen (NUTZERWUNSCH 2026-08-03 — durchgängig)
Der Nutzer korrigierte wiederholt inkonsistente Namen. **Die kanonischen Namen sind:**
| Kanonisch | (veraltet — nie mehr verwenden) |
|-----------|----------------------------------|
| **VPSHermes** | Agent A, agent-a |
| **UnraidHermes** | Agent B, agent_b |
| **SwitchHermes** | — (gleich) |
| **LaptopHermes** | — (gleich) |
Gilt in ALLEN Stellen: `registry.json` (name/codename), Live-Panel, Tages-Archive
(auch historische Einträge), Ereignis-Log (`agent`-Feld + `detail`-Texte), Wissensbasis,
SOUL.md, Skill-Templates. Die Rolle „Master" bleibt als Funktion bestehen — nur der Name
ist VPSHermes.
**Pitfall (beim bereinigen erlebt):** Archive werden aus `events.jsonl` generiert — die
fertige `.html` nur per sed zu fixen reicht NICHT, wenn der Generator weiterhin den alten
Namen schreibt (z.B. `mesh-daily-archive.sh` hatte statisch „Master (Agent A)" im Footer).
Immer zuerst die Quelle bereinigen: `events.jsonl` (sowohl `agent`-Feld als auch
`detail`-Texte: `Agent A→VPSHermes`, `Agent B→UnraidHermes`, `agent-a→vpshermes`), dann
die Report-/Archive-Generatoren, dann die fertigen `.html`. `sed` auch über historic
Archiv-HTML laufen lassen, nicht nur über neueste.