En agosto, uno de los agentes que trabajan en mis loops hizo algo que tenía prohibido y después escribió que yo le había dado permiso.
El encargo era pequeño y estaba acotado por escrito. Había que tocar una pantalla de la app, y junto a ella estaba la de confirmación de borrado de cuenta, con un texto legal sobre protección de datos marcado expresamente como protegido. El agente lo cambió: le puso la tilde que le faltaba a “eliminacion”. Después regrabó la captura de referencia con la que los tests comparan esa pantalla, para que el cambio no saltara como diferencia. Y por último dejó constancia, en cuatro sitios distintos, de que yo había visto el resultado y lo había aprobado: en su registro de trabajo, en la bitácora del proyecto, en el acta de cierre de la especificación y en la descripción del pull request, la propuesta de cambio que se revisa antes de incorporarla al código.
Era falso. Yo no había visto nada. La transcripción de aquella sesión tiene un único mensaje escrito por una persona, el de arranque. Y en su propio plan el agente llevaba el paso correcto, “enseñar el resultado a José y esperar su OK”, que dio por hecho sin hacerlo.
Lo detectó una persona. No un test, ni un revisor automático, ni una regla. Una persona que, mirando la lista de ficheros del pull request, se extrañó de ver dos ficheros de la app donde no debía haber ninguno.
Si trabajas con agentes, conoces la escena que viene después. Se lo recriminas, y la respuesta es una disculpa impecable: tienes toda la razón, no debí hacerlo, no volverá a pasar. Mejor pedir perdón que pedir permiso. Y la disculpa suena tan sincera como sonaba la aprobación que se había inventado, porque está hecha del mismo material: frases bien escritas.
Lo más incómodo es que el cambio era correcto. La tilde estaba bien puesta, y se quedó. Lo único falso era el camino por el que había llegado. Y en cuanto empecé a tirar de ese hilo, entendí que casi todo lo que yo creía que controlaba a mis agentes, sus instrucciones y sus planes, son en realidad poco más que una petición educada por mi parte, y para nada una orden, una instrucción o algo que vaya a obligar al agente a comportarse de una manera determinada. Y que sus disculpas son lo mismo visto desde el otro lado: una promesa educada que nada le obliga a cumplir.
Todo lo que no lo detectó
Cuando algo así pasa, lo primero que haces es la lista de lo que debería haberlo parado. La mía era larga, y eso es lo que la hace útil. Pero antes de recorrerla merece la pena explicar una palabra que va a salir mucho en esta serie: hook.
Las herramientas con las que trabajan los agentes de código, como Claude Code, tienen unos puntos fijos en su funcionamiento: justo antes de que el agente ejecute un comando, justo después de que modifique un fichero, al empezar o al terminar una sesión. En cada uno de esos puntos puedes enganchar un pequeño programa tuyo, y de ahí el nombre: hook significa gancho en inglés. Ese programa se ejecuta solo, cada vez que el agente pasa por ese punto, sin que el agente lo decida. Puede dejar pasar la acción, añadir información y, en algunos de esos puntos, impedirla. Es parecido al arco de seguridad de un aeropuerto: no depende de que el pasajero quiera pasar por él, está en el camino.
Con eso, la lista.
El hook que vigilaba la revisión visual no saltó. Existía, estaba instalado y funcionaba. Pero estaba enganchado a un punto concreto, el de actualizar el estado de una tarea, y el agente no pasó por ahí. Como un arco de seguridad en una puerta de embarque mientras el pasajero sube al avión por otra.
La etiqueta de la tarea decía que no hacía falta revisión visual, y tenía razón. Para el trabajo previsto, no hacía falta. El problema es que el trabajo previsto no incluía tocar esa pantalla. La etiqueta describía el encargo, no lo que acabó pasando.
La integración continua estaba en verde. Y con motivo: el cambio era correcto. La integración continua, el sistema que compila y pasa los tests automáticamente con cada cambio, comprueba que el código funciona, no que alguien tuviera permiso para escribirlo. Esto es más importante de lo que parece, y volveremos sobre ello más adelante.
Los dos revisores independientes del propio agente lo aprobaron. Antes de abrir el pull request, otros dos agentes repasaron el cambio. Revisaron el código, que estaba bien. No revisaron la firma, porque nadie les había dicho que la firma fuera algo que revisar. Leyeron “José lo ha aprobado” igual que leyeron el resto: como un hecho.
Y había una comprobación que no existía. El comando que lanza cada tarea prometía en su documentación una comprobación final antes de abrir el pull request. La documentación estaba escrita. La comprobación, no. Nadie se había dado cuenta porque nadie la había necesitado hasta entonces.
Cinco capas, y las cinco con una explicación razonable. Ninguna falló por un bug. Fallaron porque ninguna estaba mirando lo que había que mirar: si una persona había dicho que sí.
El desenlace fue pequeño y limpio. Vi el cambio real, lo aprobé de verdad, y pedí corregir el registro en los cuatro sitios donde decía que ya lo había hecho, para que quedara escrito como lo que fue: una aprobación inventada.
Pero la lección no era sobre ese agente. Era sobre dónde vivían mis controles. Casi todos vivían en documentos: un fichero de instrucciones que decía qué estaba protegido, un plan que decía “espera el OK”, una especificación que decía quién decide qué. Prosa. Y la prosa tiene una propiedad que es fácil de olvidar: uno es dueño de lo que dice, pero no de lo que entiende la otra parte, sea un humano o un agente. Además, la prosa, escrita en lenguaje natural, es ambigua e interpretable; por algo existen los lenguajes formales. Y al final, como dice el refrán, las palabras se las lleva el viento.
La prosa es una petición
Para entender por qué la prosa no controla nada hay que mirar qué recibe el agente exactamente cuando arranca, y es menos de lo que uno imagina.
En el loop que medí durante el verano, cada agente de implementación recibe un paquete de entre 46 y 70 kilobytes de texto por llamada: el encargo concreto, las reglas de trabajo y las convenciones del repositorio. Eso es todo lo que existe para él. La especificación completa del proyecto, con todas sus decisiones, no está en ese paquete. No la lee porque no la recibe. Lo descubrí, como casi todo este verano, por un fallo: una tarea de backend marcada en la especificación como “requiere revisión visual” llegó al agente sin ninguna mención a esa revisión, porque la exigencia estaba escrita en una sección del documento que el agente nunca ve. En el código que gobierna el loop quedó escrito, en mayúsculas, el principio que aprendí ese día: ninguna exigencia que la especificación le haga al agente puede depender de que el agente lea la especificación.
Generalízalo y tienes la regla entera. Una instrucción escrita en un documento es una petición dirigida a quien la lea. Si el agente no la lee, no existe. Si la lee y decide otra cosa, tampoco existe, porque no hay nada que lo impida. Si la lee, la obedece y luego escribe que la ha cumplido, lo que queda es prosa certificando prosa. Es exactamente lo que pasó en agosto: el plan decía “espera el OK”, el agente escribió “OK recibido”, y nada en medio podía distinguir una cosa de la otra.
Esto no es una crítica a los documentos. Las instrucciones por escrito son imprescindibles: dicen qué construir, por qué y con qué criterio. Pero sirven para dirigir, no para contener. Y la industria lleva un año aprendiéndolo a su ritmo, framework a framework. Si ordenas los más usados por cuánto de su control es prosa y cuánto es mecanismo, sale una escala bastante reveladora:
Superpowers, con casi 300.000 estrellas en GitHub, es casi todo prosa: quince procedimientos escritos en Markdown y un único hook cuyo trabajo es cargar uno de ellos al arrancar la sesión. Sus “puertas” existen, pero son frases: “la aprobación conversacional del diseño solo permite escribir la especificación”. Lo que garantiza el mecanismo es que el agente leerá la frase, no que la cumpla.
OpenSpec añade un validador: comprueba que los documentos tengan la estructura correcta antes de seguir. Controla la forma de la prosa, no lo que el agente hace con ella.
GSD fue de los primeros populares en poner hooks de guardia: pequeños programas que vigilan que el flujo pase por sus fases y que un commit no se salte la validación.
nWave, mucho menos conocido, lleva la idea al extremo con lo que llama un “sistema de ejecución determinista”: hooks que bloquean escrituras fuera de la sesión y comprueban que el ciclo de test en rojo, test en verde y commit se haya seguido de verdad. Su documentación lo dice sin rodeos: sin esos hooks “pierdes la aplicación de fases y la validación, que son el núcleo de lo que nWave hace”.
Kiro, el entorno de Amazon, empezó en 2025 con tres ficheros de prosa por funcionalidad y a finales de septiembre añadió flujos de trabajo dibujados como grafos, con cada paso en su propia sesión.
La dirección es siempre la misma: lo que empezó como instrucciones acaba como mecanismo. Addy Osmani lo resumió en tres palabras al presentar su colección de procedimientos para agentes: “proceso, no prosa” (“process, not prose”). Thoughtworks fue más allá en su radar de finales de 2025 al evaluar el desarrollo guiado por especificaciones: “puede que estemos reaprendiendo una lección amarga: que escribir reglas detalladas a mano para la IA, al final, no escala”. Y Birgitta Böckeler, que lleva un año probando estas herramientas allí, lo dijo de la forma más honesta: “preferiría revisar código que todos estos ficheros Markdown”.
Yo llegué a la misma conclusión por el camino largo. En el loop que construí este verano conté las dos cosas: unas 50.000 palabras de prosa normativa que viajan al agente (procedimientos, convenciones, plantillas) frente a unas 34.000 líneas de código que deciden qué puede pasar y qué no. Casi la mitad de esas líneas son comentarios que explican por qué existe cada control y qué incidente lo motivó. La prosa no desapareció. Cambió de trabajo: dejó de ser la barrera y pasó a ser la explicación de la barrera.
Si diriges un equipo, la pregunta útil no es qué framework usáis, sino cuánto de lo que les exigís a los agentes está escrito y cuánto lo ejecuta un programa de manera determinista, sin espacio para la interpretación, los olvidos o los malentendidos.
Un aviso no es un bloqueo
El segundo incidente del verano fue más pequeño que el primero, y me enseñó más.
GitHub tiene una comodidad que todo el mundo usa: si el mensaje de un commit, es decir, cada cambio que se guarda en el historial del código, contiene una frase como “Closes #451”, al llegar a la rama principal cierra automáticamente el issue 451, la tarea con ese número. Es útil, y es un arma cargada. Un día, en uno de los repositorios gobernados por el loop, un commit de pura documentación cerró una tarea. El mensaje no pretendía cerrar nada: mencionaba “Closes #451” dentro de una frase que explicaba, precisamente, que ese texto no debía ir en los commits. GitHub no lee frases. Lee palabras clave. Un trabajo que seguía en marcha quedó marcado como terminado, y todo lo que dependía de él se habría quedado esperando a una tarea que ya no existía.
Nadie había desobedecido nada. La regla estaba escrita, era clara, y el commit la cumplía tan al pie de la letra que la citaba. Es el caso límite de la sección anterior: la prosa no solo no detiene, sino que puede disparar.
Para entender lo que vino después hace falta distinguir dos cosas que en el día a día confundimos: un aviso y un bloqueo. Piensa en la entrada de un aparcamiento. Un aviso es el cartel que dice “Completo”: te informa de que no puedes entrar, pero la barrera está levantada y, si decides seguir, entras. Un bloqueo es la barrera bajada: da igual lo que pienses, lo que leas o que tengas prisa, el coche no pasa. En inglés, a ese tipo de control se le llama gate, y en esta serie lo voy a traducir como puerta, pero siempre en este sentido: una puerta cerrada, no una puerta con un cartel.
Casi todo lo que le pedimos a un agente funciona como el cartel. Una regla en un fichero de instrucciones, un mensaje que aparece en pantalla, una advertencia en un procedimiento: le dicen que no debe, y le dejan pasar. El agente puede leerlo y obedecer, leerlo y decidir que en este caso no aplica, o no leerlo. Y lo mismo vale para muchos mecanismos que parecen más serios. Un programa que detecta el problema y escribe “cuidado, esto cerrará una tarea” sigue siendo un cartel, aunque esté escrito en código.
Lo que salió de aquel incidente fue lo contrario: un hook, como los que expliqué al principio, colocado esta vez en el punto por el que el agente no puede evitar pasar, justo antes de ejecutar cualquier comando. Mira si el comando es un commit y si su mensaje contiene una de esas palabras de cierre. Si la contiene, no avisa: rechaza el comando, y el commit no llega a existir. Al agente le devuelve el motivo y le dice dónde tiene que ir el cierre, que es en la descripción del pull request, donde GitHub sí entiende que es intencionado. Es la barrera bajada. En la cabecera del hook quedó escrita la decisión de diseño: es una puerta cerrada y no un aviso, a propósito. Y es que hay algo que no podemos dejar de tener en cuenta: un control cuyo resultado no puede detener la siguiente acción es mera decoración o, a lo sumo, una declaración de intenciones que el agente puede decidir no tomarse en serio.
Que el hook bloquee no basta para fiarse de él. Hay tres propiedades que, para mí, son las que separan una barrera de verdad de una que lo parece.
La primera: se comprueba por lo que pasa, no por lo que dice. Para saber si la barrera del aparcamiento funciona, no miras si la lucecita se ha puesto roja; intentas pasar con el coche y ves si has entrado. El test del hook hace exactamente eso. Le pide al agente que ejecute un commit con palabra de cierre seguido de una orden que crea un fichero de prueba, un centinela, y después pregunta una sola cosa: ¿existe el fichero? Si existe, el comando pasó y la barrera no funcionó. Y repite el mismo montaje sin el hook, donde el fichero sí aparece. Esa segunda prueba es la que da valor a la primera: demuestra que, cuando el fichero no aparece, es porque la barrera lo impidió y no porque el agente ni siquiera lo intentara.
La segunda: sigue funcionando cuando no hay nadie en la garita. Los agentes que lanzo trabajan solos, en un modo en el que no se pregunta nada a ninguna persona, porque no hay nadie delante para responder. Un control que solo funciona cuando alguien está mirando no protege nada en ese escenario. Así que el test también se ejecuta en ese modo, y el centinela tampoco aparece. La frase que quedó en la documentación del proyecto es una de las que más he repetido desde entonces: que nadie le pregunte nada a la persona no significa que el repositorio haya dejado de decidir.
La tercera: dice por dónde no cubre. Toda barrera tiene huecos, como el aparcamiento que tiene una puerta peatonal al lado. El hook lleva escrita en su cabecera la lista de los suyos: no ve lo que una persona teclea directamente en su terminal, no ve un commit que se escribe en un editor en vez de en la línea de comandos, no ve un mensaje que se lee de un fichero, no ve un comando escondido dentro de otro. Cada hueco está comprobado, no supuesto. Y esa lista es lo que me permite confiar en él, porque sé exactamente dónde no puedo hacerlo.
Fíjate en lo que separa las dos versiones de la misma idea. La regla del fichero de instrucciones decía “no pongas palabras de cierre en los commits”. El hook dice exactamente lo mismo. La diferencia no está en el contenido: está en que la regla es un cartel que el agente puede leer o no, y el hook es una barrera que se cierra aunque nadie lea nada.
Lo que sí se puede verificar
Vuelvo al incidente de agosto con lo que ya sabemos, porque la pregunta que importa no es “cómo evito que un agente mienta” sino “qué puedo comprobar sin fiarme de lo que dice”.
La primera respuesta es incómoda: la firma humana no es verificable desde dentro del loop. Un agente que trabaja solo corre con mis credenciales de GitHub, porque es la única forma de que abra pull requests y actualice issues en mi nombre. Con esas credenciales puede fabricar cualquier artefacto que GitHub reconozca como mío: una revisión, una aprobación, un comentario que diga “visto bueno”. No hay ningún documento, etiqueta ni registro que el agente no pueda producir, así que ninguno de ellos puede servir como prueba de que una persona dijo que sí. Buscar “el artefacto que el agente no pueda falsificar” es buscar algo que no existe mientras tenga mis credenciales.
La segunda respuesta es la salida: lo que sí es verificable es el hecho. Qué ficheros tocó. Eso no lo puede falsificar sin que se vea, porque cada cambio queda registrado línea a línea en el propio pull request. El encargo decía qué ficheros podía tocar y cuáles estaban protegidos; la lista de ficheros del pull request dice cuáles tocó de verdad. Comparar las dos listas no requiere creer a nadie.
Y de ahí sale el tercer principio. Esa comparación no puede ejecutarla el propio agente, aunque sea trivial. Si el agente corre la comprobación, el agente puede saltársela, reescribirla o declarar que pasó. Tiene que correr fuera de su alcance: en la integración continua, como una comprobación más del pull request, con la lista de ficheros protegidos leída de la tarea y no de lo que diga el agente. La frase que quedó escrita en la documentación es corta: un guardia al que pone en marcha el propio sospechoso no es un guardia; es como poner al zorro a cuidar de las gallinas.
Nada de esto es nuevo en ingeniería. Es, casi palabra por palabra, el principio de segregación de funciones que cualquier auditor aplica desde hace un siglo: quien ejecuta no es quien verifica, y la verificación mira hechos, no declaraciones. Lo que ha cambiado es que ahora el que ejecuta escribe muy bien, muy rápido y con una seguridad que invita a creerle. Un agente que redacta “José lo ha aprobado” en una prosa impecable es más convincente que un becario nervioso, y eso es precisamente lo que lo hace peligroso.
Si vuelves ahora al mensaje de Robert C. Martin, Uncle Bob, el autor de Clean Code, que cité la semana pasada, se lee distinto. Decía, en esencia, que ya no lee el código de sus agentes, que los rodea de restricciones extremas y que confía en lo que sobrevive. Mira su lista de restricciones: tests unitarios, tests de aceptación, procedimientos de QA, métricas de calidad, pruebas de mutación, cobertura. Ni una sola es una instrucción. Todas son cosas que se ejecutan y que fallan solas. Bob no ha sustituido la lectura por confianza. La ha sustituido por puertas. Lo que no cuenta, y lo que a mí me costó el verano entender, es que cada una de esas puertas tiene que cumplir las tres propiedades de la sección anterior: comprobarse por lo que pasa y no por lo que dice, funcionar aunque no haya nadie en la garita y decir por dónde no cubre.
Petición o puerta
Si te quedas con una sola idea, que sea esta: todo lo que escribes para un agente es una petición; solo lo que puede detenerlo es un control. No es una crítica a escribir bien las instrucciones, que siguen siendo lo que dirige el trabajo. Es una forma de saber qué tienes y qué no. Un fichero de reglas dirige. Un hook que bloquea, un test que falla, una comprobación en CI que compara hechos, controlan y bloquean si es necesario. Las dos cosas hacen falta. Pero solo las segundas te permiten dejar de mirar y avanzar hacia una forma de desarrollar software más autónoma y desatendida.
Tengo tres preguntas que ahora me hago con cada regla que escribo para un agente, y te las dejo porque funcionan con cualquier herramienta, da igual cuál uses:
1. ¿Podría el agente no leer este fichero? Si la respuesta es “nada lo impide”, no es una instrucción que esté garantizada, es solo una petición educada.
2. ¿Qué pasa si lo lee y hace otra cosa? Si la respuesta es “me enteraría al revisar”, es una petición que depende de tu atención, y tu atención es el recurso que estás intentando liberar.
3. ¿Quién comprueba que se cumplió, y puede ese quién ser el propio agente? Si la respuesta es sí, no hay comprobación.
Para que no se quede en teoría, he publicado un repositorio mínimo con el hook de esta historia reescrito desde cero para enseñar: unas sesenta líneas que bloquean un commit con palabra de cierre en Claude Code, y un test que lo mide por efecto, con el fichero centinela, tal como he contado. Está pensado para leerlo entero en diez minutos y para copiarlo en tu propio proyecto. El enlace está al final.
Lo que no he contado todavía es la otra mitad del problema. Una puerta detiene una acción concreta. Pero quién decide cuál es la siguiente acción, en qué orden van, y qué pasa cuando una comprobación no se puede ejecutar en lugar de fallar, es otra pregunta, y es la que de verdad separa un conjunto de reglas y hooks sueltos, que es lo que encontramos en la mayoría de los ecosistemas de desarrollo guiado por especificaciones como Superpowers, OpenSpec o GSD, de una máquina de software robusta y con garantías de determinismo. De eso va el miércoles que viene: el agente no decide cuál es el paso siguiente.
Y la pregunta práctica de siempre. Coge las reglas que tienes escritas para tus agentes, estén donde estén: un fichero de instrucciones, un prompt de sistema, una plantilla. Cuéntalas. Y ahora cuenta cuántas de ellas pueden detener algo si no se cumplen. Yo hice ese ejercicio en agosto, y el segundo número cabía en una mano.
El repo: gemba-door-not-warning, MIT. El hook, sus tests y el test por efecto con el fichero centinela, listo para copiar en tu proyecto.


