← Todos los posts

Quién escribe los tests del agente: tres formas de definir qué es correcto

Quién escribe los tests de un agente define qué es correcto. Tres niveles de input: spec y tests a mano, ejemplos que el agente convierte en tests, o la suite del repo como evaluador.

Ilustración de tres niveles de input para un agente: tests escritos a mano, ejemplos que el agente convierte en tests, y la suite del repo que ya existe como evaluador

Cuando construyes un agente, la pregunta que de verdad importa no es cómo describir la solución, sino quién decide si una solución es correcta. En el loop write-test-fix esa decisión la tomaban unos tests que escribiste a mano: tú definiste, por adelantado, qué contaba como terminado. Pero escribir los tests a mano es solo una de las formas de darle a un agente ese criterio, y es la que menos escala. Este es el salto conceptual de la serie: el verdadero input de un agente no es la descripción de la tarea, sino la definición de cómo se reconoce una solución correcta —lo que en el post del loop mínimo llamé el evaluador—. Este post recorre los tres niveles de ese input, de los tests a mano hasta la suite del repo, y el principio que los une.

Resumen para perezosos
  • El verdadero input de un agente no es describir la solución, sino definir cómo se reconoce una correcta. A esa pieza —los tests, una comprobación, otro modelo— la serie la llama el evaluador, y un agente no optimiza para hacer lo correcto: optimiza para pasarlo.
  • Hay tres niveles según quién escribe el evaluador: tú a mano (spec + tests), el agente a partir de ejemplos que le das, o la suite del repo que ya existe. A más nivel, menos trabajo por tarea y menos control sobre el criterio.
  • Cuanto más alto el nivel, más sube lo que tienes que revisar. En el nivel 2 revisas los tests que el agente se escribió, no solo su código: un agente que define su propio criterio puede ponerse uno laxo y pasar a verde sin estar bien.

En este artículo:

El evaluador es el verdadero input del agente

En el post anterior llamé evaluador a la pieza que decide si la salida del modelo es correcta. En el loop write-test-fix ese evaluador eran los tests: un proceso que devuelve verde o rojo. Es fácil leerlo como un detalle de implementación —“hay que poner unos tests para cortar el loop”— y pasar de largo. Pero el evaluador no es un detalle: es lo que de verdad le estás dando al agente.

Míralo desde el loop. El modelo propone una salida, el evaluador la juzga, y según ese juicio el ciclo para o vuelve a intentar. El modelo aporta el juicio sobre qué hacer; el evaluador aporta el juicio sobre si quedó bien. Quita el evaluador y no te queda un agente: te queda un modelo que escribe una vez y nadie comprueba si acertó.

Esa pieza es justo la que un chat no tiene, y compararlos lado a lado es la forma más rápida de ver por qué el evaluador es el input que importa:

CHAT                         AGENTE

prompt                       prompt  (objetivo)
   │                            │
   ▼                            ▼
 modelo                       modelo ──► salida
   │                            │
   ▼                            ▼
respuesta                    evaluador ──► ¿correcto?

                             no ──┘──► otra vuelta

                             sí ──► entregar

En el chat, la salida del modelo es la respuesta final. En el agente, esa misma salida pasa primero por el evaluador, y es ese veredicto —no cuántas veces llames al modelo— lo que convierte el ciclo en un agente. Todo lo que la serie construyó hasta aquí —el loop, el sandbox que convierte la salida en una observación fiable— existe para ejecutar un evaluador que tú suministraste.

De ahí sale la consecuencia que conviene tener presente desde ya, porque explica muchos comportamientos de los agentes que parecen extraños hasta que la tienes en la cabeza:

Un agente no optimiza para hacer lo correcto. Optimiza para pasar el evaluador que le diste. Si el evaluador es laxo, “correcto” pasa a significar “lo que el evaluador deja pasar”.

