DEV Community

Cover image for Harness Engineering: el modelo casi nunca es el problema
Ricardo Chavarria
Ricardo Chavarria

Posted on

Harness Engineering: el modelo casi nunca es el problema

Hace poco monté un experimento pequeño: el mismo modelo de IA, la misma tarea de código, tres configuraciones distintas de lo que tenía permitido hacer. El resultado más revelador no fue que el agente fallara: fue ver, con evidencia en pantalla, que la calidad de lo que produce depende mucho menos del modelo de lo que solemos asumir, y mucho más de la infraestructura que construimos alrededor de él. A eso, en la industria, se le empezó a llamar harness engineering.

Los números que motivaron todo esto

A finales de 2025 los mejores agentes de codificación rondaban 50-60% de éxito en SWE-bench Verified, un benchmark que usa tareas curadas con tests ya existentes: el escenario más favorable posible. Anthropic corrió un experimento más incómodo todavía: el mismo modelo exacto, el mismo prompt, dos configuraciones. Sin ningún andamiaje alrededor: 20 minutos, 9 dólares, el resultado no funcionaba. Con una arquitectura de tres agentes coordinados: 6 horas, 200 dólares, un producto completo y funcional.

El modelo no cambió entre esas dos corridas. Todo lo demás sí.

Qué es, en realidad, un harness

La definición más precisa que he encontrado es también la más simple: un harness es todo lo que existe en la infraestructura de ingeniería que rodea a un agente y que no son los pesos del modelo. Si algo no vive dentro de la red neuronal misma, vive en el harness. Dicho como ecuación, agente completo = modelo + harness.

Esto importa porque cambia dónde uno busca soluciones. El modelo no lo entrenas tú: lo entrena Anthropic, OpenAI, Google. No puedes abrirlo y ajustar un parámetro. El harness sí lo controla por completo el equipo que lo usa, y es ingeniería de software normal: se diseña, se versiona, se prueba, se mejora con datos.

Una forma de verlo: el modelo es el piloto. El harness es el auto, la pista señalizada, y el equipo en boxes. Un piloto excelente en un auto mal calibrado, sin indicaciones de la pista y sin nadie que le avise el estado de las llantas, va a cometer errores que no tienen nada que ver con su talento al volante. Un piloto correcto (ni excepcional ni torpe) con un auto bien puesto a punto, una pista bien marcada, y un equipo que le confirma en cada vuelta qué está funcionando, corre carreras consistentes. La habilidad del piloto importa, pero deja de ser la variable que decide la carrera.

Concretamente, esa infraestructura cubre cinco frentes que se sostienen entre sí: qué instrucciones lee el agente al arrancar, qué herramientas tiene permitido usar, qué tan reproducible es el entorno donde trabaja, qué recuerda del estado entre una sesión y la siguiente, y (la que más rendimiento aporta y menos atención recibe) cómo recibe retroalimentación sobre si lo que hizo está bien o mal.

graph TD
    A[Agente completo] --> I[Instrucciones<br/>qué lee al arrancar]
    A --> H[Herramientas<br/>qué puede ejecutar]
    A --> E[Entorno<br/>qué tan reproducible es]
    A --> S[Estado<br/>qué recuerda entre sesiones]
    A --> R[Retroalimentación<br/>cómo sabe si acertó]

    style A fill:#20344b,color:#ffffff
    style R fill:#5cdb6d,color:#20344b

Ninguna de las cinco compensa la ausencia de las otras cuatro; un archivo de instrucciones perfecto no sirve de mucho si el agente no tiene forma de verificar que las siguió.

Un equipo real que documentó su propio proceso lo vivió en carne propia: con solo un README genérico, un agente con GPT-4o resolvía tareas correctamente 1 de cada 5 veces. Agregar un archivo de instrucciones subió eso a 60%. Agregar comandos de verificación explícitos, a 80%. Agregar una plantilla de progreso entre sesiones, terminó estabilizando el resultado entre 80 y 100%. El modelo fue el mismo en las cuatro mediciones: cada punto porcentual de mejora vino de una pieza distinta del harness, no de la red neuronal.

El repositorio es la única memoria que cuenta

Hay una idea incómoda detrás de esto: si una convención de tu equipo vive solo en la cabeza de alguien, o en un hilo de Slack de hace tres meses, el agente no tiene forma de saber que existe. No es que la ignore, no puede verla. Por eso la recomendación más repetida en este espacio es tan simple de decir y tan difícil de sostener en la práctica: todo lo que importa tiene que estar escrito, dentro del repo.

