Der OOM-Ursacher um 3 Uhr nachts: die verwaisten Prozesse deines Agenten


Der Lüfter des Laptops dreht wieder hoch, localhost:3000 meldet, dass der Port belegt ist, und ps aux zeigt vier node- und python-Prozesse, die du nicht kennst. Irgendwo im Hinterkopf: Letzte Woche hast du den Agenten gebeten, „einen Server zu starten und es auszuprobieren“, die Session endete, der Server nicht. Was diese Überbleibsel eint: Niemand erinnert sich an sie, und niemand führt Buch über sie – und genau das ist die Aufgabe von agent-janitor.

Woher verwaiste Prozesse kommen

Ein Tool-Aufruf eines Agenten sieht ungefähr so aus, als würdest du selbst etwas in ein Terminal tippen. Du bittest ihn, npm run dev im Hintergrund zu starten, einen cloudflared-Tunnel zu öffnen oder einen File-Watcher anzuhängen; der Befehl kehrt zurück, der Prozess läuft weiter. Wenn die Session endet und ihr Terminal verschwindet, hängt Linux den Überlebenden an PID 1 um – das bedeutet ppid == 1, und es ist die verlässlichste „verwaist“-Signatur, die es gibt.

Die Kosten sind konkret: belegte Ports, gefressener RAM, ein OOM um 3 Uhr nachts und beim nächsten Start ein Agent, der seinen eigenen Dienst nicht erreicht. Schlimmer ist der Aufwand bei der Triage – du hast keine Ahnung, wer dieses python app.py gestartet hat.

Es urteilt nach Belegen, nicht nach Bauchgefühl

agent-janitor bewertet Prozesse, statt auf alles Verdächtige zu schießen:

Beleg Sicherheit
An init umgehängt (ppid=1) und hält einen Port / Dev-Server-Merkmale / TTL überschritten bestätigt
Nur an init umgehängt (ppid=1) wahrscheinlich
Dev-Merkmale und TTL überschritten, aber der Parent lebt noch möglich (wird nie automatisch entfernt)

Alles, was es liest, ist lokal: ppid und Kommandozeilen aus /proc, lauschende Sockets aus /proc/net/tcp{,6} und eine Altersschwelle. Nur confirmed und likely sind beendbar.

Zwei Klassen sind namentlich ausgenommen: System-Daemons (multipathd, udisksd, ModemManager, systemd-* und Verwandte) sowie Host- und VM-Management-Agenten (tailscaled, Cloud-Provider-Agenten, amazon-ssm-agent, walinuxagent, google_guest_agent). Deine eigene Infrastruktur trägst du in eine Whitelist ein:

janitor whitelist add ngrok
janitor whitelist add cloudflared
janitor whitelist add tmux

Whitelist-Einträge sind Teilstrings, die gegen die Kommandozeile geprüft werden (Groß-/Kleinschreibung beachtet, Regex-escaped, keine Injection). Ein leeres Array oder eine fehlende Datei bedeutet: nichts ist gewhitelistet.

Installieren, dann erst scannen, bevor du aufräumst

Es steht im kuratierten Katalog (agent-janitor 0.3.1, Community-Tier, erfordert Hermes >= 0.21.6). Installiere per Namen, wenn dein Katalog es führt, sonst aus dem Repository wie in der README beschrieben:

git clone https://github.com/ZidoCode/agent-janitor ~/.hermes/plugins/agent-janitor
hermes plugins enable agent-janitor

Jetzt nur scannen – noch nichts aufräumen:

python3 ~/.hermes/plugins/agent-janitor/janitor_cli.py scan
python3 ~/.hermes/plugins/agent-janitor/janitor_cli.py sweep --ttl-h 6

Der zweite Befehl klingt bedrohlich, ist aber standardmäßig ein Dry Run: Entscheidungen werden ausgegeben und als would_kill protokolliert, nichts stirbt.

Genau eine Datei schaltet echtes Beenden frei – <hermes-home>/plugin-data/agent-janitor/policy_mode, deren Inhalt exakt enforce lauten muss:

echo enforce > ~/.hermes/plugin-data/agent-janitor/policy_mode

Dieses Design lohnt zum Nachbauen: standardmäßig sicher, und der riskante Zustand verlangt eine bewusste, schriftliche Entscheidung.

Bevor du enforce einschaltest, tu diese fünf Dinge

  1. Füll zuerst die Whitelist. Dein Tunnel, Reverse Proxy, tmux, alles „vom Agenten gestartet, aber absichtlich am Leben gelassen“ kommt per janitor whitelist add hinein.
  2. Lies die Dry-Run-Ausgabe als Abschussliste. Genau darauf wird enforce wirken, nicht mehr und nicht weniger.
  3. Wisse, wen es trifft. Die Regel lautet „seit über einer Minute an PID 1 umgehängt, nicht ausgenommen, nicht gewhitelistet“ – es prüft nicht, wer den Prozess gestartet hat. Ein per nohup gestartetes hermes gateway oder ein tmux-Server ist im enforce-Modus Freiwild, solange du es nicht whitelistest; als root ausgeführt betrifft das auch Prozesse anderer Nutzer.
  4. Wisse, was es hinterlässt. spawns.jsonl protokolliert Terminal-Befehle, die nach Start von Hintergrundarbeit aussahen, und sweeps.jsonl jede Entscheidung (would_kill oder beendet), sodass du den Sweep hinterher auditieren kannst.
  5. Kenne die Reihenfolge beim Beenden. SIGTERM, zwei Sekunden warten, SIGKILL – und vor dem kill -9 liest es die Kommandozeile erneut und bricht ab, falls die PID wiederverwendet wurde. Kein Brudermord durch PID-Reuse.

Zusammen mit dem Ledger für die Zuordnung

Für sich genommen kann janitor nur sagen: „Dieser Prozess ist verwaist.“ Mit dem agent-ledger desselben Autors installiert, liest es das Ledger und macht aus einem anonymen python app.py ein „gestartet durch genau diesen Tool-Aufruf auf Port 8080, vor 12 Tagen“. Die Zuordnung ist unscharf – sie matcht auf den Basename des Executables –, also behandle sie als Hinweis, nicht als Beweis. Der Autor kennzeichnet es genauso.

Drei Grenzen, die du kennen musst

  • Nur Linux, weil es /proc liest. macOS und Windows werden nicht unterstützt – auf einem Mac gehört dieses Werkzeug auf deine Server, nicht auf den Laptop.
  • Suspend verfälscht die Uptime. Uptime ist boot-relativ und läuft weiter, während die Maschine schläft, also sind TTL-Fehltreffer auf Geräten möglich, deren Deckel man schließt. Auf einem Server – dem eigentlichen Ziel – spielt das keine Rolle.
  • Whitelisting matcht Teilstrings der Kommandozeile, eine gewhitelistete Shell, die danach etwas Gefährliches startet, ist für das Werkzeug unsichtbar.

Um das Problem „ich weiß nicht, was mein Agent getan hat“ allgemein zu verkleinern: janitor deckt die Dimension Prozess ab, Skill-Fingerprinting die Dimension Skill und der Cron-Doctor die geplanten Jobs. Für die allgemeine Routine zum Installieren und Festschreiben siehe den Plugin-Katalog-Leitfaden.