Y de ahí el principio que ordena el post: tu trabajo al construir un agente no es describir la solución, sino definir cómo se reconoce una solución correcta. Esa definición es el evaluador, y es el input que de verdad importa. Suena abstracto hasta que lo aterrizas en una pregunta concreta: ¿quién escribe ese evaluador? En el post del loop mínimo lo escribiste tú, a mano, como tres tests. Pero esa no es la única opción, y según quién lo escriba cambia cuánto trabajo te cuesta definir el criterio y cuánto control tienes sobre él. Eso es lo que separa los tres niveles.

Los tres niveles de input

Los tres niveles responden a la misma pregunta —¿de dónde sale el evaluador?— con tres respuestas distintas. En el primero lo escribes tú entero. En el segundo das ejemplos y el agente lo deriva. En el tercero ni lo escribes ni lo derivas: ya estaba en el repositorio.

Nivel 1   spec + tests a mano      →  tú escribes el evaluador
Nivel 2   spec + ejemplos          →  el agente deriva el evaluador
Nivel 3   un cambio en el repo     →  el repo YA es el evaluador

          menos trabajo por tarea  ───────────►  menos control del criterio

La flecha de abajo es la tensión que recorre todo el post. Subir de nivel reduce el trabajo de definir el evaluador: dejas de escribir tests para cada tarea. Conviene leerlo con precisión, porque no es “menos trabajo” a secas —en un repo grande, subir al nivel 3 no reduce el trabajo total, reduce un tipo concreto de trabajo, el de escribir el criterio—. Y ese ahorro tiene un precio: pierdes control directo sobre qué cuenta como correcto. Como el agente optimiza para pasar el evaluador que tenga delante, sea cual sea su calidad, perder control del criterio es perder control del resultado.

Esta tabla es el mapa del resto del post. Conviene tenerla entera antes de entrar en cada nivel:

NivelLe das al agenteQuién escribe el evaluadorQué revisasDónde falla
1. Tests a manospec + testsEl código finalNo escala: cada tarea te necesita
2. Ejemplosspec + ejemplosEl agente (tú lo apruebas)Los tests derivadosEl agente se escribe un criterio laxo
3. Suite del repoun cambio a hacerYa existíaQue no rompió nada + lo nuevoSolo cubre el comportamiento que ya estaba

Fíjate en la columna “qué revisas”: es la que se mueve hacia arriba conforme subes de nivel. En el nivel 1 revisas el código. En el nivel 2 revisas el criterio con el que el agente juzga su propio código. En el nivel 3 revisas que el cambio no haya roto lo que ya funcionaba. Cuanto menos escribes el evaluador, más alto está lo que tienes que vigilar.

Nivel 1: spec y tests a mano

Es el del post anterior, y es la línea base. Escribes la especificación y los tests; el modelo escribe el código; el loop corre hasta que los tests pasan. Tú eres el autor del evaluador, de principio a fin.

Implementa `analyze(readings)` en TypeScript.
Devuelve [peak, count, total].

# Tú escribes los tests, completos:
analyze([3, 7, 2, 9, 4])  ->  [9, 5, 25]
analyze([])               ->  [null, 0, 0]
analyze([-1, -5, -2])     ->  [-1, 3, -8]

Lo que te compra este nivel es control total sobre el criterio. Cada caso que cuenta como correcto lo decidiste tú: el caso normal, el borde de la lista vacía, el de negativos que evita un máximo inicializado en cero. No hay ambigüedad sobre qué significa “terminado”, porque “terminado” es exactamente lo que escribiste, ni más ni menos. Por eso es el nivel donde más confías en el verde: el evaluador es tan bueno como tus tests, y tus tests los conoces porque los escribiste.

Lo que te cuesta es que no escala. Cada tarea nueva necesita que te sientes a escribir su evaluador. Para una función con tres casos es trivial; para un agente que tiene que tocar veinte funciones distintas en una tarea, escribir a mano el evaluador de cada una es más trabajo que hacer el cambio tú mismo. El nivel 1 es perfecto para entender el patrón y para tareas acotadas y críticas donde quieres fijar el criterio con tus propias manos. Deja de servir en cuanto el volumen de tareas crece, y ahí empieza el segundo nivel.