flowchart LR
    S[Reglas en Slack] --> R[Escríbelas en<br/>archivos del repo]
    C[Reglas en Confluence] --> R
    H[En la cabeza<br/>de alguien] --> R
    J[Tickets de Jira] --> R
    R --> N[Sesión nueva lee<br/>el repo directamente]

    style R fill:#5cdb6d,color:#20344b
    style N fill:#20344b,color:#ffffff

Una forma práctica de comprobar si ya lo lograste: abre una sesión de agente completamente nueva, sin contexto verbal, y pídele que responda cinco preguntas básicas solo leyendo el repo (qué es el sistema, cómo está organizado, cómo se ejecuta, cómo se verifica, y dónde va el trabajo). Si se traba en alguna, esa es exactamente la zona en blanco donde el agente termina adivinando.

El problema es que "escribir todo" tiene un límite real. Un equipo dejó crecer su archivo de instrucciones de 50 a 600 líneas, sumando una regla cada vez que el agente se equivocaba en algo. El rendimiento, en vez de mejorar, empezó a caer. La causa no era solo el volumen: era dónde vivía cada regla. Reorganizaron el archivo en un índice corto de 80 líneas que apunta a documentos temáticos separados, y una regla de seguridad crítica que antes estaba enterrada en la línea 300 pasó a estar cerca del principio. Sin cambiar una palabra de esa regla, su cumplimiento subió de 60% a 95%. Hay investigación de 2023 que explica por qué: "Lost in the Middle" (Liu et al.) muestra que los modelos usan mucho peor la información que está en medio de un documento largo que la que está al principio o al final.

Así se ve ese patrón en la práctica, en el archivo de instrucciones que usé para mi propio experimento:

# AGENTS.md: Contrato de Ejecución del Repositorio

Es un mapa, no una enciclopedia. Si necesitas el porqué de una
decisión, no está aquí: está en `DECISIONS.md`.

## Reglas arquitectónicas inviolables
1. Standalone obligatorio. Ningún NgModule nuevo.
2. Signals nativos para estado. Prohibido RxJS para estado local.
3. WIP = 1. Solo una funcionalidad en estado "active" a la vez.
Enter fullscreen mode Exit fullscreen mode

Ocho líneas que le dicen al agente exactamente qué no negociar, y ningún párrafo explicando por qué: ese contexto vive aparte, y solo se carga si hace falta.

Por qué "una cosa a la vez" no es solo buena práctica, es matemática de contexto

Cuando un agente puede tocar cualquier archivo sin límite, tiende a abrir varios frentes al mismo tiempo y no cerrar ninguno bien. Un caso documentado con una API de 8 funcionalidades lo muestra con números: dándole rienda suelta, el agente activó 5 tareas en paralelo, produjo cerca de 800 líneas, y solo 20% de esas líneas pasaban una verificación end-to-end real. Restringido a una funcionalidad a la vez, verificada antes de pasar a la siguiente, produjo una cuarta parte del código, y esa fracción sí funcionaba, terminando más funcionalidades reales al cierre del proyecto que el enfoque abierto.

Yo lo viví de forma más directa en mi propio experimento. Con un archivo de reglas que restringía explícitamente qué dependencias se podían agregar, el agente evitó importar un módulo de Angular que hubiera sido la solución más obvia para un formulario: sin que yo se lo pidiera en el prompt, lo resolvió de otra forma porque el archivo de reglas no lo permitía. Y cuando terminó su tarea, se detuvo ahí: había otra pendiente en la misma lista, y en vez de empezarla por su cuenta, me avisó que existía y esperó instrucción. Ninguna de esas dos decisiones vino de lo que yo le escribí en el chat.

Las listas de tareas como estructura de datos, no como nota

El formato que terminé adoptando viene directo de cómo Anthropic describe su propio proceso interno: cada función pendiente es un objeto con una descripción, los pasos que la verifican, y un campo booleano que dice si pasa o no: nunca decidido por el agente mismo, siempre por el resultado real de un comando. Así se ve una entrada real de mi propio feature_list.json:

{
  "id": "CART-042",
  "description": "El descuento por cupón nunca deja el total en negativo",
  "verification_command": "ng test",
  "allowed_files": ["src/app/cart.store.ts", "src/app/cart.store.spec.ts"],
  "state": "active",
  "passes": false
}
Enter fullscreen mode Exit fullscreen mode

Nada en ese objeto es ambiguo: qué archivos se pueden tocar, qué comando decide si terminó, y un booleano que solo cambia cuando ese comando corre de verdad. Es una diferencia sutil pero importante frente a la forma en que la mayoría de equipos hace seguimiento hoy: una nota de texto tipo "auth hecho, carrito casi listo" es interpretable, y lo interpretable es exactamente lo que un agente nuevo, sin memoria de la sesión anterior, no puede resolver de forma confiable.

