Cómo llevo agentes hoy · hooks, specs, subagentes

Harness Engineering: el código que rodea al agente

Un LLM es no-determinista, así que las reglas críticas tienen que vivir fuera del agente, en código que el agente no puede reescribir.

Dejas a Claude Code trabajando durante una hora mientras estás en otra reunión. Vuelves, miras los commits. ¿Cuánto te fías de ellos?

El harness es todo lo que pones alrededor del agente para que esa pregunta tenga una respuesta tranquila: hooks que validan cada tool call antes de que ocurra, specs que le marcan el problema antes de empezar, sensores que detectan cuándo se sale del carril. Birgitta Böckeler lo llama harness engineering.

Aquí cuento cómo lo monto yo, por pasos.

Para qué sirve un harness #s1

Hace unos años, una parte enorme del día se nos iba en microtareas que dábamos por hechas y casi ni contábamos: hacer pull, crear una rama, levantar el entorno, escribir el boilerplate, mover ficheros, repetir imports. Por encima de todo eso, el grueso del tiempo se nos iba en implementación: teclear el código línea a línea. Hoy ese trabajo se desplaza hacia los extremos: planificación al principio y verificación al final. La parte de teclear se la queda el agente.

Dejas al agente trabajando una hora y vuelve con veinte ficheros tocados. Ya es otro juego. Lo que importa entonces es la vigilancia: quién está mirando lo que hace mientras tú miras otra cosa. Ese alguien es el harness.

Where humans now spend their time in the software cycle Three lanes from left to right: Plan, Implement, Verify. Plan and Verify are wide and labeled human-led, holding global context, decisions, specs and acceptance. Implement is narrower and labeled delegated to agents, with codegen, edits and refactors. An arrow loops back from Verify to Plan when expectations are not met. The cycle changed — humans hold the plan, agents hold the keyboard global context stays on the human side · local execution is delegated PLAN human-led · agent as thinking partner frame the problem decide architecture / trade-offs write specs & acceptance criteria explore adversarially with the agent where the real leverage now lives IMPLEMENT delegated to agents codegen edits refactor scaffolding supervised by the harness VERIFY human-led · agents help diagnose read the diff with intent accept against the spec check long-term consequences capture lessons → back into the harness the other half of the leverage re-plan when the diff misses
Tres carriles de izquierda a derecha: PLAN (ancho, liderado por humanos con el agente como thinking partner: plantear el problema, decidir arquitectura, escribir specs, explorar adversarialmente), IMPLEMENT (estrecho, delegado a agentes: codegen, edits, refactor, scaffolding, supervisado por el harness) y VERIFY (ancho, liderado por humanos con agentes ayudando a diagnosticar: leer el diff, aceptar contra la spec, mirar consecuencias a largo plazo, capturar lecciones). Una flecha discontinua loopea de VERIFY a PLAN cuando el diff no encaja.

Mira el diagrama. Plan y verify son donde tú pasas la mayor parte del tiempo. En plan piensas el problema, decides cómo lo vas a atacar y escribes la spec. En verify lees el diff que el agente acaba de generar y compruebas si encaja con lo que pediste. Cuando algo no encaja, la lección vuelve al harness (un hook nuevo, una regla afilada, un sensor más estricto) para la siguiente vez.

  • Tú llevas el contexto grande. Decisiones de arquitectura, restricciones de negocio, qué pasa con esto dentro de seis meses. Eso no le cabe al agente en la ventana, y aunque le cupiera tampoco es donde le sacas más rendimiento.
  • El agente lleva la ejecución local. Codegen, ediciones, refactors, scaffolding. Tareas acotadas y supervisadas por el harness.
  • El loop se cierra por verify → plan. Si el diff falla, vuelves al plan, lo afilas, y arrancas otra vuelta. El siguiente diff llega mejor planteado de origen.
  • Una hora invertida en afilar una spec o apretar un hook sigue rentando en todas las pasadas siguientes. Por eso le dedico bastante más tiempo del que parece intuitivo.

