Serie AI-Native · Ingeniería de Agentes
Una IA revisó mi repositorio y me dijo que lo empeorara
Me calificó con 8.6 sobre 10 y propuso doce mejoras. La mitad eran reales. Una habría deshecho silenciosamente la mejor decisión del código — y la única forma de notar la diferencia fue dejar de leer la revisión e ir a leer el código.
Un minuto
Una revisión de IA de mi motor de bucles de código abierto fue reflexiva, bien organizada, y basada casi por completo en mi propio README. Seis de sus doce propuestas eran vacíos reales; cinco eran cosas que yo había construido a medias y documentado mal; una ya estaba construida, y la propuesta la habría reemplazado — un registro de plugins abierto — por una lista cerrada. Construí la mejora real más grande, un archivo de contrato que describe una ejecución, y lo difícil no fue el esquema. Fue lograr que el archivo fuera capaz de rechazarme a mí. Tres reglas lograron eso, y se generalizan a cualquier archivo de gobernanza que hayas escrito alguna vez.
La revisión
Alguien corrió un modelo de frontera sobre loop-engineering-anything — mi motor de bucles para llevar una herramienta generada de calificación F a calificación A frente a un juez independiente — y me mandó el resultado. Era bueno. Era mejor que la mayoría de las revisiones de código humanas que he recibido: estructurado, específico, elogioso justo en las partes donde de verdad me había esforzado. Propuso doce mejoras, y luego un backlog adicional de veintitrés ítems.
También tenía un titular: tu arquitectura tiene demasiada forma de CLI. Su recomendación final era introducir un tipo de artefacto formal — una lista fija de las clases de cosas que el motor puede converger — para que la promesa de "cualquier cosa" del repositorio se volviera técnicamente cierta:
"Keep CLI as the first proven lane, but abstract the engine around 'artifact convergence,' not 'CLI convergence.'" (Traducción: Mantén CLI como el primer carril probado, pero abstrae el motor alrededor de la 'convergencia de artefactos', no la 'convergencia de CLI'.)
Aquí está la cosa. Cada afirmación que la revisión hizo sobre mi código citaba exactamente una fuente: mi README.
Eso no es una trampa. Es la frase más útil de este artículo. Un revisor que solo puede leer la fachada revisará la fachada, y si la fachada dice "esto genera CLIs", el revisor concluirá correctamente que esto genera CLIs. El hallazgo era real. Solo que no era un hallazgo sobre mi arquitectura. Era un hallazgo sobre mi documentación, disfrazada de arquitectura.
Así que hice lo aburrido: me pasé una hora leyendo mi propio código fuente antes de tocar una sola línea.
Lo que el código respondió
La propuesta era añadir algo así a un archivo de esquema central:
artifact_type: Literal["cli", "mcp_server", "agent_skill", "workflow", "spec"]
Una lista cerrada. Cada nueva clase de cosa que el motor de bucles pueda mejorar se convierte en una edición a ese único archivo, revisada por quien sea dueño de ese archivo.
Pero el motor de bucles ya tenía una costura de generalización, agregada ocho semanas antes, y funcionaba al revés. Un domain es un plugin pequeño que responde tres preguntas — ¿este objetivo es mío?, ¿cómo construyo la primera versión?, ¿quién lo arbitra? — y se anuncia a sí mismo a un registro al arrancar. Agregar una nueva clase de objetivo es un registro. Nada central cambia. El docstring del módulo que escribí entonces dice: "a new domain arrives as a registration, never as an edit to (Traducción: un nuevo dominio llega como un registro, nunca como una edición a)router.py o al controlador…"
La sugerencia del revisor habría tomado un sistema abierto y lo habría cerrado. No por malicia, ni por torpeza — simplemente no podía ver la costura, porque la costura no está en el README.
Seis de las doce propuestas eran vacíos reales. Cinco eran cosas que yo había construido a medias y documentado a medias. Una era esta. Redacté la adjudicación ítem por ítem y la confirmé junto al código con un commit [5], porque una revisión que discutes en privado es una revisión que volverás a litigar en seis meses.
El vacío real, y la trampa que esconde
El agujero genuino más grande resultaba bochornoso una vez nombrado. Todo lo que le dices a mi motor de bucles que haga — qué mejorar, qué tan bueno es suficientemente bueno, cuántos intentos, cuánto dinero, cuándo detenerse — existía solo como flags de línea de comandos. Tecleado en una terminal. Desaparecía al cerrar la ventana.
Así que no había ningún artefacto para la petición. Podía mostrarte una prueba hermosa de lo que había logrado una ejecución, pero no lo que se le había pedido. La solución es obvia: pon la petición en un archivo y confírmalo junto con el código.
Y ahí está la trampa, porque la solución obvia es también la forma más común en que el software miente.
Una letra que faltó, mil servidores
Kubernetes es el sistema que hace correr una gran parte de las flotas de servidores del mundo. Le dices lo que quieres en un archivo YAML. En abril de 2023, dos ingenieros de Google que anunciaban una función de validación en el blog de Kubernetes describieron lo que solía pasar cuando ese archivo tenía un pequeño error [1]:
"Before Kubernetes v1.8 (!), typos, mis-indentations or minor errors in YAMLs could have catastrophic consequences (e.g. a typo like forgetting the trailing s in replica: 1000 could cause an outage, because the value would be ignored and missing, forcing a reset of replicas back to 1)." (Traducción: Antes de Kubernetes v1.8 (!), los errores de tipeo, las indentaciones incorrectas o pequeños errores en los YAML podían tener consecuencias catastróficas (por ejemplo, un error de tipeo como olvidar la 's' final en 'replica: 1000' podía provocar una interrupción del servicio, porque el valor se ignoraría y quedaría ausente, forzando un reinicio de las réplicas a 1).
Léelo otra vez, despacio. Pediste mil. Escribiste replica en lugar de replicas. El sistema no puso ninguna objeción. Aceptó tu archivo, descartó la palabra que no reconoció, no encontró ninguna instrucción sobre cuántos servidores ejecutar y, muy servicial, volvió al valor por defecto: uno.
Tu archivo fue aceptado. Tu flota, no.
El signo de exclamación de esa cita es de los propios ingenieros. Lo pusieron ahí porque "antes de v1.8" es mucho tiempo para haber lanzado un sistema que trata una falta de ortografía como un voto silencioso a favor del valor por defecto.
Esto no es un defecto de Kubernetes; es el comportamiento por defecto de casi todo analizador de configuración jamás escrito, y proviene de una tradición de ingeniería genuinamente bien intencionada: ser liberal con lo que se acepta. En 2023, el Internet Architecture Board publicó un documento que se retracta de ese consejo, alegando que la tolerancia "ya no se considera la mejor práctica en todos los escenarios". Martin Thomson y David Schinazi escriben [2]:
"Time and experience show that negative consequences to interoperability accumulate over time if implementations silently accept faulty input." (Traducción: El tiempo y la experiencia demuestran que las consecuencias negativas para la interoperabilidad se acumulan con el tiempo si las implementaciones aceptan silenciosamente entradas defectuosas.)
Su razonamiento es la parte que vale la pena robarse. La tolerancia no es gratis ni es local: en cuanto una implementación acepta silenciosamente algo mal formado, todo lo que se construye después se construye sobre esa aceptación silenciosa y no sobre la especificación. Lo llaman una reacción en cadena — "Overapplication of the robustness principle therefore encourages a chain reaction that can create interoperability problems over time" (Traducción: La aplicación excesiva del principio de robustez, por lo tanto, fomenta una reacción en cadena que puede crear problemas de interoperabilidad con el tiempo) — y señalan que el daño es mayor en los primeros días de un sistema, cuando la rareza de hoy se convierte en el requisito de compatibilidad de mañana.
Un archivo de gobernanza joven es exactamente un protocolo joven.
El permiso firmado
Aquí está el modelo que usé, y puedes aplicarlo a cualquier archivo de configuración que tengas.
Una excursión escolar necesita un permiso firmado. Tres cosas convierten ese papel en un control real y no en un mero trámite:
- Solo puede pedir cosas que la escuela realmente ofrece. Puedes solicitar el autobús. Escribir "y un helicóptero" en el permiso no hace aparecer un helicóptero.
- Si escribes algo que la oficina no reconoce, te devuelven el permiso. No te suben al autobús mientras te dejan creer que viene un helicóptero. El peor desenlace no es un permiso rechazado; es un permiso aceptado y un niño en la pista.
- No puedes firmarlo tú mismo. Tu padre o madre puede añadir "sin maní". Ninguno de los dos puede tachar "requiere firma".
Esas tres frases son todo el diseño del contrato que implementé. En el código se leen así.
Regla 1 — compila, no extiende
Cada clave que configura algo se convierte en un parámetro que el motor de bucles ya obedece. El archivo no puede introducir una perilla nueva; solo puede rellenar una que ya existe. Ocho claves en el nivel superior, diez dentro del bloque de presupuesto, una en cada uno de los bloques de compuerta y evidencia, y nada más. La ruta gobernada por flags y la ruta gobernada por archivo le entregan entonces al motor de bucles el mismo objeto idéntico: un solo camino de código, dos puertas.
Regla 2 — una palabra desconocida es un error, no un encogimiento de hombros
Esta es la lección de Kubernetes, aplicada a rajatabla:
$ loop-anything contract check loop.yaml
Error: loop.yaml.budget: unknown key(s) ['max_iteratons']; accepted:
['compression_interval', 'max_iterations', 'max_tool_retries', 'max_wall_seconds',
'min_score_gain', 'plateau_patience', 'plateau_pivots', 'target_grade',
'target_score', 'token_budget']. A contract may only declare what the engine enforces.
El análisis del archivo falla. La ejecución no arranca. Y el mensaje de error es una lista de tareas — nombra cada clave que sí está aceptada, así que un error de tipeo te cuesta segundos en lugar de una sesión de depuración. (Esa salida es una sola línea en la terminal; aquí aparece envuelta en varias líneas para que quepa en la página.)
La segunda mitad de esta regla es la que realmente exigió criterio. El esquema propuesto por quien hizo la revisión contenía un bloque como este:
safety:
forbidden:
- delete_production_data
- expose_credentials
Suena maravilloso: pasaría cualquier revisión de cumplimiento sin el menor tropiezo. Y en mi motor de bucles no lo leería absolutamente nada — la aplicación real de las reglas vive en otra parte, en un entorno aislado que rechaza metacaracteres de shell y confina las escrituras a un espacio de trabajo. Aceptar ese bloque habría creado un segundo sistema de seguridad, puramente decorativo, cuya única función sería que lo citaran en una diapositiva.
Una regla que nadie hace cumplir es un cartel de "No Fumar" en un edificio sin detector de humo. No detiene el incendio. Solo significa que ahora el incendio está fuera de norma.
Así que el analizador lo rechaza, nombrándolo, con su razón. Lo mismo ocurre con una clave domain: (la ruta de ejecución todavía no puede forzar un dominio, así que aceptarla no haría nada), y con los pesos de puntuación por dimensión (el juez es dueño de su propia rúbrica; un archivo escrito por lo que está siendo evaluado nunca debe orientar la evaluación). Cada una se vuelve legal en el momento en que algo la hace cumplir. El mensaje de error es la hoja de ruta.
Regla 3 — la compuerta solo se endurece
Mi motor de bucles tiene una regla que precede a todo esto: cuando una ejecución reporta éxito, eso es una afirmación, no un hecho ya entregado, hasta que un humano lo confirme. Existe un flag que aporta la aprobación de un humano en una ejecución supervisada. Deliberadamente no existe ningún flag que desactive la compuerta — y una ejecución programada y sin supervisión que intenta preaprobar su propio trabajo es rechazada de plano.
El diseño del contrato del revisor incluía un bloque human-gate (compuerta humana) con un booleano: require_confirm_before_ship: true. Lo cual significa que también se puede escribir false. Y el contrato lo redacta la misma persona que quiere que la ejecución se publique.
Así que en el mío, ese campo puede omitirse o fijarse en true. Fijarlo en false es un error de parseo, con su explicación. Un contrato puede sumar revisores; nunca puede quitar la firma. El permiso firmado no puede dejar de exigir la firma del padre.
Vale la pena ser precisos, ya que este artículo trata de precisión: ese campo compila a nada en tiempo de ejecución. La compuerta ya está activada por defecto, y ningún camino del código lee esa clave. Su única función es hacer que false sea imposible de escribir de otra forma — el único lugar del contrato donde el parser no es una puerta hacia un ajuste, sino la aplicación misma. Según mi propia Regla 2, esa es una clave sin lector, y la conservo solo porque lo que rechaza merece ser rechazado en voz alta. (La compuerta tiene exactamente un desvío, y no es para que lo use quien invoca el sistema: una variable de entorno que controla la plataforma de compilación, jamás respetada en una ejecución programada.)
Evidencia: la parte que impide que sea solo una promesa
La última sección del archivo enumera lo que la ejecución debe demostrar — las calificaciones antes y después, la diferencia por dimensión, las pruebas de regresión registradas cuando se acepta una corrección. Existen siete de estos elementos, y cada nombre que escribas debe ser uno de esos siete, porque son las únicas cosas que una ejecución real puede producir.
Luego un comando verifica el archivo contra la ejecución realmente registrada:
$ loop-anything contract evidence loop.yaml --run 12
present grade_trajectory
MISSING regression_tests
Error: run #12 is missing declared evidence: regression_tests
Código de salida 1. Mis paquetes de evidencia omiten, en lugar de fabricar, un campo del que no tienen fuente, así que un elemento faltante es una brecha genuina entre lo prometido y lo demostrado — en ese ejemplo, una ejecución que convergió sin registrar jamás una prueba que detectara el regreso del error.
Esa es la diferencia entre un artefacto de gobernanza y una estética de gobernanza. Uno de los dos puede imprimir un fallo con tu nombre puesto.
Por qué el revisor se equivocó de una forma que importa
Hay una segunda razón por la que no me limité a ejecutar el backlog, y no tiene que ver con esta revisión en particular.
La disciplina central del motor de bucles es que lo que construye nunca es lo que califica. Arjun Panickssery, Samuel Bowman y Shi Feng le dieron una medición a esa disciplina en 2024 [3]:
"One such bias is self-preference, where an LLM evaluator scores its own outputs higher than others' while human annotators consider them of equal quality." (Traducción: uno de estos sesgos es la autopreferencia, en la que un evaluador LLM califica sus propias salidas más alto que las de otros, mientras que los anotadores humanos las consideran de igual calidad.)
Van más allá, y aquí está la parte incisiva: afinar modelos para que reconozcan mejor su propio texto produjo "a linear correlation between self-recognition capability and the strength of self-preference bias." (Traducción: una correlación lineal entre la capacidad de autorreconocimiento y la intensidad del sesgo de autopreferencia.) Cuanto mejor reconoce un modelo su propia voz, más la favorece. La autoevaluación no falla al azar; falla de manera direccional.
Que una IA revise tu repositorio no es lo mismo que una IA calificando su propia salida — el revisor no tenía nada en juego en mi código. Pero el movimiento de fondo es idéntico, y es justo el movimiento que este motor de bucles entero existe para rechazar: aceptar un veredicto de una parte cuya evidencia no has verificado. La evidencia de la revisión era mi README. Cuando la comparé con el código fuente, cinco de sus ocho puntajes por dimensión cambiaron en mi propia recalificación: tres bajaron, dos subieron. La dimensión que peor calificó, generalidad, es la que más subí; las dimensiones que bajé fueron seguridad, observabilidad y preparación empresarial, con las que había sido más generosa de lo que el código merece.
Patrones y antipatrones
Patrones. Haz que el analizador falle de forma cerrada, y que el mensaje de error enumere las opciones válidas. Deja que un archivo de política solo pueda endurecer un valor por defecto, nunca aflojarlo. Nombra qué leerá cada campo antes de aceptar ese campo. Verifica la evidencia declarada contra la evidencia producida con un código de salida. Publica la adjudicación de una revisión junto al código, para que el argumento quede bajo control de versiones.
Antipatrones. Una clave de configuración que nadie lee. Un bloque de seguridad en un archivo que ningún código de seguridad abre. Un booleano que le permite al autor eximirse de su propia revisión. Un esquema cuya lista cerrada de tipos hay que editar para extender el sistema. Ejecutar un backlog solo porque estaba numerado y bien formateado. Y el más antiguo de todos: confundir que algo se haya analizado con que se haya entendido.
El mecanismo, en una frase
El mecanismo tiene nombre: la brecha de cumplimiento — la distancia entre lo que un archivo de configuración declara y lo que cualquier línea de código realmente lee. Ahí viven todos los bugs silenciosos de configuración y todo artefacto de cumplimiento puramente decorativo, y la brecha es invisible por construcción, porque un archivo que se ignora se ve exactamente igual que uno que se acata.
El caso más grande registrado no es siquiera un archivo de configuración. Do Not Track era un encabezado que tu navegador enviaba, una vez que lo activabas, para decir que no querías ser rastreado. Los navegadores lo enviaron fielmente durante años. Ningún servidor estuvo jamás obligado a leerlo. Su grupo de trabajo cerró el 17 de enero de 2019 y sus especificaciones fueron retiradas [4] — una década de una preferencia transmitida a la perfección que nada estaba obligado a acatar.
El primer principio, entonces: una declaración que no puede fallar no es un control, es un disfraz — así que la única manera honesta de añadir gobernanza a un sistema es añadir la posibilidad de rechazo al mismo tiempo.
Lo que este artículo no afirma
- Sin benchmark. No he medido si el contrato reduce la mala configuración en el mundo real. Es un argumento de diseño más una implementación, no un estudio.
- La revisión no era mala. La mitad era correcta, y su mejor idea —medir con qué frecuencia mi motor de bucles reporta éxito cuando un juez más exigente estaría en desacuerdo— es justo lo que todavía no he construido, y lo que más necesito.
- Un motor, un autor. Las tres reglas provienen de mi propio código base. La evidencia de Kubernetes y del IAB respalda el principio, no mi implementación particular de este.
- La cantidad de pruebas no es calidad. La suite pasó de 543 a 600 pruebas exitosas. Eso mide la cobertura del código nuevo, no que el diseño sea correcto.
La conclusión
La mejora se implementó en un día. La lección tomó todo el día también, y no tuvo nada que ver con YAML.
Cuando un sistema muy fluido te entrega una lista muy bien organizada de lo que está mal en tu trabajo, la fluidez de la lista no te dice nada sobre su fundamento. Pregunta qué leyó. Después ve y lee lo que no pudo leer.
Y cuando por fin escribas el archivo de gobernanza —el contrato, la política, el estándar, la lista de verificación— escribe primero el rechazo. Si no puedes describir la entrada que lo haría imprimir un error con tu nombre, no has construido un control. Has impreso un cartel.
Referencias
- Jeffrey Ying y Antoine Pelisse (Google), Kubernetes 1.27: Server Side Field Validation and OpenAPI V3 move to GA, Kubernetes Blog, 24 de abril de 2023. kubernetes.io/blog/2023/04/24/openapi-v3-field-validation-ga. Citado textualmente; el signo de exclamación entre paréntesis es de ellos.
- M. Thomson y D. Schinazi, Maintaining Robust Protocols, RFC 9413, Internet Architecture Board (IAB) stream, junio de 2023. rfc-editor.org/rfc/rfc9413. Ambas citas fueron verificadas palabra por palabra contra el texto del RFC (Secciones 2 y 4.1).
- Arjun Panickssery, Samuel R. Bowman y Shi Feng, LLM Evaluators Recognize and Favor Their Own Generations, arXiv:2404.13076, 15 de abril de 2024. arxiv.org/abs/2404.13076. Citas tomadas del resumen (abstract).
- W3C Tracking Protection Working Group. La página del grupo indica: "The working group is currently closed. It closed on 17 January 2019," (Traducción: "El grupo de trabajo está actualmente cerrado. Cerró el 17 de enero de 2019") y enumera los documentos de Tracking Preference Expression (DNT) entre las especificaciones retiradas. w3.org/2011/tracking-protection.
- La adjudicación punto por punto de la revisión, incorporada junto al código que discute: docs/solutions/external-eval-2026-08-12.md. El contrato en sí está en
src/loopeng/contracts/run_contract.py, su nota de diseño está endocs/solutions/run-contract.md, y el ejemplo anotado está endocs/examples/loop.yaml, en github.com/wjlgatech/loop-engineering-anything.
Escrito el 2026-08-12. La revisión que lo motivó fue producida por un modelo de lenguaje de frontera a pedido de un lector; conservé su argumento, no su identidad, porque lo importante es la procedencia, no el modelo.