← Alle Playbooks
Playbook· build

Agenten die abdriften stoppen

Vier Muster wie ein Agent mitten im Lauf entgleist, und zehn Schritte die ihn einfangen bevor er zwei Stunden in die falsche Richtung arbeitet.

Die meisten Anleitungen zu Agent-Qualität schauen sich das Ergebnis an, nachdem der Lauf durch ist. Eval-Sets, Scoring, Regression-Vergleich. Das ist richtig und wichtig, aber es hilft Dir nicht in der Stunde, in der der Agent gerade arbeitet und langsam vom Ziel wegdriftet.

Genau da entsteht der Schaden. Ein Agent, der nach vierzig Schritten das ursprüngliche Ziel nicht mehr im Kontext hat, produziert weiter fleißig Output. Er wirkt beschäftigt. Er meldet am Ende sogar "fertig". Nur hat er die letzten dreißig Schritte an etwas gearbeitet, das keiner bestellt hat.

Hier sind zehn Schritte, mit denen Du das während des Laufs abfängst statt danach. Alles über Claude Code Hooks, alles ohne zusätzliches Framework.

1. Die vier Muster benennen können

Bevor Du irgendetwas baust, musst Du wissen wonach Du suchst. In der Praxis sind es vier Muster, und sie brauchen unterschiedliche Gegenmittel.

Die Schleife: der Agent ruft dasselbe Werkzeug mit fast identischen Argumenten immer wieder auf. Test schlägt fehl, kleine Änderung, Test schlägt fehl, kleine Änderung. Nach zwölf Runden ist er keinen Schritt weiter.

Der Stillstand: viele Tool-Calls, aber nichts davon verändert den Zustand. Lesen, suchen, wieder lesen. Sieht nach Arbeit aus, ist aber Kreisen.

Der Zielverlust: der Agent hat unterwegs ein Nebenproblem gefunden und arbeitet jetzt daran. Oft ein echtes Problem. Nur eben nicht Deins.

Das Schein-Fertig: der Agent meldet Vollzug, ohne dass irgendetwas verifiziert wurde. Kein Build, kein Test, kein Blick auf das Ergebnis. Das ist das teuerste Muster, weil Du es erst merkst wenn Du selbst nachschaust.

2. Das Ziel schriftlich festnageln bevor der Lauf startet

Ein Agent kann nicht gegen ein Ziel geprüft werden, das nur in Deinem Kopf und im ersten Prompt steht. Nach genug Kontext-Kompaktierung ist der erste Prompt nicht mehr wörtlich da.

Also schreib es raus. Eine Datei, drei Zeilen, bevor der Lauf losgeht:

cat > .claude/ziel.txt <<'EOF'
ZIEL: Login-Flow auf Magic-Link umstellen, bestehende Passwort-Logins bleiben gültig.
FERTIG WENN: npm test grün, /login manuell einmal durchgeklickt.
NICHT TEIL DER AUFGABE: Design, Rate-Limits, Passwort-Reset.
EOF

Die dritte Zeile ist die wichtigste. Zielverlust passiert fast immer über ein Nebenproblem, das plausibel aussieht. Wenn Du vorher aufschreibst was ausdrücklich nicht dazugehört, hast Du später etwas, wogegen Du prüfen kannst.

3. Ein Schrittbudget setzen

Jeder Lauf bekommt eine Obergrenze an Tool-Calls. Nicht als Kostenbremse, sondern als Reißleine. Ein Agent, der für eine überschaubare Aufgabe hundertzwanzig Werkzeuge anfasst, hat kein Werkzeug-Problem, er hat ein Verständnis-Problem.

Ein PreToolUse-Hook zählt mit. Die session_id kommt als Feld in jedem Hook-Input mit, damit hast Du einen sauberen Zähler pro Lauf:

#!/usr/bin/env bash
# ~/.claude/hooks/schrittbudget.sh
input=$(cat)
sid=$(echo "$input" | jq -r '.session_id')
datei="/tmp/schritte-$sid"
n=$(( $(cat "$datei" 2>/dev/null || echo 0) + 1 ))
echo "$n" > "$datei"