El punto más difícil: declarar éxito sin haberlo verificado

Esta es, para mí, la parte más importante de todo el tema, y la que mi propio experimento terminó ilustrando mejor de lo que esperaba. Hay evidencia de hace casi una década (Guo et al., 2017) de que los modelos de lenguaje son sistemáticamente demasiado confiados en sus propias respuestas. Más reciente todavía, Anthropic encontró, en el artículo "Harness design for long-running application development" (marzo de 2026), que cuando se le pide a un agente que evalúe su propio trabajo, tiende a calificarse mejor de lo que lo haría un observador independiente.

Quise ver qué tan lejos llegaba eso. Le bloqueé a un agente el acceso a terminal por completo, sin ningún archivo de reglas de por medio, y le pedí que resolviera una función de código con un caso borde específico. Sin forma de ejecutar nada, no inventó un resultado a la ligera. Hizo algo más riguroso: trazó a mano, línea por línea, cada una de las cinco pruebas que definían si su implementación estaba correcta, incluyendo un caso de redondeo con arrastre de punto flotante que resolvió bien. Su conclusión, tal cual la escribió:

"Los 5 tests deberían pasar [...] No es lo mismo que verlos correr."

Lo sabía. Lo dijo con esas palabras. Y aun así, la afirmación que puso primero fue de alta confianza, no una declaración de incertidumbre. Corrí las pruebas después, por mi cuenta: tenía razón en los cinco casos. Pero no lo sabía cuando lo escribió, y la seguridad de esa frase hubiera sonado idéntica si se hubiera equivocado en un solo detalle.

Esto fue, literalmente, lo que trazó a mano sin poder ejecutarlo:

readonly discountAmount = computed(() =>
  Math.round(Math.min(this.subtotal(), this.subtotal() * (this.couponPercent() / 100)) * 100) / 100
);

readonly total = computed(() =>
  Math.max(0, Math.round((this.subtotal() - this.discountAmount()) * 100) / 100)
);
Enter fullscreen mode Exit fullscreen mode

El Math.min evita que el descuento supere el subtotal, el Math.max(0, ...) evita el total negativo, y el doble redondeo evita el arrastre de punto flotante. Tres líneas, tres decisiones correctas, verificadas con una calculadora mental y no con un intérprete de JavaScript.

Ahí está, creo, el núcleo real del problema. No es que los agentes adivinen con descuido. Es que un razonamiento cuidadoso y una verificación real producen el mismo tono de certeza hacia afuera, y solo una de las dos tiene evidencia detrás. La solución que se sugiere para esto no es pedirle al agente que sea "más honesto", no funciona, porque el sesgo de autoevaluación es estructural, no una falla de carácter. La solución es exigir, como regla dura del sistema, que ninguna capa de verificación sea opcional: primero que el código compile, después que pase pruebas en tiempo de ejecución, y solo al final, confirmación de extremo a extremo.

Por qué "pasan los tests unitarios" no es lo mismo que "funciona"

Ese último nivel importa más de lo que parece. Los tests unitarios, por diseño, aíslan cada pieza y simulan todo lo demás: eso es justo lo que los vuelve ciegos a los problemas que solo aparecen cuando dos componentes se conectan de verdad. Hay un caso documentado de una aplicación de escritorio donde cada capa (interfaz, script intermedio, lógica de negocio) pasaba sus pruebas por separado, y aun así el flujo completo tenía cinco defectos reales: una incompatibilidad de formato entre dos módulos, datos que nunca llegaban a la pantalla, recursos que no se liberaban, comportamiento distinto según el entorno, errores que no se propagaban hasta donde alguien pudiera verlos. Ninguno de los cinco apareció en un test unitario. Todos aparecieron al correr el sistema completo.

El código que queda no es lo mismo que el porqué que se pierde

Cuando un agente trabaja varias sesiones sobre el mismo proyecto, lo que queda al final de cada una es el código: el "qué". La razón por la que se eligió una opción y no otra, el "por qué", casi nunca sobrevive al cierre. Una sesión nueva, sin ese contexto, puede terminar "optimizando" una decisión que en realidad fue deliberada.