Empieza por un hook #s2

Un hook es un script de shell. Cuatro líneas, cinco si me apuras. Claude Code lo ejecuta cada vez que el agente quiere llamar a una tool: editar un fichero, ejecutar un bash, lanzar una búsqueda. El script mira la llamada, decide si la deja pasar, y devuelve un código de salida.

Si dentro de la sesión hay varios agentes con roles distintos (uno que planifica, otro que implementa, otro que revisa) el hook es donde decides quién puede hacer qué. ¿El reviewer quiere editar un fichero? El hook lee el JSON que le llega, ve quién está actuando, y o lo deja pasar o devuelve exit 2 para cortar la tool call. El LLM se entera del corte por el error y replanifica.

  • El JSON que recibe el hook trae todo lo que necesitas: tool_name, tool_input, agent_type. Tú escribes la política en bash y jq.
  • ¿Reviewer intentando editar un path protegido? exit 2. Y a otra cosa.
  • Los hooks PostToolUse son la versión simétrica: corren después de la tool. Sirven para loggear, marcar caches dirty, lanzar un indexador, ir guardando un diario de sesión.
  • Viven en el repo, en .claude/hooks/. El agente no tiene cómo evitarlos, y para añadir una regla nueva basta con un par de líneas más en el script.
#!/usr/bin/env bash
# .claude/hooks/pre-tool-use.sh
payload=$(cat)
agent=$(jq -r '.agent_type'              <<<"$payload")
tool=$(jq  -r '.tool_name'                <<<"$payload")
path=$(jq  -r '.tool_input.file_path // empty' <<<"$payload")

# reviewer subagents are read-only
if [[ "$agent" == "reviewer" && "$tool" == "Edit" ]]; then
  echo "reviewer cannot edit $path" >&2
  exit 2
fi
El PreToolUse más simple que puedes escribir: lee el agent_type del payload, aplica una regla, y devuelve exit 2 si la tool call no encaja. Con esto el reviewer ya no puede editar nada, diga lo que diga el modelo.
Hooks identify the actor and block by policy Vertical flow. The LLM decides to call a tool. A PreToolUse hook receives a JSON payload that includes the agent_type, the tool name and the path being touched. The hook applies a policy: if the actor is a read-only subagent and the path is in protected code, exit with code 2 and the tool call is rejected; otherwise the tool executes and a PostToolUse hook can log or react. A hook is your last word, not the LLM's the harness identifies WHO is acting and decides whether the tool runs at all LLM decides to call a tool Edit · Write · Bash · ... PreToolUse hook runs as a shell script — outside the LLM's reach { "tool": "Edit", "agent_type": "reviewer", "path": "src/payments/charge.ts" } policy who is acting? and may they touch this? violates policy exit 2 — blocked stderr → LLM context allowed tool executes PostToolUse fires next the rule lives in code the agent does not control prompts ask politely · hooks decide bindingly
Flujo vertical: el LLM decide llamar a una tool, el hook PreToolUse recibe un payload JSON con tool, agent_type y path, aplica la política, y o rechaza con exit 2 (devuelve un mensaje al LLM) o permite ejecutar la tool (y entonces dispara PostToolUse).

Antes y después de cada paso #s3

Acabas de ver un hook. Lo que viene es el lazo completo del que ese hook es solo una pieza, prestado por Birgitta Böckeler de la teoría de control.

Tienes dos tipos de señal alrededor del agente. Las guías (feedforward) llegan antes de que actúe: le dicen lo que se espera. Los sensores (feedback) actúan después de cada paso: miden lo que ha hecho y le devuelven la información para que corrija. Entre ambas se cierra el lazo.

