Qué es harness engineering y por qué falla tu agente
Tu agente clava la demo y se rompe en producción. No es el modelo: es todo lo que montas alrededor para vigilarlo, corregirlo y frenarlo antes de que mienta. →
Ingeniero de software · Málaga

En este artículo
Respuesta corta: harness engineering es todo lo que montas alrededor del modelo para que el agente no se te vaya: cómo partes el trabajo, dónde guardas el estado, qué comprueba los pasos críticos y qué pilla el fallo antes de que llegue a producción. Cuando un agente se rompe en producción, busca la comprobación que falta antes de cambiar de modelo. El arreglo más barato: no dejes que el agente se corrija a sí mismo. Pon una puerta determinista delante del “hecho”, como el astro check && astro build de este blog, para que un resultado malo falle a lo grande en vez de salir en verde.
Renombré un post de este mismo blog. Trabajo mecánico, del que le pasas a un agente sin pensar: cambias el slug, dejas redirects para que las URLs viejas no mueran, compruebas que el build pasa, deploy. Verde por todas partes. Lo di por hecho.
Semanas después, el post que más tráfico me traía había desaparecido de Google. No caído de posición: fuera del índice. La causa era de chiste. El sitio va con trailingSlash: "always", así que Google tenía guardada la URL con barra final, y los redirects estaban escritos sin barra. Ni uno casaba. Todas las peticiones se colaban hasta el 404, y al build le da exactamente igual lo que piense Google, así que seguía en verde.
Nada me avisó. El sistema siguió tan contento, sirviendo un redirect que llevaba roto desde el primer commit, y me enteré por la caída de tráfico, que es la peor forma de enterarte. Eso, exactamente eso, es lo que mata a los agentes de IA en producción: un plan que parece correcto, una ejecución que se desvía a la mitad, y ningún mecanismo que se dé cuenta. El modelo no tuvo la culpa. No había nada vigilando.
Agente = Modelo + Harness
La frase que me hizo cambiar el chip viene de la gente de LangChain, y es tan corta que parece una tontería: un agente es el modelo más el harness. Casi todo el código que hace que tu agente funcione de verdad en producción, todo lo que no sea la llamada a la API del modelo, es harness.
En el post de los loops usé “harness” de pasada, como el equipamiento: las herramientas, las skills, los MCPs que le das a un agente. Me quedé corto. El harness no es solo lo que el agente puede hacer. Es todo lo que montas alrededor para vigilar lo que hace, atrapar sus fallos, ponerle límites y devolverlo al carril cuando se va. El nombre viene de los arneses de verdad: las riendas, el cable de seguridad, el equipo con el que sujetas algo que se mueve.
La analogía que se me quedó grabada: mandar a un becario a una reunión importante con un cliente. El prompt es la agenda (“saluda, presenta el producto, pregunta por los requisitos”). El contexto es el dossier (“aquí tienes el histórico del cliente y la tarifa”). El harness es todo lo demás: la checklist que se lleva encima, la llamada de control a media reunión que le obligas a hacer, la grabación de la conversación y el criterio durísimo con el que vas a juzgar su informe. Por bueno que sea el briefing, no compensa que no haya nadie comprobando si lo que hizo está bien.
Las tres etapas, y por qué casi todos estamos atascados en la dos
Esto madura en tres saltos, y la mayoría de proyectos que veo viven en el segundo preguntándose por qué les muerden problemas del tercero.
Prompt engineering es donde empiezas. Descubres que el modelo es supersensible a cómo le hablas, y un buen rol con un par de ejemplos transforma la salida. Es real y es útil. Pero choqué con el techo rápido: por bien que redacte la instrucción, no le doy conocimiento que no tiene, ni evito que se invente un dato cuando no encuentra el de verdad.
Context engineering es el siguiente salto, y es el que más gente confunde con “tener un RAG”. Le das al modelo lo que necesita ver justo cuando decide: estado dinámico, salidas de herramientas resumidas, histórico recortado a propósito. Mi agente se volvió mucho más listo cuando dejó de adivinar. Pero el contexto no arregla la deriva de ejecución. Lo de los redirects es justo eso: toda la información del mundo, un plan correcto, y aun así se fue por las ramas con todo verde.
Harness engineering es la tercera, y es la humillante para quien creía que un modelo mejor era la respuesta. Las dos primeras hacen que el modelo piense mejor. La tercera hace que actúe de forma fiable. Cambiando cómo se descomponen las tareas, cómo se guarda el estado y cómo se validan los pasos críticos, el mismo modelo y los mismos prompts pasan de atascarse a algo que de verdad puedes dejar funcionando solo. El corazón de todo es un movimiento al que vuelvo más abajo: separa la parte que genera de la que juzga, y que nunca sean la misma llamada.
Las capas que de verdad cambiaron algo
Hay quien dibuja seis capas perfectas en un diagrama bonito. Te ahorro el diagrama y te cuento las tres que ya viven en mi código, con el commit que las puso ahí.
Una entrada mala tiene que romper el build, no renderizarse rota. En este blog, hasta hace poco, las imágenes de cada post se resolvían por un registro indexado por slug. Si publicabas un post y se te olvidaba registrar su slug, el build seguía en verde y la página renderizaba un upload/undefined: una imagen rota, en producción, sin un solo aviso. Me pasó de verdad con los posts de Fable. La solución no fue acordarme mejor, fue mover la URL al frontmatter y meter un .refine() de Zod en src/content.config.ts que dice que tiene que ser una URL de Cloudinary válida o astro check falla. Ahora una imagen mala no llega a producción: revienta el build en mi cara, que es donde quiero que reviente. En la calculadora de patrimonio que tengo aparte, lo mismo con el dinero: un esquema de Zod acota cada input (years entre 1 y 50, el interés entre -10 y 30) antes de que la simulación toque un solo número. La frontera donde entran datos de fuera es donde tienes que gritar.
El orden de los pasos no lo decide el modelo. Los modelos no fallan tanto por no saber hacer una cosa como por no saber encadenarlas. Se saltan pasos, se saltan la validación, sueltan la salida antes de tener todo lo que necesitan. Por eso el build de este blog no es astro build a secas, es astro check && astro build: el chequeo va primero y, si el esquema rechaza algo, no hay build que valga. En la calculadora el raíl es la CI: lint, luego type-check, luego tests, luego end-to-end, en ese orden y cada uno bloqueando al siguiente. Esos raíles no son adorno. Son sacar la gestión del proyecto de la parte que improvisa y metérsela a la que no.
Un agente que se evalúa a sí mismo miente. Esta es la capa donde se rompen los agentes primitivos. Generan salida, se declaran satisfechos y no tienen forma de saber si lo que hicieron está bien; tienen un sesgo optimista de manual y te dan por bueno un código roto. La respuesta es un evaluador al que el agente no le pueda colar nada. En la calculadora tengo un fichero de tests con un comentario en mayúsculas que escribí precisamente para mí del futuro y para cualquier agente: “DO NOT MODIFY these tests to make them pass”, porque están diseñados para fallar si alguien reintroduce una fórmula financiera incorrecta. Uno comprueba que el retorno real se calcula con la ecuación de Fisher y no con la resta ingenua de inflación, y se niega a pasar si los dos números coinciden. Es un portero que no se deja convencer. “He terminado de generar el texto” y “lo he ejecutado, ha fallado el test de Fisher, lo he arreglado” son dos frases distintas, y el harness es lo que te obliga a la segunda.
La parte honesta: “harness” también es la palabra de moda para esconder un modelo flojo
Tres pegas que me he encontrado yo, por si a ti te ahorran tiempo o tokens.
Primero, casi nada de esto es nuevo. Ese test de integridad financiera y esos esquemas de Zod son ingeniería de software de toda la vida: validar entradas, separar generación de QA, no fiarte de la salida de un proceso sin comprobarla. Llevamos décadas haciéndolo con servicios que no llevan un LLM dentro. Que ahora el componente impredecible sea un modelo no convierte el patrón en una disciplina nueva. Si ya sabes montar sistemas que fallan de forma controlada, ya sabes harness engineering; solo que el becario impredecible ahora habla en inglés.
Y aun así, tres cosas cambian de verdad cuando la parte impredecible es un modelo y no un servicio, y son las que hacen que esto merezca un nombre. Una: el mensaje de error deja de ser un callejón sin salida y pasa a ser una interfaz. Mi .refine("tiene que ser una URL de Cloudinary") ya no es solo un guardia; el agente lee esa cadena y se corrige en la siguiente pasada. Escribe los mensajes de validación para el modelo, no solo para el log. Dos: el coste se da la vuelta. En código normal escribir es caro y comprobar es barato; con un modelo, generar es casi gratis y verificar es todo el cuello de botella, que es justo por lo que todo tu esfuerzo se muda al harness. Tres: el enemigo no es la entrada mala, es el no determinismo. El mismo prompt te da otra respuesta a la siguiente ejecución, así que la demo que funcionó era una muestra con suerte. Los checks honestos no son “rechaza datos malos”, son “ejecútalo dos veces y compara”, “hazlo idempotente”, “devuélvele el fallo y que reintente”.
Segundo, esto tiene un lado oscuro. Cuanto más harness le metes, más estás construyendo una jaula que disimula un modelo que no da la talla para la tarea. Hay un punto donde “mi agente clava el 95%” significa en realidad “he restringido tanto el problema que ya no es un agente, es un workflow con un LLM rellenando huecos”. A veces eso es exactamente lo que quieres. Pero llamarlo autonomía es engañarte. Y el número del 95% que circula por ahí me lo tomo con pinzas: depende del todo de qué tareas metes en el denominador, y casi nadie publica ese denominador.
Tercero, y esto enlaza con el precio de Fable: cada capa de validación que añades es otra llamada al modelo que se factura. Un harness que lanza un evaluador independiente por cada paso duplica o triplica tu coste por tarea. A 10$ el millón de tokens de entrada, el harness que te salva del slop también te vacía la cartera. El techo de tokens no es un extra del harness. Es parte del harness.
¿Qué significa de verdad “autónomo”?
El truco de los agentes que supuestamente generan productos enteros sin revisión humana durante horas no es un modelo más listo. Es separar dos papeles que se niegan a confiar el uno en el otro: el que genera y el que evalúa. Y la clave está en que el evaluador valide contra el mundo, no contra la versión que el generador tiene del mundo.
Hay una trampa en cuanto montas ese evaluador: si el evaluador es a su vez un LLM, arrastra el mismo sesgo optimista del que intentas escapar: has contratado a un segundo becario para corregir los deberes del primero. La salida es un orden de prioridad. Los checks deterministas (tipos, esquemas, un test con el número esperado a fuego) son gratis y no se les puede colar nada, así que empuja todos los checks que puedas a esa capa. Reserva el LLM-juez, caro y falible, para lo que de verdad no se puede clavar a un assert. El evaluador más barato que no puede mentir le gana al más listo que sí puede.
Tengo un ejemplo tonto pero perfecto de esto en la calculadora. Un test unitario en jsdom te jura que el gráfico está renderizado: el nodo existe, las props son correctas, todo bien. El navegador de verdad ve que ese mismo gráfico tiene 0 píxeles de ancho. Por eso el test que importa es el end-to-end que abre Chromium y WebKit de verdad, localiza el boundingBox del SVG de Recharts y se niega a pasar si el ancho o el alto son cero. El generador te diría que el gráfico está. El evaluador lo mira de verdad. Esa es toda la diferencia entre una demo y un producto: “hecho” deja de significar “he generado el componente” y pasa a significar “lo he abierto en un navegador y lo he visto pintarse”.
¿Qué capa te falta?
Si te quieres saltar la filosofía: busca el síntoma de tu agente a la izquierda y monta lo de la derecha.
| Lo que tu agente hace una y otra vez | La capa que no está |
|---|---|
| Se inventa un dato, o pasa basura tan contento aguas abajo | Frontera: nada rechazó el valor malo por donde entró |
| Se salta un paso, o suelta la salida antes de tenerlo todo | Orden: tus checks corren después del trabajo en vez de bloquearlo |
| Jura que ha terminado mientras está roto a la vista | Evaluador: corrige sus propios deberes, con su propia palabra |
| Funcionaba ayer, hoy falla, mismo prompt | ninguna de las anteriores. Eso es no determinismo, y se arregla ejecutándolo dos veces y comparando, no con otro guardia |
Tres de esas cuatro son una capa que no has montado. La cuarta es el recordatorio de que un harness acota el caos, no lo borra.
Por dónde empiezas, si solo haces una cosa
No montas las tres capas un viernes por la noche. Eliges la que sangra y la acotas. Este es el orden que yo seguiría, lo barato primero, porque lo barato es además lo que más caza.
Empieza en la frontera. Busca el único sitio por donde entran datos de fuera a tu agente: el campo del formulario, el payload de la API, el frontmatter, la salida de la herramienta que parseas antes de fiarte. Pon ahí un schema que reviente en vez de seguir tirando. En Zod son unas pocas líneas:
// dos fronteras reales de mis repos, el mismo gesto:
const Input = z.object({
years: z.number().min(1).max(50), // la calculadora
imageUrl: z.string().refine( // el blog
(u) => u.startsWith("https://res.cloudinary.com/"),
"tiene que ser una URL de Cloudinary",
),
})
Ese .refine() es toda la lección. Un valor malo muere aquí, a gritos, en vez de tres pasos más abajo como un upload/undefined que nadie ve hasta que cae el tráfico. La mitad de mis incendios en producción habrían sido un build en rojo con uno de estos. Quince minutos, hoy.
Luego mueve el chequeo antes del build, no después. Sea cual sea tu paso de generar, coge la validación que ya tienes y ponla delante, bloqueando. En este blog es literalmente un &&: astro check && astro build. Si tienes CI, ordena los raíles para que cada uno bloquee al siguiente (tipos, luego unit, luego end-to-end) y haz que el lento y honesto, el navegador o el test de integración, no se pueda saltar. Aquí no escribes checks nuevos. Mueves los que ya tienes a un sitio por donde el agente no pueda colarse.
Luego escribe un test al que el agente no le pueda colar nada. Elige la única pieza de lógica que tiene una respuesta correcta (una fórmula, un invariante, algo que tiene que renderizarse) y escribe un solo test que la valide contra el mundo, no contra lo que el generador dice de ella. Mete un comentario en mayúsculas para que tú-del-futuro y cualquier agente sepáis que no se “arregla” borrándolo. Con un test basta para empezar. No buscas cobertura; buscas tener un portero en la sala que el generador no controla.
Si esta noche haces solo el primero, ya has movido tu peor modo de fallo de “silencioso en producción” a “a gritos en mi cara”. Ese único cambio ya marca la diferencia.
Lo que se queda contigo
Esto es lo que yo me llevo de haberme estampado un rato contra esto.
La inteligencia del modelo define su techo en una tabla de benchmarks. La robustez del harness decide si esa inteligencia sobrevive cuando todo está en verde y aun así algo está roto. El modelo no es tu cuello de botella. No lo ha sido desde hace un buen rato. El hueco entre la demo y el producto vive entero en todo lo que montas alrededor: el .refine() que rompe el build, el test que no se deja convencer, el navegador que mira de verdad.
Así que la próxima vez que tu agente falle, antes de cambiar de modelo, mira qué había vigilándolo. Lo más probable es que la respuesta sea “nada”, y que ese sea justo el problema.
Lectura relacionada: el harness es lo que va dentro de cada agente; el loop es lo que los orquesta. Uno sin el otro genera slop más rápido o lo genera más despacio, pero lo genera igual. Las skills que cargo en el mío están en las 5 agent skills de mi flujo de código.
Fuentes
- El encuadre “Agente = Modelo + Harness” viene de Vivek Trivedy en LangChain, The Anatomy of an Agent Harness; lo recogí de un ensayo que circuló sobre harness engineering que ordenó estas tres etapas y las capas.
- Todo lo demás sale de mi propio código: este blog (el registro de imágenes borrado, los redirects con barra final, el
.refine()de Cloudinary) y la calculadora de patrimonio (el esquema de Zod, el test de la ecuación de Fisher, el end-to-end del gráfico). Si te pica, está en mis repos.
P.D. ¿Tienes un harness que te ha salvado de un agente mentiroso, o uno que se convirtió en una jaula tan grande que ya no era un agente? Quiero los dos. Cuéntamelo en LinkedIn.
Última actualización:
Artículos relacionados


Subagentes o skills en Claude Code: el fallo del simulador
