← Alle Playbooks
Playbook· build

Frenar a los agentes que se desvían

Cuatro patrones de cómo un agente descarrila a mitad de ejecución, y diez pasos que lo atrapan antes de que trabaje dos horas en la dirección equivocada.

La mayoría de las guías sobre calidad de agentes miran el resultado cuando la ejecución ya ha terminado. Conjuntos de eval, scoring, comparación de regresiones. Todo eso está bien y hace falta, pero no te sirve de nada en la hora en la que el agente está trabajando justo ahora y se aleja poco a poco del objetivo.

Ahí es donde se produce el daño. Un agente que después de cuarenta pasos ya no tiene el objetivo original en su contexto sigue produciendo output con toda diligencia. Parece ocupado. Al final incluso avisa de que "está listo". Solo que los últimos treinta pasos los ha dedicado a algo que nadie pidió.

Aquí tienes diez pasos para atrapar eso durante la ejecución en lugar de después. Todo con hooks de Claude Code, todo sin ningún framework adicional.

1. Saber nombrar los cuatro patrones

Antes de construir nada tienes que saber qué estás buscando. En la práctica son cuatro patrones, y cada uno necesita su propio antídoto.

El bucle: el agente llama a la misma herramienta con argumentos casi idénticos una y otra vez. El test falla, cambio pequeño, el test falla, cambio pequeño. Después de doce rondas no ha avanzado ni un paso.

El estancamiento: muchas llamadas a herramientas, pero ninguna cambia el estado. Leer, buscar, volver a leer. Parece trabajo, pero es dar vueltas.

La pérdida del objetivo: el agente ha encontrado por el camino un problema secundario y ahora trabaja en eso. A menudo es un problema real. Solo que no es el tuyo.

El falso listo: el agente anuncia que ha terminado sin que se haya verificado nada. Ni build, ni test, ni una mirada al resultado. Es el patrón más caro, porque solo te enteras cuando lo compruebas tú.

2. Fijar el objetivo por escrito antes de arrancar

Un agente no se puede contrastar contra un objetivo que solo existe en tu cabeza y en el primer prompt. Después de suficiente compactación de contexto, el primer prompt ya no está ahí literalmente.

Así que escríbelo. Un archivo, tres líneas, antes de que empiece la ejecución:

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

La tercera línea es la más importante. La pérdida del objetivo casi siempre llega a través de un problema secundario que parece razonable. Si apuntas de antemano lo que expresamente no forma parte del encargo, después tienes algo contra lo que comprobar.

3. Poner un presupuesto de pasos

Cada ejecución recibe un límite máximo de llamadas a herramientas. No como freno de costes, sino como cuerda de emergencia. Un agente que toca ciento veinte herramientas para una tarea abarcable no tiene un problema de herramientas, tiene un problema de comprensión.

Un hook de PreToolUse lleva la cuenta. El session_id viene como campo en cada input de hook, así que tienes un contador limpio por ejecución:

#!/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

El código de salida 2 es la vía con la que un hook bloquea y al mismo tiempo le dice al modelo por qué. El efecto exacto del código 2 varía según el evento, la documentación tiene una tabla propia para eso.

Ciento veinte es un valor de partida, no una ley natural. Mide durante dos semanas lo que necesitan tus ejecuciones normales y luego pon el límite en el doble de la mediana.

4. Contar las repeticiones en vez de intuirlas

Para el bucle no necesitas ningún detector inteligente. Basta con guardar el nombre de la herramienta más un hash corto de los argumentos y contar cuántas veces aparece la misma combinación.

# -S ordena las claves: dos llamadas semánticamente iguales deben dar el
# mismo hash, si no el bucle se cuela.
# jq primero solo: dentro de una tubería su error se perdería y sha1sum
# haría el hash de la entrada VACÍA. Todas las llamadas rotas compartirían
# una firma y se bloquearían como bucle por error.
payload=$(echo "$input" | jq -cS '{t:.tool_name, i:.tool_input}') || exit 0
# sha1sum gibt es nicht ueberall (auf macOS heisst es shasum). Und ein leerer
# Hash waere schlimmer als kein Schutz: alle Aufrufe saehen gleich aus.
hash_cmd=$(command -v sha1sum || command -v shasum) || exit 0
sig=$(printf '%s' "$payload" | "$hash_cmd" | cut -c1-12)
case "$sig" in [0-9a-f][0-9a-f]*) ;; *) exit 0 ;; esac
state="/tmp/sig-$sid"

# Racha, no total: contar solo llamadas idénticas CONSECUTIVAS.
read -r last count < "$state" 2>/dev/null || { last=""; count=0; }
if [ "$sig" = "$last" ]; then count=$((count + 1)); else count=1; fi
echo "$sig $count" > "$state"

if [ "$count" -ge 4 ]; then
  echo "Dieser Aufruf ist der $count. identische in Folge. Ändere den Ansatz oder frag nach." >&2
  exit 2
fi

Cuatro veces la misma llamada es raro en el trabajo real y es lo normal dentro de un bucle. Las herramientas que por su naturaleza se llaman muchas veces igual, sácalas del recuento.

Un límite que conviene conocer: leer, comparar y escribir el archivo del contador ocurre aquí sin bloqueo. Si dos llamadas se disparan a la vez, las dos leen el mismo estado antiguo y una racha de cuatro puede colarse como una de tres. Para un freno de emergencia eso es asumible, simplemente actúa una llamada más tarde. Si lo necesitas exacto, envuelve el bloque en un flock.

5. Usar SubagentStop como punto de control

