Wenn dein Zweites Gehirn durchdreht: Was ich gelernt habe
Zwei Wochen lief alles. Das System aus den ersten drei Teilen — Hooks die bei jeder Session feuern, ein Compiler der Daily Notes in Wissensartikel verwandelt, ein Content-Scout der Ideen erkennt — das funktionierte genau wie beschrieben, still und zuverlässig im Hintergrund.
Dann habe ich eines Morgens festgestellt, dass die Knowledge Base seit sechs Tagen nicht mehr aktualisiert worden war. Kein Fehler, keine Warnung, einfach Stillstand. Und als ich nachforschte und versuchte die Kompilierung manuell anzustoßen, hat sich das System in eine Prozesskaskade gespawnt, die meinen Rechner in die Knie gezwungen hat.
Das ist die Geschichte davon — und von den fünf Lektionen, die dabei herausgefallen sind.
Was passiert ist
Das Problem kam in zwei Wellen.
Welle 1: Der stille Ausfall. Die Hooks in settings.json — also die automatischen Auslöser, die bei jeder Session feuern — referenzierten eine Umgebungsvariable namens $PROJECT_DIR als Arbeitsverzeichnis. Die Variable war aber leer. Das bedeutete, dass uv run --directory "" bei jedem Hook sofort crashte, bevor auch nur eine Zeile Python lief. Kein Fehler sichtbar im Vault, kein Popup, keine Warnung — nur ein eintöniges error: a value is required for '--directory' im Logfile, das niemand las.
Sechs Tage lang. Bei jeder Session. Dreimal pro Session (SessionStart, SessionEnd, PreCompact). Dutzende gescheiterte Hook-Aufrufe, und ich habe nichts davon bemerkt.
Welle 2: Die Kaskade. Nachdem ich den Pfad-Bug gefixt und die Kompilierung manuell gestartet hatte, kam das eigentliche Problem zum Vorschein. compile.py nutzt das Claude Agent SDK, um für jede Daily Note einen Sub-Agenten zu starten — eine separate Claude-Instanz, die den Kompilierungsauftrag ausführt. Soweit die Theorie.
In der Praxis passierte Folgendes: Jeder Sub-Agent ist technisch gesehen eine eigene Claude-Code-Session. Und jede neue Session triggert die Hooks. Und der SessionStart-Hook prüft, ob es unkompilierte Daily Notes gibt — und startet in dem Fall den Compiler. Der Compiler startet Sub-Agenten. Die Sub-Agenten triggern Hooks. Die Hooks starten den Compiler. Die Sub-Agenten…
compile.py → Sub-Agent → SessionStart-Hook → compile.py → Sub-Agent → ...
Eine rekursive Kaskade. Innerhalb von Minuten liefen dutzende Prozesse parallel — jeder davon machte API-Calls, jeder davon öffnete Dateien, und keiner davon wusste, dass die anderen existierten. Das compile.log wuchs auf 15 Megabyte an. Ich musste alles manuell mit pkill abschießen.
Was ich daraus gebaut habe
Nachdem ich die Prozesse gestoppt und den Schaden inspiziert hatte, war klar: Das System braucht nicht nur einen Fix für den konkreten Bug, sondern grundsätzliche Sicherungen, die verhindern dass so etwas jemals wieder passiert — egal welcher Bug als nächstes auftaucht.
Ich habe ein zentrales safeguards.py Modul geschrieben, das alle KB-Scripts importieren. Es enthält fünf Mechanismen:
1. Der Recursion Guard
Das ist die wichtigste Sicherung und gleichzeitig die einfachste: Jedes Script, das Sub-Agenten spawnen kann (compile.py, flush.py, query.py, lint.py), setzt beim Start eine Umgebungsvariable: CLAUDE_INVOKED_BY. Alle Hooks prüfen diese Variable als Erstes — und wenn sie gesetzt ist, beenden sie sich sofort, ohne irgendetwas zu tun.
# Am Anfang von compile.py, BEVOR irgendein Import passiert:
import os
os.environ["CLAUDE_INVOKED_BY"] = "kb_compile"
# Am Anfang von session-start.py:
if os.environ.get("CLAUDE_INVOKED_BY"):
print(json.dumps({"hookSpecificOutput": {..., "additionalContext": ""}}))
sys.exit(0)
Die Umgebungsvariable wird von Kind-Prozessen geerbt — das heißt, wenn compile.py einen Sub-Agenten startet, erbt der diese Variable, und wenn der Sub-Agent dann Hooks feuert, sehen die Hooks die Variable und brechen ab. Die Kaskade wird an der Wurzel gekappt.
Der Fehler im Original-Code: session-start.py hatte überhaupt keinen solchen Guard. Es war der einzige Hook ohne Recursion-Check, und genau dieser Hook war es, der die Kompilierung im Hintergrund startete und damit die Kaskade auslöste.
2. Der PID-Lock
Ein klassisches Muster aus der Systemprogrammierung: Bevor ein teurer Prozess startet, schreibt er seine Prozess-ID und den Startzeitpunkt in eine Lock-Datei. Jeder andere Prozess prüft diese Datei, bevor er startet — und wenn ein Lock existiert und der Prozess noch lebt, bricht er ab.
Das verhindert, dass zwei compile.py-Instanzen gleichzeitig laufen — egal wie sie gestartet wurden. Zusätzlich hat der Lock eine Altersgrenze: Wenn ein Lock älter als 15 Minuten ist und der zugehörige Prozess noch lebt, wird er als Zombie eingestuft und per SIGKILL beendet.
3. Der Rate Limiter
Das war Michaels Anforderung — also meine eigene, in der dritten Person, weil dieses System mich vor mir selbst schützen muss: Maximal vier API-Calls pro Stunde. Jeder Call wird mit Zeitstempel und Kosten in einer JSON-Datei protokolliert, und vor jedem neuen Call prüft das System ob das Limit erreicht ist.
Vier klingt wenig, aber es reicht: Ein Compile-Durchlauf braucht einen Call pro Daily Note, und selbst bei fünf offenen Notes sind vier Calls pro Stunde eine gesunde Drosselung, die den Prozess über zwei Stunden streckt statt alles auf einmal zu feuern.
4. Das Budget-Cap
Zusätzlich zum Call-Limit gibt es ein Kostenlimit: maximal drei Dollar pro Compile-Run und fünf Dollar pro Stunde. Wenn ein einzelner Daily-Note-Compile mehr als erwartet kostet — etwa weil die Daily Note ungewöhnlich lang ist — stoppt der Prozess, bevor er das nächste File anfasst. Die verbleibenden Files werden beim nächsten Run nachgeholt.
In meinem Fall kostet ein durchschnittlicher Compile-Durchlauf zwischen 20 und 60 Cent pro Daily Note. Das Budget-Cap ist also großzügig genug für Normalfall, aber eng genug um einen Runaway zu stoppen.
5. Der Timeout
Jeder Prozess setzt beim Start einen SIGALRM-Timer. compile.py killt sich nach 15 Minuten, flush.py nach 5 Minuten. Wenn irgendetwas schief geht und ein Prozess hängt, stirbt er verlässlich — und räumt dabei seine Lock-Dateien auf, damit der nächste Run nicht blockiert wird.
Die fünf Lektionen
Was ich aus diesem Vorfall gelernt habe, geht über das rein technische Fix hinaus. Es sind Erkenntnisse über Automatisierung im Allgemeinen — und speziell über Automatisierung mit KI-Agenten.
1. Stille Fehler sind schlimmer als laute Fehler
Sechs Tage lang hat das System bei jeder Session einen Fehler geworfen — und ich habe nichts bemerkt. Der Fehler landete in einem Logfile, das ich nicht routinemäßig prüfe. Es gab keinen visuellen Hinweis, keine Benachrichtigung, keinen Unterschied in meinem Arbeitsablauf.
Das ist das Tückische an Hintergrundprozessen: Wenn sie funktionieren, merkt man nichts. Und wenn sie nicht funktionieren, merkt man auch nichts. Im Nachhinein ist mir klar, dass ich mindestens einen Health-Check brauche — etwas, das mir beim Session-Start sagt: „Achtung, die letzte Kompilierung ist X Tage her“ oder „Die Hooks sind Y Mal gecrasht seit dem letzten Erfolg.“
Das ist das gleiche Prinzip wie bei einem Backup: Ein Backup das nicht getestet wird, ist kein Backup. Und ein automatischer Prozess der nicht überwacht wird, ist keine Automatisierung.
2. Sub-Agenten sind Pandoras Büchse
In meinem System nutze ich das Claude Agent SDK — eine Python-Bibliothek, mit der man Claude Code als Sub-Prozess aufrufen kann. Das ist mächtig: Man kann einem Agenten eine Aufgabe geben, ihm Dateizugriff erlauben, und ihn selbstständig arbeiten lassen. Aber jeder Sub-Agent ist eine vollständige Claude-Session, und jede vollständige Session feuert Hooks.
Das ist kein Bug — es ist das erwartete Verhalten. Aber es bedeutet, dass jedes Script, das Sub-Agenten nutzt, sich aktiv gegen Rekursion schützen muss. Nicht „sollte“, sondern „muss“ — denn ohne Guard ist die Endlosschleife nicht eine Frage des Ob, sondern des Wann.
In der Softwareentwicklung gibt es das Konzept der „Blast Radius“ — wie viel Schaden kann ein einzelner Fehler anrichten? Bei Sub-Agenten ohne Sicherungen ist der Blast Radius unbegrenzt: ein Prozess spawnt Prozesse, die Prozesse spawnen, exponentiell wachsend, bis entweder der Rechner oder das API-Budget aufgibt.
3. Jeder Hintergrundprozess braucht ein Lock und ein Timeout
Das klingt nach einem offensichtlichen Prinzip — und ist es auch, wenn man an Server-Daemons oder Cron-Jobs denkt. Aber bei KI-Agenten vergisst man es leicht, weil sich das Ganze wie ein harmloses Script anfühlt: „Es schreibt doch nur Markdown-Dateien.“ Stimmt — aber es macht dabei API-Calls die Geld kosten, es hält Dateien offen, und es kann sich unter den richtigen Bedingungen vervielfältigen.
Die Faustregel die ich jetzt verwende: Wenn ein Prozess mehr als fünf Sekunden läuft oder mehr als null Dollar kostet, bekommt er einen Lock und einen Timeout. Keine Ausnahme.
4. Rate Limiting ist kein Feature, sondern eine Sicherung
Ich hatte das Rate Limiting ursprünglich als optionalen Luxus betrachtet — „wäre nett, wenn es eine Bremse gäbe.“ Nach dem Vorfall sehe ich es anders: Es ist die letzte Verteidigungslinie, wenn alle anderen Sicherungen versagen.
Selbst wenn der Recursion Guard nicht greift und der Lock irgendwie umgangen wird — der Rate Limiter stoppt die Kaskade nach vier Calls, egal was. Vier Calls, dann ist Schluss. Nicht „nach vernünftiger Prüfung“, nicht „wenn es schlau genug ist aufzuhören“, sondern einfach: vier, fertig, Ende.
In der Praxis hat sich gezeigt, dass die Kompilierung nach zwei Daily Notes gestoppt wurde — der Rate Limiter hat nach vier Calls (zwei Compile-Durchläufe mit jeweils internen Calls) korrekt gebremst. Die restlichen drei Dateien werden beim nächsten Run nachgeholt. Kein Drama, kein Datenverlust, nur eine leichte Verzögerung.
5. Dein System muss sich selbst bremsen können
Die vielleicht wichtigste Erkenntnis geht über das technische hinaus: Wenn du ein System baust, das autonom im Hintergrund arbeitet — egal ob es KI-Agenten sind, Cron-Jobs oder automatische Pipelines — dann muss dieses System in der Lage sein, sich selbst zu stoppen. Nicht du stoppst es, nicht ein Monitoring-Tool, sondern das System selbst.
Das bedeutet: eingebaute Limits, nicht nur externe Überwachung. Lock-Files, nicht nur „ich pass schon auf.“ Budget-Caps, nicht nur „das wird schon nicht so teuer.“ Timeouts, nicht nur „der Prozess wird schon irgendwann fertig.“
Denn die Situationen, in denen etwas schiefgeht, sind genau die Situationen, in denen du nicht da bist um einzugreifen. Das System läuft im Hintergrund, du arbeitest an etwas anderem, und wenn es keine eingebauten Bremsen hat, merkt du das Problem erst wenn das Logfile 15 Megabyte groß ist und dein API-Budget merkwürdig aussieht.
Der Gesamtblick: Zwei Wochen Betrieb
Um die Proportionen zurechtzurücken: Das System hat nicht zwei Wochen lang nicht funktioniert. Es hat acht Tage lang einwandfrei funktioniert, dann sechs Tage stillen Ausfall gehabt, und ist jetzt mit Sicherungen zurück.
In den acht funktionierenden Tagen hat es: – 29 Daily Notes zu 60+ Wissensartikeln kompiliert – Automatisch Kontext in jede Session injiziert – Mehrere Content-Ideen erkannt und in den Ideen-Backlog geschrieben – Diese Blogserie als content-tauglich identifiziert
Die sechs Tage Ausfall haben keine Daten zerstört — nur die automatische Kompilierung pausiert. Die Daily Notes wurden weiterhin manuell geschrieben und von den Hooks korrekt extrahiert (sobald der Pfad-Bug gefixt war). Die Knowledge Base war veraltet, aber nicht kaputt.
Und ironischerweise hat genau dieser Ausfall den besten Content der ganzen Serie geliefert. Denn „alles hat geklappt“ ist langweilig zu lesen, aber „das System hat sich selbst in eine Endlosschleife geschickt und hier ist wie ich es abgesichert habe“ — das ist eine Story die man erzählen kann.
Was ich beim nächsten System anders mache
Wenn ich morgen ein neues automatisiertes System aufsetzen würde — egal ob Knowledge Base, Monitoring-Pipeline oder irgendein anderer Agent der im Hintergrund arbeitet — dann würde ich drei Dinge vom ersten Tag an einbauen, nicht nachträglich:
Lock + Timeout + Rate Limit ab Tag eins. Nicht „wenn es mal Probleme gibt“, sondern sofort, als Teil des Grundgerüsts. Der Aufwand sind 50 Zeilen Python. Die Alternative ist ein Sonntagmorgen mit
pkillund einem 15-MB-Logfile.Einen Health-Check der laut ist. Nicht ein Logfile das man manuell prüfen muss, sondern etwas das aktiv warnt, wenn der letzte erfolgreiche Durchlauf zu lange her ist. In meinem Fall plane ich, beim SessionStart eine kurze Statuszeile zu injizieren: „KB: 60 Artikel, letzter Compile vor 2h, 3 Notes offen.“ Drei Sekunden zu lesen, sofort sichtbar ob etwas nicht stimmt.
Einen bewussten Test des Rekursionsfalls. Bei jedem System das Sub-Agenten oder Hintergrundprozesse nutzt: einmal absichtlich den Recursion-Fall durchspielen und prüfen ob die Guards greifen. In meinem Fall hätte ein einfacher Test —
CLAUDE_INVOKED_BY=test python session-start.py— den fehlenden Guard in drei Sekunden aufgedeckt.
Abschluss der Serie
Mit diesem vierten Teil schließe ich die Serie „Zweites Gehirn mit KI“ vorerst ab. Nicht weil das System fertig wäre — es wird sich weiterentwickeln, und ich bin sicher dass neue Probleme und neue Erkenntnisse kommen werden. Aber der Bogen ist komplett:
- Teil 1: Der Aufbau — woher die Ideen kommen und wie der Vault entsteht
- Teil 2: Das Gedächtnis — die technische Infrastruktur die dafür sorgt dass nichts verloren geht
- Teil 3: Die Content-Maschine — wie das System erkennt was eine Story ist
- Teil 4: Die Lessons Learned — was passiert wenn es schiefgeht und wie man es absichert
Jeder Teil baut auf dem vorherigen auf, und zusammen erzählen sie die Geschichte eines Systems, das mit jeder Session klüger wird — einschließlich der Sessions, in denen es sich selbst zerlegt hat.
Das System läuft jetzt wieder, mit Sicherungen die es vorher nicht hatte. Und der beste Beweis, dass es funktioniert: Genau diese Session — in der ich den Schaden inspiziert, die Ursache gefunden und die Sicherungen eingebaut habe — hat der Content-Scout als blog-tauglich erkannt.
Er hatte recht.