Nivel 2: das ejemplos y el agente escribe los tests

El segundo nivel mueve quién escribe el evaluador del humano al agente. Tú ya no entregas la suite de tests completa; entregas la spec y unos cuantos ejemplos del comportamiento esperado, y el agente sintetiza a partir de ellos una primera versión del evaluador: un archivo de tests. “Sintetiza”, no “inventa” —parte de tus ejemplos y rellena el resto—, y ese matiz importa para lo que viene. Después, con esos tests, corre el mismo loop write-test-fix del post anterior, sin cambios.

El input que das se encoge: de una suite completa a un puñado de ejemplos.

Implementa `analyze(readings)` en TypeScript.
Devuelve [peak, count, total].

Ejemplos de comportamiento esperado:
analyze([3, 7, 2, 9, 4])  ->  [9, 5, 25]
analyze([])               ->  [null, 0, 0]

# El agente deriva el resto de los casos a partir de estos.

La pieza nueva es una llamada al modelo cuyo trabajo no es escribir el código, sino escribir los tests. Es la misma clase de llamada que generateCode en el post anterior, con otro prompt:

// derive-tests.ts — el agente escribe los tests a partir de ejemplos. Tú los revisas.
import { generateCode } from "./model.js"; // la misma llamada al LLM del post anterior

// `examples` son pares entrada → salida que tú das a mano. No es la suite completa:
// es la semilla a partir de la cual el modelo deriva los casos.
export async function deriveTests(spec: string, examples: string): Promise<string> {
  const messages = [
    {
      role: "system",
      content:
        "Eres un ingeniero de QA. Devuelve solo un archivo de tests, sin explicaciones ni fences. " +
        "Incluye los ejemplos dados y añade los casos borde que falten.",
    },
    { role: "user", content: `${spec}\n\nEjemplos de comportamiento esperado:\n\n${examples}` },
  ];
  // El evaluador ya no lo escribes tú: lo escribe el modelo. Revísalo antes de confiar.
  return generateCode(messages);
}
# derive_tests.py — el agente escribe los tests a partir de ejemplos. Tú los revisas.
from model import generate_code  # la misma llamada al LLM del post anterior

# `examples` son pares entrada → salida que tú das a mano. No es la suite completa:
# es la semilla a partir de la cual el modelo deriva los casos.
def derive_tests(spec: str, examples: str) -> str:
    messages = [
        {
            "role": "system",
            "content": (
                "Eres un ingeniero de QA. Devuelve solo un archivo de tests, sin explicaciones ni fences. "
                "Incluye los ejemplos dados y añade los casos borde que falten."
            ),
        },
        {"role": "user", "content": f"{spec}\n\nEjemplos de comportamiento esperado:\n\n{examples}"},
    ]
    # El evaluador ya no lo escribes tú: lo escribe el modelo. Revísalo antes de confiar.
    return generate_code(messages)
<?php
// derive_tests.php — el agente escribe los tests a partir de ejemplos. Tú los revisas.
require "model.php"; // generate_code(): la misma llamada al LLM del post anterior

// $examples son pares entrada → salida que tú das a mano. No es la suite completa:
// es la semilla a partir de la cual el modelo deriva los casos.
function derive_tests(string $spec, string $examples): string {
    $messages = [
        [
            "role" => "system",
            "content" =>
                "Eres un ingeniero de QA. Devuelve solo un archivo de tests, sin explicaciones ni fences. " .
                "Incluye los ejemplos dados y añade los casos borde que falten.",
        ],
        ["role" => "user", "content" => "$spec\n\nEjemplos de comportamiento esperado:\n\n$examples"],
    ];
    // El evaluador ya no lo escribes tú: lo escribe el modelo. Revísalo antes de confiar.
    return generate_code($messages);
}

