Er sagte, der Job sei angelegt. Den Job gab es nie.


Sie bitten den Agenten, einen Job einzurichten, der jeden Morgen läuft. Er antwortet: angelegt, startet morgen um 8 Uhr. Am nächsten Tag um 8 Uhr passiert nichts. Sie öffnen jobs.json — kein solcher Job. Sie prüfen die Ausführungsprotokolle — leer. Das Log sieht aus, als wäre nie etwas geschehen. Er hat Sie nicht belogen: Er hat das Tool tatsächlich aufgerufen. Das Ergebnis kam nur nie an, weil die Kontext-Kompaktierung diesen ganzen Turn durch eine Zusammenfassung ersetzt hat — und damit gingen sowohl „der Call wurde abgesetzt“ als auch „der Job existiert“ verloren.

Das Üble an dieser Fehlerklasse ist nicht die verlorene Operation. Es ist, dass niemand weiß, dass sie verloren ging. Der Agent glaubt, er habe die Sache nie getan, Sie glauben, er habe sie nie getan, und so geht niemand zurück und repariert es. Das Community-Plugin agent-ledger, diese Woche in den offiziellen Plugin-Katalog aufgenommen, beobachtet genau diese Lücke — und wirft dabei eine allgemeinere Frage auf: Was hat Ihr Agent eigentlich getan, das etwas verändert hat?

Was ein Ghost-Call ist

Zuerst etwas Hintergrund. Wenn sich eine Session ihrer Kontextschwelle nähert, kompaktiert Hermes: Ältere Turns werden durch eine Zusammenfassung ersetzt, damit das Gespräch weitergehen kann. Dieser Mechanismus ist notwendig, hat aber einen Sonderfall. Wenn die Kompaktierung mitten in einem Tool-Call ausgelöst wird, der abgesetzt wurde, dessen Ergebnis aber noch nicht festgeschrieben ist, wird der Turn komplett ersetzt. Die Folgen:

  • Die Operation wurde nie abgeschlossen;
  • kein Fehler, kein Retry, keine Log-Zeile;
  • der Session-Zustand sagt, das Tool sei nie aufgerufen worden.

Das Upstream-Issue #90985 dokumentiert einen echten Fall: Ein cronjob_create wurde um 20:39:07Z abgesetzt, und der Job existierte nirgends — nicht in jobs.json, nicht in executions.db, nicht in cron/output/. Der im Issue beschriebene plausible Ablauf: Ein Turn setzt mehrere Tool-Calls ab, deren Ausgabe den Kontext über die Kompaktierungsschwelle drückt, der Preflight-Check greift, die Historie wird durch eine Zusammenfassung ersetzt, und das Call-Ergebnis verdampft mit ihr. Keine Spur, dass überhaupt etwas versucht wurde.

Bis der Core dieses Fenster schließt (und wohl auch danach), verdient sich ein Ledger seinen Platz, das weiß, was es verloren haben könnte. Das ist die Prämisse von agent-ledger.

Wie es sie erkennt: ein Write-Ahead-Ledger

Das Plugin hängt sich an vier Hooks und zeichnet jeden zustandsändernden Tool-Call in einem lokalen Ledger auf:

Hook Was er tut
pre_tool_call schreibt einen PENDING-Eintrag (Tool, Args, Zeitstempel, Session)
post_tool_call setzt den Eintrag auf CONFIRMED oder vermerkt ERROR:<type>
pre_llm_call gleicht ab: Ein PENDING, das älter ist als seine Kulanzfrist, ist ein Ghost
on_session_start bringt die Bücher beim Session-Start in Ordnung

Erfasst werden nur Tools, die den Zustand ändern: write_file, patch, terminal, cronjob, delegate_task, Kanban-Mutationen und jedes MCP-Tool (erkennbar am Präfix mcp__). Read-only-Tools kosten nichts.

Kulanzfristen gelten pro Tool statt als eine globale Zahl — standardmäßig zwei Minuten, aber 30 Minuten für delegate_task und 15 für terminal, damit lang laufende Calls nicht falsch etikettiert werden. Ein Call, der fälschlich als Ghost gemeldet und später doch bestätigt wird, wird wiederbelebt, statt seine Zeile zu duplizieren. Die Einträge sind SQLite-Inserts im WAL-Modus, laut Autor unter 10 ms, und das Ledger blockiert nie und schreibt nie Argumente um — es ist ein Flugschreiber, kein Tor.