if [ "$n" -gt 120 ]; then
  echo "Schrittbudget 120 erreicht. Fass den Zwischenstand zusammen statt weiterzumachen." >&2
  exit 2
fi
exit 0

Exit-Code 2 ist der Weg, mit dem ein Hook blockt und dem Modell gleichzeitig sagt warum. Die genaue Wirkung von Exit-Code 2 unterscheidet sich je nach Event, die Doku hat dafür eine eigene Tabelle.

Hundertzwanzig ist ein Startwert, kein Naturgesetz. Miss zwei Wochen mit was Deine normalen Läufe brauchen, dann setz die Grenze auf das Doppelte des Medians.

4. Wiederholungen zählen statt sie zu erahnen

Für die Schleife brauchst Du keinen klugen Detektor. Es reicht, den Werkzeugnamen plus einen kurzen Hash der Argumente zu speichern und zu zählen wie oft dieselbe Kombination auftaucht.

sig=$(echo "$input" | jq -rc '{t:.tool_name, i:.tool_input}' | sha1sum | cut -c1-12)
treffer=$(grep -c "^$sig$" "/tmp/sig-$sid" 2>/dev/null || echo 0)
echo "$sig" >> "/tmp/sig-$sid"

if [ "$treffer" -ge 4 ]; then
  echo "Dieser Aufruf lief bereits $treffer mal identisch. Ändere den Ansatz oder frag nach." >&2
  exit 2
fi

Viermal derselbe Aufruf ist in echter Arbeit selten und in einer Schleife der Normalfall. Bei Werkzeugen, die naturgemäss oft gleich aufgerufen werden, nimmst Du die aus der Zählung raus.

5. SubagentStop als Kontrollpunkt nutzen

Wenn Du mit Subagenten arbeitest, ist das Ende jedes Subagenten der beste Moment für eine Prüfung. Der Lauf ist abgeschlossen, das Ergebnis liegt vor, und der Hauptlauf hat es noch nicht übernommen.

SubagentStop bekommt dafür alles was Du brauchst: stop_hook_active, agent_id, agent_type, agent_transcript_path und last_assistant_message. Über agent_type kannst Du unterschiedlich streng prüfen, ein Recherche-Subagent braucht andere Kriterien als einer der Code schreibt. Über agent_transcript_path kommst Du an den kompletten Verlauf, falls die letzte Nachricht allein nicht reicht.

6. Den Fertig-Claim gegen den Anker prüfen

Der Stop-Hook ist Deine letzte Schranke vor dem Fertig-Melden. Hier prüfst Du das teuerste Muster, das Schein-Fertig.

Blocken geht über JSON auf stdout:

#!/usr/bin/env bash
# ~/.claude/hooks/fertig-gate.sh
input=$(cat)

if [ "$(echo "$input" | jq -r '.stop_hook_active')" = "true" ]; then
  exit 0
fi

if [ -f /tmp/verify-ok ]; then exit 0; fi

ziel=$(cat .claude/ziel.txt 2>/dev/null)
jq -n --arg z "$ziel" '{
  decision: "block",
  reason: ("Kein Verifikationsnachweis in diesem Lauf. Prüfe gegen den Auftrag:\n" + $z)
}'

Der Punkt ist nicht das Blocken an sich. Der Punkt ist, dass die Begründung den ursprünglichen Auftrag wörtlich zurück in den Kontext trägt. Ein Agent, der nach sechzig Schritten das Ziel verloren hat, bekommt es hier nochmal vorgelegt.

7. Die Endlosschleife im Hook selbst verhindern

Ein Stop-Hook, der blockt, führt dazu dass das Modell weiterarbeitet und danach wieder stoppt. Wenn Dein Hook dann wieder blockt, hängt die Sitzung.

Genau dafür gibt es stop_hook_active. Das Feld sagt Dir, dass der aktuelle Durchlauf bereits durch einen blockenden Stop-Hook ausgelöst wurde. Im Beispiel oben ist das die erste Abfrage, und sie gehört in jeden Stop- und SubagentStop-Hook den Du schreibst.