Closed control loop — guides + agent + sensors A horizontal control loop. On the left, Guides feed the agent before it acts (feedforward). In the middle, the Agent reasons, edits and runs tools. On the right, Sensors observe the result after the action and feed signals back (feedback). A dashed arrow loops from Sensors back to Guides through the bottom, indicating that the sensor signal returns to the agent as observation in the next turn. The diagram emphasises that both sides of the loop are obligatory. Closed loop — both sides are obligatory guides set the signal before · sensors return the signal after before the action after the action Guides feedforward CLAUDE.md · rules skills · ADRs frontmatter schemas aim before acting Agent model + harness reasons edits runs tools acts on the codebase Sensors feedback tests · linters type-check hooks reviewer subagent observe after acting the sensor signal becomes observation on the next turn drop one side → either rules nobody checks, or the same mistake on repeat feedforward-only · feedback-only — both fail differently
Lazo de control horizontal. A la izquierda, Guides alimentan al agente antes de que actúe (feedforward). En el centro, el Agent razona, edita y ejecuta tools. A la derecha, los Sensors observan el resultado y devuelven la señal (feedback). Una flecha discontinua loopea de Sensors a Guides por debajo, indicando que la señal vuelve al agente como observación en la siguiente vuelta. El diagrama subraya que ambos lados del lazo son obligatorios.

Es el patrón del termostato decente: la guía le dice «mantén 21°C» y el sensor mide la temperatura cada minuto. Quítale una de las dos y se queda en un cacharro tonto.

  • Si solo tienes guías: un CLAUDE.md enorme con 200 líneas de reglas y ningún test que las verifique. El agente las lee la primera vez, las olvida en la segunda, y nadie se entera del olvido.
  • Si solo tienes sensores: tests y CI estrictos pero ninguna spec viva. El agente choca diez veces contra la misma regla porque nadie le ha contado el porqué.
  • Los sensores tienen que estar pensados para el LLM. Si un test falla y suelta 800 líneas de stack trace, el modelo se queda con esa basura en el contexto de la siguiente vuelta y se atasca. Te toca diseñar el mensaje del fallo igual que diseñas el assert: dos líneas, una con el problema y otra con una sugerencia accionable.

La foto entera: la matriz #s4

Tenemos el cuándo: guías antes del paso, sensores después. Falta el cómo. Y hay dos formas muy distintas de aplicar un control.

Semántico: texto. El CLAUDE.md, el frontmatter de los agentes, los ADRs. El LLM los lee y decide si los respeta. Útiles, pero confiables solo si el modelo coopera.

Determinista: código que falla solo. Un test, un hook, una validación de schema. Le da igual lo que el modelo haya decidido: si la regla salta, la tool call se corta.

Cuando una regla importa de verdad, va al cuadrante determinista. Cuando es preferible pero no crítica, basta con el semántico. Birgitta Böckeler organiza estos ejes (feedforward × feedback × computational × inferential en su nomenclatura original) en una matriz 2×2.

The four cells of the harness A 2×2 matrix. Rows are Feedforward (before the step) and Feedback (after the step). Columns are Computational (the code decides) and Inferential (the model decides). Each cell carries a combined label in bold and a spoken motto in italics at the bottom: guides · deterministic "this is always done this way", guides · semantic "think about it this way", sensors · deterministic "this is broken — look here", sensors · semantic "this smells off — are you sure?". Each cell also lists concrete instances in monospace. The four cells of the harness Computational Inferential Feedforward Feedback guides · deterministic PreToolUse hooks allowed-tools per agent permission allowlists schema · scope guards "this is always done this way" guides · semantic CLAUDE.md · project rules ADRs · decision records skills · agent frontmatter specs (OpenSpec, etc.) "think about it this way" sensors · deterministic build & tests type-check · lint PostToolUse hooks spec validate "this is broken — look here" sensors · semantic adversarial review reviewer subagent structured diagnostics lesson capture "this smells off — are you sure?"
Las cuatro celdas del harness: matriz 2×2. Filas: Feedforward (antes del paso) y Feedback (después). Columnas: Computational (el código decide) e Inferential (el modelo decide). Cada celda lleva en negrita su etiqueta combinada y cierra con el motto que diría un control de ese tipo: guías · deterministas «esto se hace siempre así», guías · semánticas «piénsalo de esta manera», sensores · deterministas «esto está roto, mira aquí», sensores · semánticos «esto huele mal, ¿seguro?». Cada celda lista además instancias concretas.