flowchart TD
    subgraph SIN["Sin artefactos de continuidad"]
        direction LR
        A1[Sesión nueva] --> A2[Sin memoria] --> A3[Re-explora<br/>~15 min] --> A4[Puede duplicar<br/>trabajo]
    end
    subgraph CON["Con artefactos de continuidad"]
        direction LR
        B1[Sesión nueva] --> B2[Lee PROGRESS.md +<br/>DECISIONS.md + git log] --> B3[Retoma en<br/>~3 min]
    end

    style A4 fill:#ea4335,color:#ffffff
    style B3 fill:#5cdb6d,color:#20344b

Un caso real lo muestra con números: un sistema de blog con autenticación, 12 puntos de función, 5 sesiones estimadas. Sin ningún archivo de estado, para la sesión 5 solo se habían completado 7 de las 12 funciones, y 3 de esas tenían errores ocultos por la deriva acumulada. Con un diario de progreso y un registro de decisiones, las 12 quedaron completadas y verificadas en las mismas 5 sesiones, con el tiempo de "reconstrucción" al abrir cada sesión bajando cerca de 78%.

Hay un fenómeno documentado por Anthropic que explica parte de esto: cuando un agente nota que el contexto de la sesión se está agotando, tiende a apresurarse y saltar verificaciones que normalmente haría. Un buen registro de estado no es solo comodidad para retomar más rápido: reduce las ventanas donde el agente siente esa presión y empieza a cortar esquinas.

Un dato menos citado: por qué el sistema necesita poder explicarse

Hay una dimensión de esto que se discute menos, y que a mí me pareció de las más aplicables: no basta con que el sistema sepa si algo falló, tiene que poder decir por qué. Un flujo de trabajo con tres roles (uno que planea, uno que genera, uno que evalúa) probó una misma tarea con y sin ese nivel de detalle en la retroalimentación. Sin él, el ciclo se volvía una serie de reintentos a ciegas: 3-4 rondas, cerca de 45 minutos, resultado apenas aceptable. Con un contrato explícito de qué se esperaba y una rúbrica de evaluación concreta, la misma tarea se resolvió en una sola iteración, en unos 15 minutos, con mejor resultado: tres veces más eficiente. El problema, cuando existía, siempre había sido el mismo tipo de tarea: lo que cambió fue si el sistema podía explicarse a sí mismo qué estaba mal.

Cerrar bien también es parte del trabajo

Una sesión de agente no debería considerarse terminada solo porque el código "funciona". Debería dejar un estado del que cualquiera (humano o el mismo agente en la siguiente sesión) pueda partir sin adivinar: que el build compile, que las pruebas pasen, que quede un registro legible de qué se hizo, que no queden artefactos de depuración regados por el código, y que el siguiente arranque no dependa de reconstruir contexto perdido. Un proyecto real medido a lo largo de 12 semanas sin esa disciplina terminó con poco más de dos tercios de sus builds compilando correctamente, y arranques de sesión que tomaban una hora. Con la disciplina puesta, esos mismos números se mantuvieron cerca del 97% y bajaron a menos de diez minutos.

El modelo y la herramienta no son lo mismo que el harness

Vale la pena separar una cosa más: la herramienta que usas para hablar con el agente ya trae consigo parte de esta infraestructura: un sistema de permisos, gestión de la ventana de contexto, acceso a herramientas. Eso no es algo que uno construye. Lo que sí hay que construir es todo lo específico del propio proyecto: qué arquitectura respetar, qué se puede tocar y qué no, qué comando decide si algo está realmente terminado. En mis tres corridas, la herramienta y el modelo fueron exactamente los mismos de principio a fin. La variable que cambió, y la única que explica la diferencia de resultado, fue esa segunda capa.

La conclusión práctica

Cuando un agente de código produce algo mal hecho, el reflejo casi universal es pensar en cambiar de modelo. Es, casi siempre, la opción más cara y la menos efectiva. Antes de eso, vale la pena preguntarse si existe un archivo que diga qué reglas seguir, si hay una forma de que el sistema verifique su propio trabajo sin depender de que el agente decida hacerlo, y si esa verificación es realmente obligatoria o solo una posibilidad entre varias. Ese conjunto de decisiones, no los parámetros del modelo, es lo que terminó marcando la diferencia en cada una de mis pruebas.


Fuentes

Los conceptos, casos de estudio y datos de este artículo se apoyan en:

Top comments (1)

Collapse
 
prpatel05 profile image
Pratik Patel

En mi experiencia, el harness también puede mentir si la verificación corre contra mocks o un workspace sucio. He visto al agente dejar el build y los unit tests en verde mientras el contrato real de una API había cambiado; solo un entorno limpio con una prueba de extremo a extremo y datos representativos lo detectó. Por eso guardo el comando, el entorno y el artefacto de ejecución, no solo el booleano passes.