Un agente que implementa contra una spec hace algo bastante literal. Toma el texto, lo convierte en tokens, lo mete en el context window y genera acciones condicionadas por eso. La spec es evidencia que sesga la próxima predicción, y su fuerza es exactamente esa.
Eso cambia qué es una buena spec. Con una persona, la especificación es un punto de partida y el resto lo completa el sentido común compartido: las convenciones del repo, lo que se charló en el stand-up, la intuición de que ese endpoint obviamente necesita idempotencia. El agente no tiene ese fondo común. Cuando la spec deja un hueco, el hueco se llena con el promedio de GitHub.
Hay una segunda diferencia, más importante. Una persona sabe cuándo terminó. El loop de un agent termina cuando el modelo decide que terminó, o cuando algo externo se lo dice. De ahí sale el criterio que ordena todo lo demás: una spec sirve como artefacto principal cuando define cómo se comprueba que está cumplida. Sin ese oráculo es una lista de deseos bien redactada.
Qué le pasa a una spec adentro del loop
Dos cosas del mecanismo determinan cómo hay que escribirla.
La spec compite por atención, y los huecos que deja no quedan vacíos. Entra al contexto en el paso 2 y después convive con árboles de archivos, salidas de comandos, stack traces y diffs. En un loop de cuarenta pasos, el peso relativo de esas 800 líneas de prosa baja. Mientras tanto, el modelo no tiene una primitiva limpia para "esto no está definido, freno y pregunto": cuando la spec dice "guardá el resultado" y no dice dónde, elige, y elige lo más probable dado su training. El benchmark Orchid mide esto sobre 1.304 tareas con ambigüedad léxica, sintáctica, semántica y vaguedad, y encuentra dos cosas incómodas: la ambigüedad degrada a todos los modelos y el efecto es más pronunciado en los más avanzados, y los modelos producen implementaciones funcionalmente divergentes ante la misma spec ambigua sin ser capaces de detectar ni resolver esa ambigüedad por sí solos (arXiv 2604.21505). Muchas veces aciertan. El problema es que no distinguís dónde acertó de dónde adivinó, porque el output se ve igual.
El criterio de terminación puede ser interno o externo. Si es interno —"cuando esté completo"— el resultado depende del estado del contexto en ese momento. Si es externo —un comando que devuelve exit code 0— depende del código. La diferencia entre una spec que funciona y una que no suele reducirse a eso. Consecuencia práctica: los criterios de aceptación conviene que sean comandos, porque un comando se vuelve a ejecutar y su salida vuelve a entrar al contexto.
En una spec para un agent, la línea que más pesa es la que decide cuándo se terminó.
Los tres grados de un criterio de aceptación
Un criterio es verificable si algo que no sea el modelo puede evaluarlo. Eso admite grados.
| Grado | Quién evalúa | Ejemplo | Costo | Cuándo |
|---|---|---|---|---|
| Ejecutable | Un comando, un exit code | pytest tests/idempotencia.py::test_replay_devuelve_el_mismo_id | Bajo, repetible | Default |
| Inspeccionable | Un chequeo determinista que no es un test | El diff de openapi.yaml es vacío; tsc --noEmit pasa; no hay ocurrencias de SELECT * en el módulo | Bajo | Invariantes estructurales |
| De juicio | Una persona o un LLM-as-judge | "El mensaje de error explica qué hacer" | Alto y ruidoso | Última instancia, nunca como criterio único |
La operación de trabajo es la conversión: cuando un criterio cae en el tercer grado, preguntate qué tendría que ser cierto para bajarlo uno.
| Frase que no se puede fallar | Criterio que sí |
|---|---|
| "El endpoint tiene que ser rápido" | k6 run load/cobros.js con p95 por debajo de 200 ms sobre 500 requests contra el dataset de fixtures |
| "Hay que manejar bien los errores" | Todo path de error devuelve un body con code del enum ErrorCode, y pytest tests/errores -q cubre los cinco códigos |
| "La UI tiene que ser accesible" | axe-core no reporta violaciones de nivel A o AA en las tres vistas nuevas |
| "No romper compatibilidad" | npm run openapi:diff no reporta breaking changes contra el tag del release anterior |
No todo baja, pero baja más de lo que uno espera. Los criterios de juicio que quedan cargan el problema que ya conocés del artículo de evals: un judge tiene sesgos propios y una varianza que hay que medir antes de confiarle una decisión. Sirven como señal, no como gate.
El alcance se escribe en negativo
La sección que más rinde de una spec es la de lo que queda afuera. Un agente con acceso a bash tiene alcance máximo sobre el repo, y al harness le llega un string opaco. Si la spec no delimita, el blast radius del cambio es todo lo que el proceso pueda escribir.
La documentación de Anthropic sobre harness design para aplicaciones de larga duración describe specs auto-contenidas con tres propiedades: nombran los archivos y las interfaces que tocan, declaran explícitamente el fuera de alcance, y cierran con un paso de verificación end-to-end. Eso se escribe así:
## Fuera de alcance
- No se modifica el schema de `payments`. Si hace falta una columna nueva,
frená y reportá en vez de escribir la migración.
- No se tocan tests fuera de `tests/idempotencia/`. Si un test existente
falla, es un hallazgo para reportar, no un archivo para editar.
- No se agregan dependencias nuevas a `pyproject.toml`.
- No se implementa idempotencia en el resto de los endpoints.
## Verificación end-to-end
Levantar el stack con `make dev`, mandar el mismo cobro dos veces con la
misma `Idempotency-Key` contra el servicio corriendo, y confirmar que
`SELECT count(*) FROM movimientos WHERE external_id = $1` devuelve 1.
La segunda prohibición cierra el agujero clásico: la forma más barata de hacer pasar la suite es editar la suite. Las restricciones que importan de verdad después se promueven del texto al harness, que es donde efectivamente se hacen cumplir.
Cómo se ve una spec ejecutable
Un ejemplo del tamaño que conviene: un cambio real, no un épico.
# Idempotencia en POST /v1/cobros
## Objetivo
Un cliente que reintenta el mismo cobro por timeout de red no debe generar
un segundo movimiento.
## Comportamiento
- El request acepta un header `Idempotency-Key` (UUIDv4, obligatorio).
- Si la clave existe y el body hasheado coincide, se devuelve la respuesta
original con status 200 y header `Idempotent-Replay: true`.
- Si la clave existe y el body difiere, se devuelve 422 con código
`idempotency_key_reuse`.
- Las claves expiran a las 24 horas.
## Criterios de aceptación
1. `pytest tests/idempotencia -q` pasa. Cubre: replay exacto, replay con
body distinto, clave ausente, clave expirada, dos requests concurrentes
con la misma clave.
2. El test de concurrencia usa dos conexiones reales contra Postgres y
verifica que `movimientos` tiene exactamente una fila.
3. `openapi.yaml` documenta el header y los códigos de error nuevos, y
`npm run openapi:diff` no reporta breaking changes.
4. `make lint typecheck` pasa sin warnings nuevos.
5. `make spec-check` confirma que cada comando nombrado en esta spec existe
y corre. Si un criterio referencia un comando que dejó de existir, el CI
falla y la spec se actualiza en el mismo PR.
Lo ejecutable acá viene de tres cosas. Cada criterio nombra un comando o un artefacto inspeccionable. El criterio 2 fija el detalle que un agente resolvería mal por default: "dos requests concurrentes" a secas se implementa con dos llamadas secuenciales en el mismo proceso, que pasa el test y no prueba nada. Y el criterio 5 hace que la spec envejezca ruidosamente en vez de en silencio.
Lo que las herramientas automatizaron
Spec Kit, de GitHub, parte el flujo en comandos con prefijo propio: /speckit.constitution para los principios del proyecto, /speckit.specify para el qué y el porqué, /speckit.clarify para las zonas subespecificadas, /speckit.plan, /speckit.tasks, /speckit.implement, /speckit.analyze y /speckit.checklist. Su plantilla de spec (templates/spec-template.md) obliga a escenarios Given/When/Then, requisitos numerados, criterios de éxito medibles y agnósticos de tecnología, y marca las ambigüedades con [NEEDS CLARIFICATION: ...] en vez de dejarlas pasar.
La pieza más interesante es /speckit.analyze (templates/commands/analyze.md): un pase de lectura sobre los propios artefactos con seis categorías de detección —duplicación, ambigüedad, subespecificación, choques con la constitution, gaps de cobertura e inconsistencia— y una rúbrica donde "criterio de aceptación no testeable" es HIGH. Un requisito con cobertura cero escala a CRITICAL solo cuando bloquea funcionalidad baseline; un gap de cobertura genérico no lo es. Es una eval de la spec, no del código. Y templates/commands/checklist.md lo dice explícito: los checklists son "unit tests for requirements writing". Un ítem válido pregunta si el requisito está definido, no si el sistema anda.
Kiro, de AWS, usa tres archivos por feature: requirements.md con user stories y criterios de aceptación, design.md con arquitectura, tasks.md con las tareas discretas. Los criterios se escriben con el patrón WHEN [condición] THEN the system SHALL [comportamiento], y para bugfixes agrega SHALL CONTINUE TO para fijar lo que no debe cambiar. La forma viene de la ingeniería de requisitos tradicional y sirve para lo mismo acá: eliminar la ambigüedad sintáctica antes de que la resuelva un modelo.
Un escalón más arriba está la práctica que documenta Anthropic en harness design for long-running application development: un agente planner expande un prompt de una a cuatro frases en una spec de producto, y después cada iteración se negocia como un sprint contract donde el generador declara qué construye y cómo se va a comprobar el éxito antes de escribir código. Un evaluador después ejercita la app corriendo vía Playwright MCP en vez de dar por buena la spec leyendo el diff. El criterio de verificación se escribe antes que la implementación, no después.
Qué tan madura es la práctica
Emergente, no consensuada. Birgitta Böckeler, en martinfowler.com (octubre de 2025), ordena el campo en tres niveles: spec-first, donde la spec guía la generación y después el código manda; spec-anchored, donde la spec se mantiene viva junto al código; y spec-as-source, donde el código pasa a ser un artefacto derivado. Su observación sobre el tooling actual es la que conviene tener presente: tanto Kiro como Spec Kit funcionan en la práctica como spec-first, aunque Spec Kit aspire a más.
Böckeler marca además dos riesgos concretos. El primero es heredar la rigidez del Model-Driven Development, con la complicación agregada de que ahora el motor de transformación no es determinista. El segundo es la inflación de ceremonia: documenta un bugfix chico que el flujo convirtió en cuatro user stories con dieciséis criterios de aceptación. La evaluación comparada de seis frameworks de arXiv 2606.04967 llega a un diagnóstico compatible: ninguno cubre bien las seis dimensiones que mide, hay un trade-off estructural entre profundidad de proceso y portabilidad, y los riesgos recurrentes son drift entre spec y código, exceso de confianza en artefactos generados y ausencia de benchmarks del proceso completo. Son buenas plantillas y buenos chequeos de forma. No son evidencia de que el enfoque escale.
Dónde vive la verificación de verdad
Un criterio de aceptación escrito en prosa es una intención. La aplicación ocurre en el harness, y hay una escalera.
| Dónde se escribe | Qué hace | Su límite |
|---|---|---|
| Prosa en la spec | Sesga la predicción | El modelo puede no seguirla y el output se ve igual |
| Herramienta dedicada | Intercepta con argumentos tipados | Solo cubre lo que dejó de pasar por bash libre |
/goal en Claude Code | Evalúa el objetivo antes de dejar cerrar | El evaluador no corre herramientas: juzga lo que el agente ya mostró |
Hook PreToolUse con exit code 2 | Bloquea la tool call y le devuelve el motivo al modelo | Cubre la acción, no la omisión |
Hook Stop con {"decision": "block"} | Impide que el turno termine | Claude Code lo anula y termina el turno tras 8 bloqueos consecutivos |
La distinción de fondo es bash contra herramientas dedicadas. Bash da alcance máximo, pero el harness solo ve un string. Promover una acción a herramienta dedicada le da un punto de intercepción: puede pedir confirmación, chequear que el archivo no cambió desde la última lectura, renderizar UI propia o paralelizar lo seguro. Lo difícil de revertir se promueve.
/goal es el escalón intermedio y tiene una consecuencia sobre cómo se redacta la condición. El evaluador no ejecuta nada: juzga la conversación. Entonces la condición tiene que ser algo que la salida del propio agente demuestre. "La salida de pytest tests/idempotencia -q aparece en la conversación y termina en 0" es evaluable; "los tests pasan" es una afirmación que el agente puede escribir sin haberla corrido.
Los hooks son el escalón duro. Un PreToolUse que sale con exit code 2 impide que la tool call se ejecute, y un Stop con {"decision": "block"} impide que el turno termine (docs). Eso convierte "corré los tests antes de dar por terminado" en una condición de salida del loop en vez de una línea sepultada treinta turnos atrás. Con un techo: tras 8 bloqueos consecutivos, Claude Code anula el Stop hook y termina el turno igual. El gate frena al agente que se distrajo, no al que no puede cumplir el criterio.
La traducción es directa. Lo que la spec prohíbe y es irreversible va a PreToolUse. Lo que la spec exige como condición de completitud va a Stop o a /goal. Lo demás queda en prosa.
Cuánta libertad dejarle
Sobre-especificar rompe tanto como sub-especificar, y de manera menos visible. La especificidad tiene que coincidir con la fragilidad de lo que describís. Donde hay una única secuencia segura —el orden de una migración, los flags de un deploy— van comandos exactos. Donde el campo es abierto —cómo estructurar un módulo, qué nombre poner— van heurísticas en prosa, porque un guion exacto para una decisión de juicio produce peor resultado que el criterio del modelo. La misma doc de harness design de Anthropic lo recomienda al revés de lo que dicta el instinto: mantener la spec alta y no sobre-especificar la implementación.
Hay un caso que sorprende: los prompts y skills escritos para modelos anteriores suelen ser demasiado prescriptivos para los actuales y bajan la calidad del output. Enumerar quince pasos donde alcanzaba con enunciar el objetivo y las restricciones es más frágil, no más seguro. Lo mismo con pedir explícitamente "verificá tu trabajo": en los modelos actuales esa instrucción produce sobre-verificación, porque el comportamiento ya viene de fábrica. La spec dice qué tiene que ser cierto al final, no cómo llegar.
Lo que sí conviene pedir es la lectura. Specine, aceptado en ICSE 2026, ataca el problema de specification perception —el modelo no percibe la spec que vos creés que escribiste— extrayendo la spec tal como el LLM la interpretó y reconciliándola con la original, y reporta +29,60 % de Pass@1 sobre el mejor baseline (arXiv 2509.01313). La versión barata de eso es pedirle al agente que devuelva su lectura de la spec antes de tocar código, y leerla.
Qué tipo de trabajo no se especifica bien
Los cuatro criterios que Anthropic usa para decidir si construir un agent —complejidad, valor, viabilidad y costo del error— sirven igual de bien acá. El que más filtra es el último: si un error no se detecta ni se revierte, no hay spec que te cubra, porque la spec no ejecuta nada.
Diagnóstico y exploración. No podés escribir el criterio de aceptación de "averiguá por qué el consumer se traba los martes", porque el criterio es el hallazgo. Lo que sí se especifica es el entregable —qué evidencia hay que traer, qué comandos hay que correr— y ahí el artefacto principal deja de ser una spec y pasa a ser una skill.
Cambios donde el criterio es el gusto. Copy, jerarquía visual, tono de un mensaje de error. Se acota con restricciones duras —largo máximo, tokens del design system, contraste— pero la decisión final es de juicio y conviene que quede afuera del gate.
Migraciones e infraestructura irreversible. El problema no es la spec sino el costo del error, y la respuesta es bajar de tier. Un workflow con la lógica en tu código, donde el paso destructivo es una llamada tuya y no una decisión del modelo, es más simple y más seguro que un agent con una spec muy detallada.
Refactors grandes. Este parece inespecificable y no lo es. El criterio de aceptación de un refactor es "el comportamiento observable no cambió", y eso se escribe: tests de caracterización escritos antes, cobertura medida antes y después, diff de la API pública vacío. Si esos tests no existen, escribirlos es el trabajo, y ese trabajo sí se especifica.
Cómo se rompe en la práctica
El agente escribe el test y el código. El criterio pasa por construcción: el test verifica lo que el código hace, no lo que la spec pide. Es la falla más silenciosa porque el CI queda verde. La mitigación estructural es que el criterio nombre tests que ya existen, o que los escriba otro actor: vos, o un agent con tarea separada y sin acceso al diff de implementación.
Spec drift. El código evoluciona, la spec no, y a los dos meses es un documento arqueológico. La defensa que se sostiene es tratarla como código: vive en el repo, entra en el mismo PR que el cambio, y el chequeo de comandos del criterio 5 la rompe en CI cuando dejó de describir el sistema.
Inflación. Cuatrocientas líneas de spec para un cambio de veinte diluyen los criterios que importan en prosa que compite por atención. El bugfix convertido en cuatro user stories con dieciséis criterios que documenta Böckeler es el caso testigo. Si la spec es más larga que el diff esperado, hay algo mal calibrado.
Checklist theater. Ítems que suenan a verificación y no verifican: "confirmar que el manejo de errores es correcto". Un ítem que no nombra un comando, un archivo o un valor concreto es decoración.
Herramientas
| Herramienta | Para qué | Costo |
|---|---|---|
| GitHub Spec Kit | El toolkit de referencia y el que fija el vocabulario. Ciclo constitution → specify → plan → tasks → implement, con plantillas, checklists y análisis cruzado entre artefactos. Agnóstico del agente. Doc oficial | Gratis |
| AWS Kiro (Specs) | La implementación más opinionada: tres archivos con roles separados y criterios en WHEN / THEN / SHALL. La mejor para ilustrar spec ejecutable | Tier gratuito de 50 créditos; los planes útiles arrancan en USD 20 al mes |
| OpenSpec | La variante liviana: menos ceremonia, orientada a cambios incrementales sobre código existente en vez de features de cero | Gratis |
| BMAD-METHOD | El otro polo: reparte la especificación entre roles de agente que se pasan artefactos. Útil para discutir dónde la ceremonia se vuelve costo puro | Gratis |
| Gherkin / Cucumber | El antecedente que hace ejecutable a una spec desde antes de los LLMs. "Criterio de aceptación verificable" tiene una definición operativa de veinte años | Gratis |
| Playwright MCP | La pieza que cierra el loop: el agente evaluador ejercita la app corriendo en vez de leer el diff | Gratis |
El nivel de rigor conviene que escale con el tamaño del cambio. Spec Kit para una feature nueva y OpenSpec para un ajuste incremental resuelven problemas distintos, y usar el primero para el segundo es exactamente la inflación que describe Böckeler.
El criterio operativo
La práctica es nueva y el tooling más nuevo todavía. Antes de convertirla en proceso, medila sobre tu propio trabajo: tomá el mismo tipo de ticket hecho de las dos maneras, y registrá cuántos pasaron review sin cambios de comportamiento en el primer intento, cuánto tiempo llevó escribir la spec y cuánto se ahorró en iteraciones. Un artículo que pide criterios verificables no puede pedirte que adoptes el enfoque sin uno.
Mientras tanto, el criterio chico se sostiene solo. Si podés escribir el comando que dice que el trabajo está hecho, escribí la spec. Si no podés, ese es el primer problema, y probablemente no lo resuelva un agent.
Keep going
Reading
- Spec-driven development: Using Markdown as a programming language when building with AI — El caso extremo de la tesis: el .md es el fuente y el binario se recompila desde ahí. El propio autor admite que igual hacen falta tests tradicionales para comprobar lo generado.
- Spec-driven development with AI: Get started with a new open source toolkit — El post fundacional de Spec Kit: las cuatro fases y el punto de control humano en cada una. Describe el proceso y no define cómo se verifica lo generado.
- Harness design for long-running application development — Los sprint contracts: el generador declara qué construye y cómo se va a comprobar el éxito antes de escribir código, y un evaluador lo ejercita vía Playwright MCP contra la app corriendo.
- Spec-Driven Development: From Code to Contract in the Age of AI Coding Assistants — Tres niveles de rigor con criterios de cuándo aplica cada uno, y un framework de decisión sobre cuándo SDD no aporta valor.
- Assessing the Impact of Requirement Ambiguity on LLM-based Function-Level Code Generation — Benchmark Orchid, 1.304 tareas ambiguas. La ambigüedad degrada a todos los modelos, más a los más avanzados, y ninguno detecta ni resuelve la ambigüedad por su cuenta.
- From Prompt to Process: a Process Taxonomy and Comparative Assessment of Frameworks Supporting AI Software Development Agents — Seis frameworks contra seis dimensiones. Ninguno cubre bien las seis, y los riesgos recurrentes son drift, exceso de confianza en artefactos generados y ausencia de benchmarks del proceso completo.
- Aligning Requirement for Large Language Model's Code Generation (Specine) — El problema de specification perception: el modelo no percibe la spec que creés que escribiste. El argumento técnico para pedirle su lectura antes de implementar.
Videos
- Spec-Driven Dev Is Back. But Not How You Think — Daniel Terhorst-North y Gojko Adzic discuten el regreso del spec-driven con dos décadas de haber peleado el mismo problema. El mejor contrapeso al hype.
- Agent Mode in Action: AI Coding with Vibe and Spec-Driven Flows | BRK102 — Contrasta en vivo el flujo vibe contra el spec-driven sobre el mismo problema, y muestra el criterio de cuándo aplica cada uno.
- The ONLY guide you'll need for GitHub Spec Kit — Cuarenta minutos del PM de GitHub detrás de Spec Kit, con el detalle operativo que el post de blog omite.