Mi último post es del 5 de abril. En los cinco meses siguientes no escribí aquí una línea, pero seguí trabajando en un proyecto privado en el que la mayor parte del código no lo escribo yo: lo escribe un agente. Este post no va de qué construí — va de qué tuvo que ser cierto en el repositorio para que eso no acabara en un desastre.

El problema, dicho sin adornos

Cuando un agente escribe la mayoría del código de un proyecto, y lo hace todos los días durante meses, la revisión humana línea a línea deja de existir. No es cuestión de disciplina ni de ponerle ganas. Puedo leer un diff con atención, o puedo leer los que se acumulan en una tarde. No las dos cosas.

La respuesta que no funciona es revisar más despacio. La que sí funciona es dejar de revisar código y empezar a revisar invariantes: cosas que tienen que ser ciertas siempre, comprobadas por una máquina, en cada PR, sin excepción y sin que yo esté delante.

// CLAVE

Cuando el agente escribe la mayor parte del código, tu trabajo deja de ser escribirlo y deja de ser leerlo. Tu trabajo es decidir qué no puede romperse nunca, y convertir esa decisión en algo que falle solo.

No voy a dar cifras de volumen ni a decir qué es el proyecto. Es privado y seguirá siéndolo un tiempo. Y un contador de commits es una métrica pobre para lo que quiero contar: mide actividad, no criterio. Lo que sirve es el proceso que lo sostiene, que además es lo único de aquí que te puedes llevar.

El estado no vive en el chat

Antes de los gates hay un problema anterior: cómo se dirige a un agente durante meses cuando su memoria se muere al final de cada sesión.

La respuesta que fui construyendo se resume en una línea que acabó escrita en las instrucciones del repositorio: mantén el contexto compartido pequeño y duradero, y pon el estado de la tarea en el sistema de ficheros, no en el historial de conversación. Todo lo demás sale de ahí.

Un libro de registro, no una conversación

Cada tarea tiene un identificador y un expediente en el repositorio. Lo que le pasa a esa tarea se escribe como eventos tipados en JSON, en un fichero al que solo se añade, nunca se reescribe: claim cuando un agente la reclama, progress, question, answer, handoff, verify, close, y también supersede y abandon para las que mueren.

Dos reglas de ese registro me ahorran más tiempo que cualquier gate:

Y antes de parar o de pasarle el trabajo a otro agente, se escribe un handoff con el estado, la rama y la siguiente acción. Ese fichero es lo que sobrevive a la ventana de contexto.

Escala de dureza

No todo el trabajo merece la misma ceremonia. Tener que decidirlo caso por caso es agotador, así que lo convertí en una tabla que el agente aplica solo:

Nivel Cuándo Qué exige
H0 Typo, texto, cambio local trivial Nota final
H1 Cambio pequeño de código o docs Tarea recomendada
H2 Cambio de comportamiento con tests Tarea, criterios de aceptación, verificación
H3 Función cross-módulo o de cara al usuario Especificación, tareas, evidencia
H4 Migración multi-paquete o alto riesgo RFC, especificación, olas, revisión independiente

La frase operativa que va con la tabla: ajusta la profundidad de la verificación a la dureza. Un agente sin esto hace una de dos cosas, y las dos son malas: monta un proceso de tres actos para arreglar una errata, o se lanza a una migración de veinte ficheros sin plan.

La separación que sostiene todo: los RFC deciden; las especificaciones y las tareas ejecutan. Cuando algo se discute en el sitio equivocado, se nota inmediatamente.

Coordinación en su propio worktree

Con varios agentes trabajando a la vez, el registro se convierte en el punto de conflicto: todos quieren escribir en él, y cada uno está en su propia rama.

La solución que funcionó fue separar los dos tipos de estado en árboles de trabajo distintos. Los worktrees de funcionalidad llevan código y tests, cada uno en su rama. Un worktree de coordinación aparte, anclado a la rama de integración, lleva solo lo compartido: reclamaciones, preguntas, handoffs, resúmenes de verificación y evidencia de cierre. El código diverge en paralelo; la coordinación converge en un único sitio.