Dos ejes. En las columnas: cómo actúa el control: código que falla solo (determinista) o texto que el LLM lee (semántico). En las filas: cuándo actúa: antes del paso (guía) o después del paso (sensor). Cada pieza del harness cae en uno de los cuatro cuadrantes.

  • Guía · Determinista: «esto se hace siempre así». Allowed-tools por agente, hooks PreToolUse, allowlists de permisos, validaciones de schema. Le quitas la llave al agente.
  • Guía · Semántica: «piénsalo de esta manera». Reglas del proyecto, ADRs, skills, frontmatter de agentes, specs. El agente las lee y decide si las respeta.
  • Sensor · Determinista: «esto está roto, mira aquí». Build, tests, type-checks, hooks PostToolUse, validación de specs. Verde o rojo, sin negociar.
  • Sensor · Semántico: «esto huele mal, ¿seguro?». Revisión adversarial, diagnósticos en JSON, subagentes revisores, captura de lecciones. Crítica suave que el modelo puede aprovechar en la siguiente vuelta.
  • Regla práctica: cada vez que una regla te falla en producción, súbela un cuadrante, de semántica a determinista. Un exit 2 corta antes de que el modelo tenga oportunidad de decidir.

Lo que circula por el harness #s5

La matriz ordena los controles. Lo que falta ahora es el tejido conectivo: cómo se hablan los agentes entre sí, qué queda escrito al cerrar la sesión, qué señales se persisten para que el siguiente turno arranque sabiendo dónde estaba. Sin esta capa, el lazo se abre cada vez que cierras la ventana de Claude Code.

What flows through the harness Four horizontal lanes, each a different pattern of state and observability that connects the four control quadrants. Lane 1 (blue): structured outputs — a tester subagent emits a 7-field JSON to the orchestrator, enabling mechanical back-pressure. Lane 2 (yellow): session snapshots — a Stop hook writes a last.md with git status, the active OpenSpec change, dirty markers and drift verdict, which the next session reads on open. Lane 3 (green): passive markers — editing a Swift file fires a PostToolUse hook that touches .codegraph-dirty, a stamp the next turn reads without anything being blocked. Lane 4 (pink): meta-sensors — harness state on disk and operational docs both feed a drift check whose verdict either passes silently or blocks an archive. What flows through the harness the connective tissue between controls, turns and sessions structured outputs back-pressure between agents tester JSON · 7 fields orchestrator re-delegate or pass session snapshots continuity across windows session N Stop hook last.md git · change · dirty · drift session N+1 passive markers state without blocking edit .swift PostToolUse .codegraph-dirty next turn reads meta-sensors second-order Böckeler harness on disk docs state drift check verdict / block
Cuatro lanes horizontales, cada uno un patrón de estado y observabilidad que conecta los controles. Lane 1 (azul): salidas estructuradas: un subagente tester emite un JSON de 7 campos al orchestrator, que activa back-pressure mecánico. Lane 2 (amarillo): snapshots de sesión: un hook Stop escribe un last.md (git status, change activo de OpenSpec, markers dirty, veredicto de drift) que la siguiente sesión lee al abrirse. Lane 3 (verde): markers pasivos: al editar un .swift, un PostToolUse toca .codegraph-dirty, un sello en disco que el siguiente turno lee. Lane 4 (rosa): meta-sensores: el harness en disco y los docs operativos alimentan un drift check cuyo veredicto pasa en silencio o bloquea un archive.