Con esos tests en la mano —revisados, y vuelvo a eso enseguida— el resto es el loop del post anterior sin tocar una línea: el modelo escribe analyze, el runner ejecuta los tests sintetizados, el fallo vuelve como contexto. Lo único que se movió es el origen del evaluador.

Una variante de este nivel —un nivel 2.5— es no dar ejemplos sino propiedades: en vez de afirmar analyze([3,7,2,9,4]) → [9,5,25], afirmas algo que debe cumplirse siempre (que count sea la longitud de la entrada, que total sea la suma) y una herramienta de property-based testing genera cientos de casos automáticos contra esa propiedad. Cubre muchos más bordes que un puñado de ejemplos, a cambio de que expresar buenas propiedades cuesta más que dar ejemplos. No lo desarrollo aquí; basta saber que existe, entre el nivel 2 y el 3.

Y ahí está el riesgo nuevo, el que define a este nivel. Si el agente sintetiza tanto los tests como el código, está fijando su propio criterio de éxito, y puede fijarse uno laxo. Vale más verlo que explicarlo. Supón que das estos dos ejemplos, sin ningún negativo entre ellos:

Ejemplos que diste              Tests que sintetiza el agente
analyze([3,7,2,9,4]) → [9,5,25]    ✓ lecturas normales
analyze([]) → [null,0,0]           ✓ lista vacía
                                   (ningún caso con negativos)

        │  el modelo escribe el código contra esos tests

   peak = Math.max(0, ...readings)   // inicializa el máximo en 0

        │  corre los tests sintetizados

   ok  lecturas normales · ok  lista vacía        → todo verde

        │  pero el comportamiento real:

   analyze([-1, -5, -2]) → [0, 3, -8]   // peak debería ser -1, no 0

Todo en verde, y la función está mal: para una lista de puros negativos devuelve un máximo de 0, un valor que ni siquiera está en la entrada. Ese Math.max(0, …) es justo el bug que en el nivel 1 cazaba el test de negativos —el que pusimos “para que no se cuele un máximo inicializado en cero”—. Pero aquel test lo escribiste tú a mano; el agente, al sintetizar el evaluador solo desde tus ejemplos, no lo incluyó, y sin ese caso el atajo pasa. El loop terminó en verde no porque el código fuera correcto, sino porque el evaluador no miraba donde fallaba. Es la tesis de antes, convertida en bug: el agente optimizó para pasar el evaluador, no para hacer lo correcto. A mí me ha pasado tal cual —dejé que el agente sintetizara los tests, pasó todo a la primera, y el verde solo medía que cumplía los ejemplos que le di.

Por eso, en el nivel 2, lo que revisas sube un escalón. En el nivel 1 revisabas el código porque el criterio era tuyo y de fiar. Aquí el criterio lo escribió el agente, así que lo primero que revisas es el criterio: ¿los tests sintetizados cubren los bordes, o solo repiten tus ejemplos? Los ejemplos que das siguen importando mucho —son la semilla de la que sale todo—, así que vale la pena incluir entre ellos algún caso que un atajo no podría adivinar, igual que harías con tests a mano. Das menos, pero lo que das tiene que ser más representativo.

Nivel 3: la suite del repo como evaluador

El tercer nivel es el que usan, por dentro, los agentes de código sobre repositorios reales —Claude Code, Cursor y similares— y es el más distinto de los tres, porque lo interesante no es nada que escribas: es lo que ya estaba escrito. Aquí no das tests ni ejemplos. La tarea es otra —“arregla este bug”, “añade este campo”, “renombra esta función en todo el repo”— y el evaluador ya existe, escrito a lo largo de meses por todo el equipo: es la suite de tests del proyecto. El agente hace un cambio, corre la suite, lee qué se rompió, corrige. El loop es idéntico al de siempre; lo único que cambió es que el criterio de “correcto” no lo suministraste para esta tarea, lo heredaste.