Y un comando de reconciliación para el fallo más común de todos: tareas cuya rama ya se integró pero que siguen abiertas en el registro. Las busca y las señala.

El resto del andamiaje

// CLAVE

Nada de esto va de escribir mejores prompts. Va de que el proyecto tenga una memoria que no dependa de que el agente la recuerde. El prompt es lo único de todo el sistema que se borra solo.

Los guardarraíles

Ninguno de estos nació de un diseño previo: casi todos existen porque algo se rompió antes y no quise que volviera a pasar. Los que más trabajo me ahorran:

El patrón: ninguno de estos revisa si el código es bueno. Todos comprueban que no se ha roto algo concreto que ya sé que importa. Es una distinción aburrida y es toda la diferencia.

Qué se rompió para que existan

Ninguno de estos gates es una buena práctica que leí en algún sitio. Cada uno tiene una fecha y un destrozo detrás.

El diff que parecía un parche

Un bot de dependencias subió una acción de CI de la versión 6.4.0 a la 7.0.0 en seis workflows a la vez, en un PR que se auto-mergeó. La acción compuesta que esos workflows invocan se quedó en 6.4.0. Y los comentarios # v6 se quedaron puestos en los seis sitios.

Así que el diff se leía como un cambio de parche. Un humano revisándolo con atención habría visto seis veces v6 y habría seguido adelante. Yo lo habría aprobado.

Lo detectó un invariante que no tiene nada que ver con versiones: la regla que exige que la build de comparación de tamaño use exactamente la misma versión de herramientas que la compuesta, porque si no, la comparación no mide lo que dice medir. La divergencia salió por ahí, de rebote, once días después de entrar.

// CLAVE

El cambio era invisible a la inspección humana por diseño: los comentarios decían una versión y el código otra. Ninguna cantidad de atención lo habría cogido. Solo lo coge una máquina que comprueba una propiedad, no un texto.

Las tres veces que el agente movió el guardarraíl en vez de arreglar el código

Esta es la que no me esperaba, y es la razón por la que escribo el post.

El tercero era correcto: el test tardaba medio segundo en local y se iba a cinco bajo carga con cobertura activada, y el límite por defecto era el que estaba mal. Los otros dos no lo eran.

Y esa es justo la dificultad. No es que el agente haga trampa — las tres veces argumentó bien, y una de las tres tenía razón. Es que cuando algo le bloquea el paso, mover el obstáculo y resolver el problema le parecen la misma cosa, porque ambas hacen que el CI se ponga verde. Verde es su función objetivo. La mía es otra.

La consecuencia práctica: los ficheros que definen los propios gates —límites, listas de excepciones, umbrales— necesitan tratarse como código crítico y no como configuración. Un PR que toca un umbral no es un PR de mantenimiento.

El gate que existía y no estaba corriendo

Una migración de formato reescribió 453 documentos del corpus de una versión a la siguiente. Pasó CI y entró. Rompía la decodificación en uno de los clientes, que exige el formato antiguo y lanza una excepción a propósito si recibe el nuevo. Hubo que revertir las dos PRs enteras.

Existía un gate que lo cubría exactamente. La frase que escribí en el PR de revert: "lo habría detectado, pero el runner estaba caído y nunca llegó a ejecutarse".

Un guardarraíl que no se ejecuta no es un guardarraíl: es una creencia. Y es peor que no tenerlo, porque te hace aprobar cosas que no habrías aprobado. Desde entonces, un gate que no ha corrido cuenta como gate en rojo, no como gate ausente.

El único incidente que llegó a producción

Dos entornos distintos acabaron declarando el mismo nombre de servicio en el enrutador. El tráfico se cruzaba entre ellos de forma intermitente. Afectó a los tres entornos y tardó en verse porque no fallaba: respondía mal de vez en cuando.

El hotfix fue de una tarde. Lo que salió del post-mortem es lo que quedó: una sonda de tres pasos que corre en cada despliegue y comprueba tres cosas que antes nadie comprobaba — que el HTML servido apunta siempre al mismo build si lo pides varias veces, que ese build responde 200 todas las veces, y que las rutas de API devuelven JSON y no el HTML de la aplicación por fallback.

