PRUEFE… Verbinde mit dem Backend …
SCHRITT 1

Wo die Jobs liegen — und in welchem Format

Jedes Hermes-Profil hat sein eigenes Verzeichnis. Die Jobs stehen in JSON, nicht in YAML. Eine Datei pro Profil:

ProfilDateiJobs (gemessen)
default~/.hermes/cron/jobs.json—
hermes-system-linux~/.hermes/profiles/hermes-system-linux/cron/jobs.json—
hypdb-creator~/.hermes/profiles/hypdb-creator/cron/jobs.json—
jarvis~/.hermes/profiles/jarvis/cron/jobs.json—
Gesamt4 Dateien—

Innerhalb einer Datei ist es ein Objekt mit jobs (Array) und updated_at:

{ "jobs": [ { "id": "e566fc448a8c", "name": "router-watchdog", … } ], "updated_at": 2026-10-05T14:50:21.103775+02:00 // gemessen 05.10.2026 in cron/jobs.json; /api/automation fuehrt dieses Feld nicht }
⚠️ FALSCHES FORMAT — DER HAEUFIGSTE FEHLER

Es gibt kein YAML-Format. Die Schluessel goal, context, steps, deliver_to, profile und timeout existieren im echten jobs.json nicht. Eine Anleitung, die sie zeigt, beschreibt eine Datei, die der Scheduler nicht liest — der Job startet nie und der Fehler bleibt still.

Die Aufgabe steckt im Feld prompt. Das Ziel im Feld deliver. Das Zeitlimit in timeout_seconds.

SCHRITT 2

Der Zeitplan: drei Arten, nicht eine

schedule ist ein Objekt mit kind. Genau drei Arten kommen im laufenden System vor:

kindZusatzfeldBedeutungAnzahl (gemessen)
cronexprStandard-Cron-Ausdruck (Min St Tag Mon Woch)—
intervalminutesalle n Minuten, unabhaengig von der Uhrzeit—
oncerun_ateinmalig zu einem Zeitpunkt, danach inaktiv—
Cron-Ausdruck — echter Job, unveraendert uebernommen
"schedule": { "kind": "cron", "expr": "0 6 * * *", // taeglich 06:00 Uhr "display": "0 6 * * *" }, "schedule_display": "0 6 * * *"
Intervall
"schedule": { "kind": "interval", "minutes": 5, "display": "every 5m" }

Die im System tatsaechlich verwendeten Cron-Ausdruecke, ohne Duplikate:

wird aus /api/automation geladen …
💡 INTERVAL VS. CRON

interval richtet sich nicht nach der Uhrzeit. Ein Job mit minutes: 5 laeuft alle 5 Minuten — auch um 03:12 nachts. Fuer eine spaetere Umstellung des Systems (Neustart um 04:00) zaehlt das genaue Intervall. cron ist fuer feste Uhrzeiten.

SCHRITT 3

Aufgabe und Zustellziel

Ein vollstaendiger Job hat rund 35 Felder. Die, die man wirklich setzt, sind diese:

{ "id": "e566fc448a8c", "name": "router-watchdog", "prompt": "Router-Wartung: prueft taeglich, ob alle Modell-Slugs verfuegbar sind. Still bei OK; meldet nur fehlende Modelle.", "script": "router_watchdog.py", "no_agent": true, "schedule": { "kind": "cron", "expr": "0 6 * * *" }, "repeat": { "times": null, "completed": 25 }, "enabled": true, "deliver": "local", "model": "stealth/space-bunny-alpha", "provider": "nous" }

Zwei Betriebsarten, und das ist der wichtigste Unterschied:

💡 REGEL

Ein reiner Statuscheck braucht kein Modell. no_agent: true + script ist schneller, kostenlos und laeuft auch dann, wenn der Modellanbieter nicht erreichbar ist.

Zustellziele (deliver). Nur diese drei Formen kommen vor:

WertBedeutungAnzahl (gemessen)
localNur in der lokalen Ablage — das Ergebnis erscheint im Chat-Verlauf, es wird nichts versendet.38
local,telegram:<chat-id>Lokal und als Telegram-Nachricht an die konkrete Chat-ID.5
local,bot-chat:defaultLokal und im Bot-Chat des Standardprofils.4
(kein Feld)Ein Job ohne deliver-Feld — er steht auf state: "geplant".1

Die Kanaele sind Kommalisten: local steht in allen drei Varianten, die Zustellung ist also additiv.

⚠️ KEIN E-MAIL-KANAL

Es gibt in diesem System keinen E-Mail-Zustellweg fuer Cron-Ergebnisse. deliver kennt genau die drei Formen oben. Eine Anleitung, die „Versand als formatiertes E-Mail-Bericht" anbietet, beschreibt eine Faehigkeit, die nicht existiert.

SCHRITT 4

Zustand lesen — läuft der Job wirklich?

Ein Job kann enabled: true haben und trotzdem jeden Lauf vermasseln. Die Zustandsfelder trennen das:

FeldBedeutung
enabledIst der Job ueberhaupt eingeplant? false = nie laeuft.
last_statusErgebnis des letzten Laufs: ok oder error.
last_errorDie Fehlermeldung des letzten Fehlschlags im Klartext.
failure_streakAnzahl aufeinanderfolgender Fehlschlaege. Der wichtigste Wert.
last_run_at / next_run_atWann er zuletzt lief, wann er als naechstes laeuft.
paused_at / paused_reasonWurde er von Hand angehalten — und warum.
last_dispatchVerspaetung in Sekunden und kind (on_time / late).

Stand des laufenden Systems, bei jedem Seitenaufruf aus /api/automation gezaehlt:

KennzahlWertQuelle
Jobs gesamt—aufgaben.length
davon enabled—aufgaben[].enabled
state = aktiv—aufgaben[].state
state = Fehler—aufgaben[].state
state = pausiert—aufgaben[].state
state = geplant—aufgaben[].state
Erfolgsquote (Serverangabe)—leistung.erfolgsquote_pct
⚠️ DER SERVER ZAEHLT ZWEIMAL — UND BEIDES IST RICHTIG

Die Antwort enthaelt leistung.aktiv — und leistung.fehler —. Die Liste aufgaben[] zaehlt dagegen — aktive und — fehlerhafte Jobs. enabled: true ist etwas anderes als state: "aktiv": ein Job kann eingeplant sein und trotzdem im Fehlerzustand stehen. Beide Zahlen sind dieselbe Quelle — die Aggregate zaehlen die eingeschalteten Jobs, die Liste zaehlt den aktuellen Zustand.

⚠️ FAILURE_STREAK IST DIE WAHRHEIT

Im Profil default steht der Job hypdb-backup-restore auf enabled: false und last_status: error mit failure_streak: 607. Er ist also seit 607 Versuchen fehlgeschlagen — und haette trotzdem weitergelaufen, wenn er aktiviert gewesen waere. Ein deaktivierter Job mit hohem Streak ist kein gesunder Job, sondern ein Totalausfall, der nichts mehr kostet.

Umgekehrt: pdf zeigt failure_streak: 196 (nachgemessen 05.10.2026) bei enabled: false und last_status: ok. Ein alter Streak bleibt stehen, nachdem der Job abgeschaltet wurde — last_status ist die juengere und verlaesslichere Angabe.

SCHRITT 5

Ein Job pruefen, bevor er laeuft

Reihenfolge, die sich bewahrt hat:

  1. Datei pruefen: Existiert jobs.json unter dem Profil? Eine kaputte Datei wird still ignoriert.
  2. JSON pruefen: Ein Syntaxfehler in einer der 46 Jobs verhindert alle Jobs dieser Datei, nicht nur den.
  3. Zeitplan pruefen: kind und das passende Zusatzfeld. kind: cron ohne expr ist ungueltig.
  4. Modell pruefen: Bei Agent-Jobs muessen model und provider zusammenpassen — ein unbekannter Slug endet mit 404 model not found bei jedem Lauf.
  5. Ersten Lauf abwarten und dann last_status, last_error und failure_streak lesen.
💡 STILLER FEHLER IST DER NORMALE FALL

Ein Job mit falschem Zeitplan oder fehlendem Modell meldet sich nicht. Er steht einfach auf enabled: true, macht alle 30 Minuten nichts und liefert dieselbe Fehlermeldung. Deshalb gehoert failure_streak in jede Ueberwachung — nicht nur enabled.

SCHRITT 6

Mehrstufige Ablaeufe

Ein Job kann mehrere Schritte enthalten — aber nicht ueber ein steps-Feld, denn das gibt es nicht. Es gibt zwei Wege:

Weg 1 — Skript (no_agent: true): Die Logik liegt in script. Das Skript kann beliebig viele Schritte machen, speichern und zusammenfassen. Der Job liefert nur das Ergebnis von local.

Weg 2 — Prompt (no_agent: false): Der Auftrag steht als Text im prompt. Auch mehrstufig, aber als Anweisung an das Modell, nicht als Liste von Schritten im Format.

// Weg 1: Skript erledigt die Teilschritte, Agent nicht belastet { "name": "agenten-status-frisch", "script": "agenten_status.py", "no_agent": true, "schedule": { "kind": "interval", "minutes": 5 }, "deliver": "local" } // Weg 2: Modell plant die Teilschritte aus dem Prompt { "name": "Morning briefing", "prompt": "Erstelle ein Morning Briefing: Termine, offene Punkte, ein Vorschlag fuer den Tag.", "schedule": { "kind": "cron", "expr": "0 8 * * *" }, "deliver": "local,telegram:<chat-id>", "model": "stealth/space-bunny-alpha", "provider": "nous" }

Ein Skript-Job muss im Skript-Verzeichnis des Profils liegen. Ein script-Wert, dessen Datei fehlt, laeuft still ins Leere — im System gibt es einen eingebauten Watchdog genau fuer diesen Fall.