Selbst nachsehen:

sqlite3 ~/.hermes/plugin-data/agent-ledger/ledger.db "
  SELECT ts, tool_name, status, substr(args_json,1,60)
  FROM ledger ORDER BY id DESC LIMIT 20;"

Es gibt vier Status: PENDING, CONFIRMED, ERROR:<type> und GHOST_REPORTED (bereits an den Agenten gemeldet).

Warum es dem Agenten erst im nächsten Turn davon erzählt, statt den Call zu stoppen

Das ist die Designentscheidung, die man klauen sollte.

Wenn ein Ghost erkannt wird, ist dieser Turn vorbei — Sie können nicht zurückgehen und ihn wiederholen. Also schiebt das Plugin die Tatsache in den nächsten Turn: Der pre_llm_call-Hook kann einen context-Wert zurückgeben, der in die User-Nachricht des aktuellen Turns injiziert wird statt in den System-Prompt. Der System-Prompt ist cache-empfindlich; ihn anzufassen invalidiert den Cache. Legen Sie die Warnung in die User-Nachricht, und der Agent lernt mitten im Gespräch, wo er noch handeln kann: Ein Call wurde abgesetzt und nie bestätigt — den Zustand prüfen, bevor man ihm traut.

Die zweite Entscheidung ist ebenso zurückhaltend: Warum verdächtige Calls nicht schon in pre_tool_call ablehnen? Weil Hermes diesen Hook nach einem 30-Sekunden-Timeout fail-closed behandelt — ein langsames Ledger würde so legitime Tools blockieren. Ein Flugschreiber sollte diese Macht nicht haben. Blockieren ist eine separate, optionale Funktion, die weiter unten besprochen wird.

In Betrieb nehmen

Aus dem offiziellen Katalog installieren (der Eintrag ist diese Woche gelandet):

hermes plugins install agent-ledger
hermes plugins enable agent-ledger

Wenn Sie stattdessen die Repository-Version des Maintainers nutzen wollen: Die README klont sie nach ~/.hermes/plugins/agent-ledger/, aktiviert sie und startet das Gateway neu, damit das Plugin in der nächsten Session geladen wird.

Dann den Selbsttest laufen lassen, um die Hook-Signaturen gegen Ihre Version zu prüfen:

python3 selftest.py

Er prüft die Paarung Issue/Bestätigung, die Fallback-Bestätigung anhand des Tool-Namens, die Ghost-Erkennung, die Kontext-Injektion und dass nur mutierende Tools aufgezeichnet werden.

Das Extra: ein OWASP-Posture-Score

Das Plugin liefert außerdem ein Skript, das die aufgezeichnete Aktivität gegen die OWASP Top 10 for Agentic Applications 2026 bewertet:

python3 posture.py                        # das Live-Ledger bewerten
python3 -m posture ~/.hermes/plugin-data/agent-ledger/ledger.db

Allein aus den Ledger-Metadaten deckt es Tool Misuse (ASI02), unerwartete Code-Ausführung (ASI05), kaskadierende Fehler (ASI08) und Rogue Agents (ASI10) ab, plus eine Community-Erweiterung für Excessive Agency mit dem Label ASI11 — die offizielle Liste endet bei ASI10, und der Autor sagt das überall, wo sie auftaucht. Die Bewertung ist nachvollziehbar: score = 100 − Σ severity_weight(finding), mit CRITICAL 25 / HIGH 15 / MEDIUM 8 / LOW 3, Findings werden nie dedupliziert, und die vollständige Fundliste wird immer zurückgegeben, damit Sie sie von Hand nachrechnen können.

Ihr Vertrauensmodell wird klar benannt, und es ist der Satz, den man sich merken sollte: Das Ledger wird von dem Agenten geschrieben, der bewertet wird. Ein Beobachter kann keinen Agenten bewerten, der nicht protokolliert, ein Score von 100 heißt also „nichts Verdächtiges in der aufgezeichneten Aktivität“ — nicht „dieser Agent hat sich gut verhalten“. Nehmen Sie es als Hygienesignal, nicht als Beweis.

Policy-Enforcement: erst mit einem Dry-Run beginnen

Beobachten ist das, was der Core tut; Durchsetzen ist die optionale Schicht, die dieses Plugin ergänzt. Installieren Sie ein Policy-Pack und beobachten Sie ein paar Tage, was blockiert worden wäre:

cp policy.example.json ~/.hermes/plugin-data/agent-ledger/policy.json
echo dry_run > ~/.hermes/plugin-data/agent-ledger/policy_mode   # nur protokollieren, nichts blockieren

Sobald der Bericht Dinge zeigt, die Sie wirklich gestoppt haben wollen, wechseln Sie auf enforce: Passende Calls werden abgelehnt, bevor sie laufen, mit Begründung zurück an den Agenten.

Die Ehrlichkeit des Autors zur Abdeckung sollte man weitergeben: Das Matching arbeitet signaturbasiert über den extrahierten Befehl, behandeln Sie es also als Sicherheitsgurt, nicht als Mauer. Mindestens drei Umgehungen sind bekannt — den destruktiven String mit sh -c zusammensetzen (das Literal erscheint nie im Befehl), Umleitung über Umgebungsvariablen (rm -rf $TARGET) und das Aufteilen einer gefährlichen Aktion in zwei einzeln harmlose Calls. Verlässlich fängt es die Klasse der versehentlichen Katastrophen: rekursive Löschvorgänge, Pipe-to-Shell, Credential-Reads. Genau dafür ist der Dry-Run-Bericht da.

Wo die Daten liegen und wie Sie damit umgehen sollten

  • Das Ledger ist eine lokale Klartext-SQLite-Datenbank unter <hermes_home>/plugin-data/agent-ledger/ledger.db. Es speichert Tool-Argumente bis 4000 Zeichen (Terminal-Befehle, Dateiinhalte, MCP-Argumente) plus einen 300 Zeichen langen Ergebnis-Digest. Jeder mit Dateisystemzugriff kann es lesen — behandeln Sie es wie Ihre Shell-Historie.
  • Ghost-Warnungen sind sessionbezogen: Eine Session sieht nur die eigenen unbestätigten Calls.
  • Das Ledger wird nie automatisch bereinigt. Setzen Sie, falls gewünscht, Ihr eigenes Limit, etwa die neuesten 50.000 Zeilen behalten:
sqlite3 ~/.hermes/plugin-data/agent-ledger/ledger.db \
  "DELETE FROM ledger WHERE id NOT IN (SELECT id FROM ledger ORDER BY id DESC LIMIT 50000)"
  • Für einen täglichen Posture-Bericht wird posture_daily.py von Ihnen geplant, nicht vom Plugin, und es nutzt eigene Bot-Credentials (POSTURE_TELEGRAM_BOT_TOKEN / POSTURE_TELEGRAM_CHAT_ID). Es liest nie die .env von Hermes und verwendet nie den Bot-Token Ihres Agenten erneut.

Wie es zu dem steht, was Hermes bereits tut

Hermes hat eigene Schutzmechanismen rund um geplante Arbeit: welcher Prozess einen Job beansprucht hat, in welchem Zustand er festhängt, wie lange er still ist — die Mechanik haben wir in Mid-Turn-Kompaktierungsintegrität und in der Cron-Referenz behandelt. agent-ledger nähert sich von der anderen Seite: es kümmert sich nicht um den Zustand eines Jobs, sondern darum, ob eine zustandsändernde Aktion ohne Bestätigung abgesetzt wurde, und es deckt weit mehr als Cron ab — jedes mutierende Tool. Die beiden widersprechen sich nicht, und die Installation ändert nichts am Verhalten von Hermes, solange Sie das Enforcement nicht ausdrücklich einschalten.

Die Kurzfassung

  • „Die Operation ist nicht passiert“ und „die Operation ist passiert, aber niemand weiß es“ sind verschiedene Fehler, und der zweite ist oft teurer, weil nichts ihn sichtbar macht.
  • Ein PENDING → CONFIRMED Write-Ahead-Ledger, Kulanzfenster pro Tool und sessionbezogene Kontext-Injektion machen aus einem Ghost-Call eine Zeile Rat: prüfen, bevor man vertraut.
  • Der Posture-Score ist eine nützliche Prüflinse, aber das Ledger wird von der gemessenen Partei geschrieben — lesen Sie ihn als Hygiene, nicht als Urteil.
  • Enforcement ist optional und signaturbasiert, mit den vom Autor dokumentierten Umgehungen. Fahren Sie es zuerst im Dry-Run-Modus und sehen Sie, was es tatsächlich fangen würde.