Zweite Regel aus der gleichen Familie: Hooks scheitern nach außen offen. Wenn jq fehlt oder die Ankerdatei weg ist, soll der Hook mit 0 durchlassen und nicht Deine Arbeit blockieren. Eine Schranke, die bei jedem eigenen Fehler zumacht, schaltest Du nach drei Tagen ab.

8. Die Zwischenebene über TaskCompleted abdecken

Zwischen einzelnem Tool-Call und Sitzungsende liegt die Ebene der Aufgaben. TaskCreated und TaskCompleted decken sie ab, und TaskCompleted hat eine eigene decision control.

Das ist die passende Stelle für die Frage "hat diese Teilaufgabe überhaupt etwas mit dem Auftrag zu tun". Nicht jede Teilaufgabe muss verifiziert sein, aber jede sollte sich auf eine Zeile in Deiner Zieldatei zurückführen lassen. Die genaue Ausgabe-Form für diese decision control steht in der Hooks-Referenz, sie unterscheidet sich von der des Stop-Hooks.

9. Zurückholen statt nur blocken

Ein Block allein sagt "nein". Er sagt nicht "hier lang". Für Zielverlust ist das zu wenig, weil der Agent seinen Nebenpfad ja für sinnvoll hält.

Hooks können Text in den Kontext geben, statt nur abzubrechen. Die Felder dafür heißen additionalContext und systemMessage. Damit schiebst Du bei Verdacht auf Abdriften die Zieldatei und die Nicht-Teil-der-Aufgabe-Zeile aktiv nach.

Ein Detail, das man sonst schmerzhaft lernt: Hook-Ausgaben sind bei 10.000 Zeichen gekappt, additionalContext und systemMessage eingeschlossen. Wer da den halben Transkript-Verlauf reinschiebt, verliert das Ende stillschweigend. Drei Zeilen Ziel reichen völlig.

10. Scharf schalten, aber nicht überall

Wenn Du alle diese Schranken dauerhaft an hast, bremst Du Dich bei jedem Zweizeiler aus. Das hält keiner durch, und was keiner durchhält wird abgeschaltet.

Mach es zweistufig. Der Schrittzähler und der Wiederholungszähler laufen immer mit, die kosten nichts und blocken nur im echten Ausnahmefall. Das Fertig-Gate schaltest Du bewusst scharf, wenn ein Lauf groß oder riskant ist. Ein kleiner Schalter reicht:

# scharf: touch /tmp/gate-an     entwaffnen: rm -f /tmp/gate-an
[ -f /tmp/gate-an ] || exit 0

Und leg Dir einen Notaus dazu, eine Umgebungsvariable die alles durchlässt. Es kommt der Tag, an dem eine Schranke falsch greift und Du gerade keine Zeit hast sie zu reparieren.

Was Du danach hast

Vier Muster, gegen die Du benannte Gegenmittel hast statt eines vagen Gefühls. Ein Auftrag, der schriftlich existiert und deshalb prüfbar ist. Zwei Zähler, die im Hintergrund mitlaufen. Ein Gate, das Fertig-Meldungen ohne Nachweis nicht durchlässt.

Was das nicht ersetzt: die Eval-Schicht nach dem Lauf. Die beiden Dinge arbeiten an verschiedenen Enden. Eval sagt Dir ob Dein Agent über viele Läufe hinweg besser oder schlechter wird, die Schranken hier sagen Dir ob dieser eine Lauf gerade entgleist. Wenn Du die Eval-Seite noch nicht hast, ist das Playbook Agent-Eval in 60 Minuten der passende nächste Schritt.

Und wenn beim Aufsetzen gar nichts feuert, liegt es fast nie am Konzept und fast immer am Zusammenspiel von Matcher, Pfad und Rechten. Dafür gibt es Hooks debuggen wenn nichts feuert.

Quellen

Alle hier verwendeten Hook-Events und Felder stammen aus der offiziellen Referenz und wurden am 05.08.2026 gegengeprüft: Claude Code Hooks reference. Dort stehen auch die Tabelle zum Verhalten von Exit-Code 2 je Event und die genauen Ausgabe-Formen der decision controls.