Las tres son triviales. Las tres habrían detectado el incidente en el primer minuto.

Lo que sigue siendo mío

Casi todo lo que firmo yo en ese repositorio son merges. El agente propone, implementa, prueba y defiende; yo decido qué entra.

Suena a poco y no lo es. El merge es el único punto del proceso que no he automatizado, a propósito. Un agente puede argumentar muy bien a favor de un cambio. No decide si ese cambio es el que el producto necesita. Esa sigue siendo la parte cara, y sospecho que va a seguir siéndolo bastante después de que el resto deje de serlo.

Crear se ha vuelto gratis; mantener no

Este verano creé también tres repositorios que no llegaron a tener un solo commit. Existen porque reservar un nombre cuesta ocho segundos. Uno de ellos iba a ser mi sitio para trastear con Glyph, el trabajo de Tsinghua que amplía la ventana de contexto renderizando el texto como imágenes y pasándoselo a un modelo de visión en vez de alargar el contexto en tokens. Busqué, vi que ya estaba hecho y mejor de lo que yo lo haría, y no escribí nada. De los tres vacíos, ese es el único acierto.

Y abandoné el proyecto en el que más código había escrito este año, con diferencia, porque salió una herramienta terminada que hacía lo mismo. No fue una derrota: fue aritmética. Mantener mi versión peor habría sido pagar por el privilegio de tenerla.

Los dos casos dicen lo mismo que el proyecto que sí sigue vivo, desde el otro lado. Montar el andamiaje de un proyecto —el repo, el CI, el README, los primeros miles de líneas que funcionan— es hoy cuestión de una tarde con un agente. Ese coste se ha desplomado. Sostenerlo no ha bajado ni un céntimo: sigue siendo atención humana, y la atención humana no escala con el modelo que uses.

Antes, montar el andamiaje dolía lo suficiente como para obligarte a pensar si merecía la pena. Ese dolor era información. Lo hemos eliminado y no hemos puesto nada en su lugar. Los guardarraíles de arriba son mi intento de poner algo: si el coste de empezar ya no te obliga a decidir, tiene que obligarte otra cosa.

// LIMITACIONES

Es una muestra de uno. Un proyecto, cinco meses, mis costumbres. No he medido a nadie más, así que esto es una hipótesis con datos detrás, no un hallazgo.

Las técnicas que describo son las que uso, no un catálogo de lo que existe. Salen de las instrucciones y la configuración de mi propio repositorio. No he comparado ninguna con alternativas ni he medido cuánto aporta cada una por separado: sé que el conjunto funciona, no qué pieza pesa más.

Los cuatro incidentes salen de mis propias PRs y commits, no de la memoria. Los he releído para escribirlos y he quitado todo lo que identificaba el producto, así que lees una versión abstracta de algo concreto. La abstracción es mía y puede haberle quitado matiz.

No puedes verificar nada de esto. El repositorio es privado y he quitado a propósito los contadores. Así que aquí tienes que creerme — y eso es exactamente lo que esta web dice que no deberías tener que hacer. Es la limitación más grande del post y prefiero decirla a maquillarla. Lo que sí puedes hacer es juzgar las técnicas por sí solas: o te resuelven un problema que tienes, o no.

No puedo separar qué agente hizo qué. Trabajé con Claude Code y con Codex alternando. Claude Code firma el commit con el modelo que participó; Codex no firma nada. Cualquier recuento por herramienta saldría sesgado hacia el único que deja rastro. GPT-6 Astra, que hoy uso a diario, salió el 3 de septiembre: llegó al final de esta historia, no la escribió.

Por qué cuento el proceso y no el producto

Podría haber escrito el post de "mira lo que he construido este verano". Habría funcionado mejor en el feed y no habría servido de nada, porque no puedes abrirlo.

El proceso sí te sirve. Si vas a dejar que un agente escriba la mayoría de tu código —y vas a hacerlo, si no lo estás haciendo ya—, la pregunta útil no es qué modelo usar. Es qué tiene que fallar solo en tu repositorio para que puedas dormir. Esa lista es corta, es aburrida, y es la diferencia entre un repositorio del que te fías y uno que tendrás que auditar entero en enero.

// Fuentes