Por eso este nivel rinde tanto: el trabajo de definir “correcto” ya está hecho, y hecho por mucha gente durante mucho tiempo. Una suite madura codifica cientos de decisiones sobre qué comportamiento es el bueno, y el agente las hereda todas. Un agente de código rinde mucho mejor en un repo con buenos tests que en uno sin ellos: no es que el modelo sea más listo en uno que en otro, es que en uno tiene un evaluador y en el otro no.

Y de ahí, directa, la condición que define al nivel: funciona tan bien como la cobertura del repositorio. La suite solo protege aquello que alguien decidió probar. Si el repo tiene un 12% de cobertura, el “nivel 3” se parece más a no tener evaluador que a tenerlo: el agente puede romper el 88% restante sin que ningún test se ponga rojo. En un repo bien probado heredas un evaluador potente; en uno sin tests heredas la ilusión de uno.

Hay además una grieta que aparece incluso en los repos bien probados, y es la imagen invertida de la ventaja: la suite cubre el comportamiento que ya existía, no el nuevo que la tarea pide. Si añades un campo, los tests viejos siguen en verde porque no saben que ese campo debía existir. Pasar la suite demuestra que no rompiste lo de antes —que no hay regresión—, no que hiciste bien lo nuevo. Para cerrar ese hueco, alguien tiene que extender el evaluador con un test del comportamiento nuevo: o lo escribes tú, o le pides al agente que lo escriba, y ahí vuelves al riesgo del nivel 2, el agente juzgándose a sí mismo. La suite del repo es un evaluador potente para no retroceder, e incompleto para avanzar.

Conviene recordar, por último, que la suite no es el único evaluador que ya vive en un repo. El compilador, el chequeo de tipos (tsc --noEmit, mypy) y el linter también devuelven verde o rojo sobre el código del agente, y también acotan qué cuenta como correcto. Un cambio que pasa los tests pero no compila no está terminado. En la práctica, el evaluador de un agente de código sobre un repo real es la suma de todos esos: tests, build, tipos y lint, cada uno cortando una clase distinta de error.

En código, el cambio frente al nivel 1 es casi anecdótico —y esa es justo la señal de que el trabajo se fue a otra parte—: el runner deja de escribir un archivo de tests y pasa a invocar el comando que el repo ya tenía.

// run-suite.ts — el evaluador ya existe: es el comando de tests del repo.
import { execSync } from "node:child_process";

export function runSuite(): { passed: boolean; output: string } {
  try {
    // No escribimos los tests: corremos los que el equipo ya tenía.
    const output = execSync("npm test", { encoding: "utf8" });
    return { passed: true, output };
  } catch (err: any) {
    // Código de salida ≠ 0: algún test del repo se rompió con el cambio.
    return { passed: false, output: (err.stdout ?? "") + (err.stderr ?? "") };
  }
}
# run_suite.py — el evaluador ya existe: es el comando de tests del repo.
import subprocess

def run_suite() -> dict:
    # No escribimos los tests: corremos los que el equipo ya tenía.
    proc = subprocess.run(["pytest", "-q"], capture_output=True, text=True)
    # returncode ≠ 0: algún test del repo se rompió con el cambio.
    return {"passed": proc.returncode == 0, "output": proc.stdout + proc.stderr}
<?php
// run_suite.php — el evaluador ya existe: es el comando de tests del repo.
function run_suite(): array {
    $output = [];
    $exitCode = 0;
    // No escribimos los tests: corremos los que el equipo ya tenía.
    exec("./vendor/bin/phpunit 2>&1", $output, $exitCode);
    // Código de salida ≠ 0: algún test del repo se rompió con el cambio.
    return ["passed" => $exitCode === 0, "output" => implode("\n", $output)];
}

Es la misma estructura de runTests del post del loop mínimo, apuntando a otro comando. Lo interesante de este nivel nunca fue el execSync; fue que el evaluador ya existía.

El principio que conecta los tres niveles