Cuatro piezas pequeñas hacen el trabajo:

  • Salidas estructuradas como mensajes entre agentes. Cuando un subagente tester reporta un fallo, emite un JSON de siete campos: qué test cayó, módulo, severidad, hipótesis de root cause, repro mínimo, ubicación sugerida del fix. El orchestrator lee esa estructura y decide solo si vuelve a la fase de implementación o si pasa a verificación. Eso es back-pressure mecánico: el lazo se cierra sin pasar por el humano.
  • Snapshots al cerrar la sesión. Un hook Stop escribe un last.md con git status, el change activo de OpenSpec, qué ficheros quedaron sucios, y el veredicto del último check de coherencia. La memoria del LLM se evapora cuando cierras la ventana, el snapshot la reconstruye en el siguiente prompt sin que tengas que recordarlo tú.
  • Markers pasivos para señales que no bloquean. Un .codegraph-dirty que se toca cada vez que se edita Swift es eso: un sello en disco que el siguiente turno lee y reacciona. Persiste estado entre operaciones sin bloquear nada. Es el patrón dirty bit de los sistemas distribuidos, aplicado a tu propio repo.
  • Meta-sensores que vigilan al propio harness. Un script semanal comprueba que los hooks declarados en settings coinciden con los scripts que existen en disco. Otro detecta cuando la documentación operativa (CLAUDE.md, ROADMAP) y el estado real de OpenSpec han divergido. Es Böckeler de segundo orden: un sensor sobre los sensores. Sin esta capa, descubres el drift solo cuando un agente actúa raro.
  • Esto es lo que diferencia un harness completo de uno que solo tiene controles sueltos. La matriz te dice dónde colocar cada pieza; estos cuatro patrones te dicen cómo las piezas se hablan, persisten, y se mantienen alineadas cuando tú no estás mirando.

Cómo lo afino en cada proyecto #s6

Los hooks y los permisos cubren la mitad reactiva del harness: lo que pasa mientras el agente trabaja. La otra mitad (la proactiva) son las specs: lo que le das al agente antes de que empiece. Para esa mitad OpenSpec es el primer sitio donde miro.

OpenSpec te da una plantilla seria (proposal, design, tasks, aceptación) y te empuja a generarla, refinarla con un revisor adversarial, y luego ejecutarla. Un buen punto de partida para el cuadrante feedforward · inferential.

Ahora la parte fea: no hay un harness universal. Una app de escritorio nativa en Swift no se vigila igual que un servicio full-stack en TypeScript. El build es más lento, las APIs de la plataforma son más estrictas, los tests pesan más. Y un equipo que toca infraestructura tiene un blast radius muy distinto al de uno que toca UI. Cada repo termina dibujando su propio cableado.

  • Reutiliza la matriz: feedforward + feedback × computational + inferential. Esa descomposición la llevas a todos los proyectos.
  • Las piezas concretas las afinas en cada repo: qué hooks, qué agentes, qué checks, qué specs. Eso depende del lenguaje, del stack y del blast radius del proyecto.
  • Promueve reglas hacia arriba cuando duelan. Cada incidente recurrente es un candidato a hook o sensor nuevo. El harness aprende contigo.
  • Ojo con el drift: los hooks viven en settings, los scripts en disco, las reglas en CLAUDE.md. Pueden desincronizarse en silencio. Un check periódico de coherencia es un sensor sobre los sensores.

Llevo ya un tiempo aplicándolo en los proyectos que llevo de principio a fin: flujos spec-driven, un orchestrator que delega en subagentes tipados, hooks pre/post que imponen quién-puede-hacer-qué y una librería pequeña de skills que sobrevive entre sesiones. El harness termina siendo parte del repo igual que el código fuente. Si lo construyes con agentes, te toca llevarlo y mantenerlo al lado del producto. Si te suena familiar y los hooks te están dando guerra, esa es la conversación que me interesa tener.

Stack

Claude Code Hooks (PreToolUse / PostToolUse) Subagentes OpenSpec Skills ADRs Spec-Driven Development

Enlaces