Si trabajas con subagentes, el final de cada subagente es el mejor momento para una comprobación. La ejecución está cerrada, el resultado está ahí y la ejecución principal todavía no lo ha asumido.

SubagentStop te da para eso todo lo que necesitas: stop_hook_active, agent_id, agent_type, agent_transcript_path y last_assistant_message. Con agent_type puedes comprobar con distinta severidad, un subagente de investigación necesita otros criterios que uno que escribe código. Con agent_transcript_path accedes al historial completo, por si el último mensaje por sí solo no basta.

6. Contrastar el aviso de "listo" con el ancla

El hook de Stop es tu última barrera antes del aviso de finalización. Aquí compruebas el patrón más caro, el falso listo.

Bloquear se hace con JSON en 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)
}'

Lo importante no es el bloqueo en sí. Lo importante es que el motivo devuelve al contexto el encargo original palabra por palabra. Un agente que ha perdido el objetivo después de sesenta pasos lo tiene aquí otra vez delante.

7. Evitar el bucle infinito dentro del propio hook

Un hook de Stop que bloquea hace que el modelo siga trabajando y que después vuelva a parar. Si tu hook bloquea otra vez en ese momento, la sesión se queda colgada.

Justo para eso existe stop_hook_active. El campo te dice que la pasada actual ya fue provocada por un hook de Stop bloqueante. En el ejemplo de arriba es la primera consulta, y pertenece a todos los hooks de Stop y SubagentStop que escribas.

Segunda regla de la misma familia: los hooks fallan hacia fuera, es decir, dejando pasar. Si falta jq o el archivo del ancla ha desaparecido, el hook debe dejar pasar con 0 y no bloquear tu trabajo. Una barrera que se cierra con cada error propio es una barrera que desactivas a los tres días.

8. Cubrir el nivel intermedio con TaskCompleted

Entre la llamada suelta a una herramienta y el final de la sesión está el nivel de las tareas. TaskCreated y TaskCompleted lo cubren, y TaskCompleted tiene su propio decision control.

Ese es el sitio adecuado para la pregunta "¿esta subtarea tiene algo que ver con el encargo?". No hace falta verificar cada subtarea, pero cada una debería poder remitirse a una línea de tu archivo de objetivo. La forma de salida exacta para este decision control está en la referencia de hooks, y es distinta de la del hook de Stop.

9. Reconducir en vez de solo bloquear

Un bloqueo por sí solo dice "no". No dice "por aquí". Para la pérdida del objetivo eso es poco, porque el agente considera que su desvío tiene sentido.

Los hooks pueden meter texto en el contexto en lugar de limitarse a abortar. Los campos para eso se llaman additionalContext y systemMessage. Con ellos, ante la sospecha de desvío, vuelves a empujar activamente el archivo de objetivo y la línea de lo que no forma parte de la tarea.

Un detalle que si no se aprende por las malas: la salida de los hooks se corta a 10.000 caracteres, additionalContext y systemMessage incluidos. Quien mete ahí medio historial de la conversación pierde el final en silencio. Con tres líneas de objetivo sobra.

10. Activarlo, pero no en todas partes

Si dejas todas estas barreras activas de forma permanente, te frenas a ti mismo en cada cambio de dos líneas. Eso no lo aguanta nadie, y lo que nadie aguanta acaba desactivado.

Hazlo en dos niveles. El contador de pasos y el de repeticiones corren siempre, no cuestan nada y solo bloquean en un caso excepcional de verdad. La puerta de "listo" la activas conscientemente cuando una ejecución es grande o arriesgada. Basta con un interruptor pequeño:

# activar: touch "/tmp/gate-an-$sid"   desactivar: rm -f "/tmp/gate-an-$sid"
# Con $sid el interruptor vale SOLO para esta ejecución. Un marcador global
# activaría también todas las sesiones que corran en paralelo.
# Parada de emergencia para el día en que una barrera salte mal: GATE_AUS=1
[ "${GATE_AUS:-0}" = "1" ] && exit 0
[ -f "/tmp/gate-an-$sid" ] || exit 0

La primera línea de comprobación es la parada de emergencia: una variable de entorno que lo deja pasar todo. Llegará el día en que una barrera salte cuando no debe y tú no tengas tiempo de repararla. Entonces pones GATE_AUS=1 y sigues trabajando. Lo único que importa es que esa comprobación esté arriba del todo, antes que las demás, porque si no te falla justo cuando la necesitas.

Lo que tienes después

Cuatro patrones para los que tienes antídotos con nombre, en vez de una sensación vaga. Un encargo que existe por escrito y por eso se puede comprobar. Dos contadores corriendo de fondo. Una puerta que no deja pasar avisos de "listo" sin prueba.

Lo que esto no sustituye: la capa de eval posterior a la ejecución. Las dos cosas trabajan en extremos distintos. El eval te dice si tu agente mejora o empeora a lo largo de muchas ejecuciones, las barreras de aquí te dicen si esta ejecución concreta está descarrilando ahora mismo. Si todavía no tienes el lado del eval, el playbook Eval de agente en 60 minutos es el siguiente paso adecuado.

Y si al montarlo no salta absolutamente nada, casi nunca es cosa del concepto y casi siempre de la combinación de matcher, ruta y permisos. Para eso está Depurar hooks cuando no salta nada.

Fuentes

Todos los eventos y campos de hooks usados aquí proceden de la referencia oficial y se verificaron el 05.08.2026: Claude Code Hooks reference. Ahí está también la tabla sobre el comportamiento del código de salida 2 en cada evento y las formas de salida exactas de los decision controls.

Frenar a los agentes que se desvían — StudioMeyer Academy