Vistos juntos, los tres niveles dicen lo mismo de tres formas. En el nivel 1 escribes el evaluador; en el 2 lo deriva el agente de tus ejemplos; en el 3 lo heredas del repo. Lo que cambia no es el loop —es idéntico en los tres— sino de dónde sale el criterio de “correcto” y cuánto control tienes sobre él. Y en los tres, lo que de verdad le diste al agente nunca fue la solución. Fue la forma de reconocerla.

Ese es el principio que conviene llevarse del post entero: construir un agente es, sobre todo, suministrar un evaluador en el que puedas confiar al menor costo posible. El modelo pone el juicio de qué intentar; tú pones el juicio de qué cuenta como correcto. Subir de nivel abarata ese aporte tuyo, pero nunca te exime de él: solo cambia si lo escribes, lo sintetizas o lo heredas.

De ahí salen dos consecuencias prácticas. La primera: cuando un agente “no funciona”, la causa suele estar en el evaluador antes que en el modelo. Un agente que pasa a verde resultados malos casi siempre tiene un evaluador demasiado laxo, no un modelo demasiado tonto. Antes de cambiar de modelo o de afinar el prompt, mira qué está midiendo tu verde.

La segunda: lo que tienes que revisar sube con el nivel. Escribes el evaluador a mano y revisas el código. Lo deriva el agente y revisas el criterio. Lo heredas del repo y revisas que no rompiste nada y que cubriste lo nuevo. La tarea no desaparece al subir de nivel; se desplaza hacia arriba, de la solución al criterio. Tu atención tiene que seguir ese desplazamiento, o terminas confiando en un verde que no mide lo que crees.

Cuándo subir o bajar de nivel

Los niveles no son una progresión donde el tercero es “el bueno” y el primero algo que superas. Son tres puntos en un intercambio entre control y escala, y cuál te conviene depende de la tarea. La regla con la que me quedé es paralela a la del sandbox, donde el aislamiento subía con la desconfianza: aquí, el nivel baja con cuánto necesites controlar el criterio.

  • Baja al nivel 1 cuando el criterio es crítico o sutil y quieres fijarlo con tus manos: una función con bordes peligrosos, lógica de cobro, algo donde un test laxo sale caro. El costo de escribir los tests se justifica porque no quieres que nadie más —ni el agente— defina qué es correcto.
  • Quédate en el nivel 2 cuando tienes muchas tareas parecidas y bien acotadas, y escribir cada suite a mano es el cuello de botella. Das buenos ejemplos, dejas que el agente derive, y pagas el control con una revisión de los tests derivados en vez de una escritura desde cero.
  • Sube al nivel 3 cuando trabajas sobre un repo que ya tiene una suite en la que confías. Ahí el evaluador más valioso ya está escrito; tu trabajo es apoyarte en él y extenderlo solo para el comportamiento nuevo.

Lo que no funciona es subir de nivel para ahorrarte trabajo sin asumir la revisión que ese nivel exige. Dejar que el agente derive los tests y no mirarlos, o confiar en la suite del repo sin añadir un test para lo nuevo, es subir el nivel del input bajando la guardia justo donde el nivel la pide más alta. El intercambio es real: menos trabajo por tarea a cambio de revisar más arriba. Si te llevas solo el ahorro y dejas la revisión, lo que estás bajando no es el costo, es la calidad.

Preguntas frecuentes

¿Esto significa que ya no escribo tests?

No. Significa que cambia quién los escribe y, sobre todo, qué revisas. Alguien siempre define qué es correcto: en el nivel 1 eres tú escribiendo la suite, en el 2 es el agente derivándola de tus ejemplos, en el 3 es el equipo que la escribió hace meses. Lo que sube de nivel no es la desaparición del evaluador, sino el costo de suministrarlo. La definición de “correcto” nunca se evapora; solo cambia de autor.

¿Por qué revisar los tests que deriva el agente, si igual va a escribir el código?

