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.
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 yjq. - ¿Reviewer intentando editar un path protegido?
exit 2. Y a otra cosa. - Los hooks
PostToolUseson 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 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.
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.mdenorme 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.
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 2corta 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.
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
Stopescribe unlast.mdcon 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-dirtyque 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óndirty bitde 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.