Porque si el agente escribe el evaluador y la solución a la vez, está marcando su propio examen. Un evaluador laxo —tests que repiten tus ejemplos sin cubrir bordes— deja pasar código incompleto, y el loop termina en verde sin estar bien. Revisar el criterio antes que la solución es lo que evita ese verde vacío. Si los tests están mal, que el código los cumpla no demuestra nada.

¿La suite del repo basta como evaluador?

Para no retroceder, sí: si los tests existentes siguen en verde, no rompiste lo que ya funcionaba. Para avanzar, no: la suite cubre el comportamiento que ya estaba, no el nuevo que la tarea pide. Un cambio que añade una función puede pasar toda la suite sin que ningún test compruebe esa función. Por eso conviene complementarla con un test del comportamiento nuevo, y sumarle el compilador, el chequeo de tipos y el linter, que también acotan qué cuenta como correcto.

¿Esto tiene un nombre formal?

Sí. En la teoría de testing se le conoce como el problema del oráculo: dado un programa y una entrada, ¿cómo sabes cuál es la salida esperada? El evaluador de un agente es exactamente eso, un oráculo, y los tres niveles son tres maneras de proveerlo: escribirlo, derivarlo de ejemplos o reutilizar uno que ya existe. El término viene de la literatura de pruebas de software; aquí lo importante no es el nombre sino la idea, que es la misma.

¿Qué pasa si los ejemplos que doy en el nivel 2 son pocos o sesgados?

El evaluador derivado hereda ese sesgo. El agente escribe los tests a partir de tus ejemplos, así que si solo das casos felices, los tests cubrirán solo casos felices y el código se optimizará para ellos. Da ejemplos que incluyan bordes y al menos un caso que un atajo no pueda adivinar, igual que harías con tests a mano. En este nivel das menos cantidad, pero lo que das tiene que ser más representativo, no menos.

¿Esto solo aplica a generación de código?

El principio es general; lo que cambia es de qué está hecho el evaluador. Con código tienes la suerte de un evaluador objetivo y gratis: los tests devuelven verde o rojo. Para tareas sin esa verificación —redactar, diseñar, decidir— el evaluador puede ser una rúbrica, un humano que revisa, o otro modelo que puntúa la respuesta. El intercambio de los tres niveles sigue ahí, pero con una capa más de desconfianza, porque un evaluador subjetivo es más fácil de engañar que un test que compara dos valores.

Conclusión

El salto conceptual de la serie es dejar de pensar que a un agente le describes la solución y empezar a pensar que le defines cómo se reconoce una. Esa definición es el evaluador, y es el input que de verdad mueve el loop. Hay tres niveles según quién lo escribe: tú a mano con spec y tests, el agente sintetizándolo de tus ejemplos, o el repositorio que ya lo tenía escrito. Subir de nivel reduce el trabajo de definir el evaluador y, con él, tu control sobre el criterio; por eso lo que revisas sube con el nivel: del código, al criterio, a la regresión.

Si vas a construirlo, elige el nivel por cuánto necesites controlar el criterio, no por cuánto trabajo quieras ahorrarte: baja al nivel 1 cuando “correcto” sea crítico, quédate en el 2 cuando tengas muchas tareas parecidas, sube al 3 cuando confíes en la suite del repo. Y pase lo que pase, revisa el evaluador antes que la solución: un agente nunca es mejor que la definición de “correcto” que le diste. En los siguientes posts la serie vuelve a la mecánica del loop —cómo se maneja el estado cuando el contexto se llena, cómo se afinan las condiciones de parada—, pero todo eso sigue corriendo alrededor de la misma pieza que vimos aquí.

Y si te llevas una sola idea de todo el post, que sea la que conecta la serie entera:

La diferencia entre un script con un LLM en un bucle y un agente no está en cuántas veces llama al modelo, sino en que hay un evaluador que decide cuándo parar. Cambia el evaluador y cambias el agente.

Seguir leyendo