Empezar aquí
Índice de la sección
Lección 5.3 · práctica

Explorar, planificar, implementar, verificar

El flujo de cuatro fases que separa entender de tocar, con herramientas distintas en cada fase y un plan revisable antes de cambiar código.

Publicado 50 min
Al terminar podrás
  • Explicar qué produce cada fase del flujo y por qué separarlas reduce errores
  • Implementar un agente por fases con herramientas de solo lectura en la exploración
  • Diseñar un plan como artefacto revisable con criterio de verificación por paso

Requiere: Cómo trabaja un agente de código · Planificar antes de actuar

Al terminar esta lección tendrás un agente de código que no toca un archivo hasta haber entendido el problema y tener un plan aprobado. Verás cómo forzar las fases con herramientas, no solo con instrucciones, y qué debe contener un plan para que puedas revisarlo en dos minutos.

La idea

El fallo más común de un agente de código es empezar a editar demasiado pronto. Lee un archivo, cree entender el problema, cambia algo y luego encadena parches para arreglar lo que rompió. El resultado funciona a veces, pero el diff es grande y difícil de revisar.

El flujo explorar → planificar → implementar → verificar separa las decisiones en cuatro momentos con una salida clara cada uno:

FasePreguntaHerramientasSalida
Explorar¿Qué hay y dónde?Leer, buscar, listarNotas: archivos implicados, cómo funciona hoy
Planificar¿Qué voy a cambiar?Ninguna nuevaPlan con pasos y cómo verificar cada uno
ImplementarHacer el cambioLeer, buscar, editar, ejecutarDiff
Verificar¿Funciona y no rompe nada?Ejecutar tests, lint, tiposEvidencia: salidas de comandos
flowchart LR
  E[Explorar] --> P[Planificar]
  P --> H{Revisión humana}
  H -- aprobado --> I[Implementar]
  H -- cambios --> P
  I --> V[Verificar]
  V -- falla --> I
  V -- pasa --> F[Entregar]

Es la misma idea que Planificar antes de actuar aplicada al código, y la forma de trabajar descrita en Explorar, planificar, implementar, verificar. La diferencia aquí es que lo construyes dentro del agente.

Explorar: solo lectura

En la exploración el agente debe poder leer todo y modificar nada. Pedirle «no edites todavía» funciona casi siempre. Quitarle la herramienta de edición funciona siempre. Restringe por herramientas lo que no puedes permitirte que falle.

Una buena exploración termina con notas concretas: qué archivos están implicados, qué función hace qué, qué tests cubren la zona y qué dudas quedan. Si la tarea es ambigua, este es el momento de preguntar, antes de que haya código que deshacer.

Planificar: un artefacto que puedas revisar

El plan es lo que revisas en lugar de revisar código a ciegas. Un buen plan es corto y verificable:

Objetivo: slugify debe convertir tildes y ñ a ASCII ("Año Ñu" → "ano-nu").

1. Añadir test que falla en tests/test_texto.py con 4 casos (tildes, ñ, mayúsculas, espacios dobles).
   Verificar: `uv run pytest tests/test_texto.py` falla en los 4 casos.
2. En src/app/texto.py, normalizar con unicodedata.normalize("NFKD") y descartar marcas combinantes.
   Verificar: los 4 casos pasan.
3. Ejecutar la suite completa y el linter.
   Verificar: todo en verde; ningún otro archivo modificado.

Fuera de alcance: transliterar alfabetos no latinos.
Riesgo: otros módulos dependen de slugify (api/rutas.py); la suite los cubre.

Cada paso dice cómo se comprueba. «Fuera de alcance» evita que el agente se extienda. La línea de riesgo te dice dónde mirar al revisar. Si el plan no cabe en una pantalla, la tarea es demasiado grande: divídela (Cambios pequeños y revisables).

Implementar y verificar

Con el plan aprobado, el agente implementa paso a paso y verifica cada uno con el comando que el propio plan indica. La verificación final ejecuta la suite completa: el agente tiene que leer la salida, no suponerla. Un test escrito antes del arreglo que primero falla y luego pasa es la mejor evidencia de que el cambio hace lo que dice.

El agente por fases

Este código reutiliza las herramientas de Cómo trabaja un agente de código. Cada fase recibe solo las herramientas que necesita, y el plan pasa por tu aprobación antes de implementar.

import anthropic
from agente_codigo import HANDLERS, TOOLS, MODEL

client = anthropic.Anthropic()
LECTURA = {"leer_archivo", "buscar"}

def bucle(system: str, prompt: str, permitidas: set[str], max_pasos: int = 30) -> str:
    tools = [t for t in TOOLS if t["name"] in permitidas]
    messages = [{"role": "user", "content": prompt}]
    for _ in range(max_pasos):
        kwargs = {"tools": tools} if tools else {}
        resp = client.messages.create(model=MODEL, max_tokens=4096, system=system,
                                      messages=messages, **kwargs)
        messages.append({"role": "assistant", "content": resp.content})
        if resp.stop_reason == "end_turn":
            return "".join(b.text for b in resp.content if b.type == "text")
        if resp.stop_reason == "pause_turn":
            continue
        if resp.stop_reason != "tool_use":
            raise RuntimeError(f"stop_reason inesperado: {resp.stop_reason}")
        resultados = []
        for b in resp.content:
            if b.type != "tool_use":
                continue
            try:
                salida, error = HANDLERS[b.name](**b.input), False
            except Exception as e:
                salida, error = f"Error: {e}", True
            resultados.append({"type": "tool_result", "tool_use_id": b.id,
                               "content": salida, "is_error": error})
        messages.append({"role": "user", "content": resultados})
    raise RuntimeError("Límite de pasos alcanzado.")

def resolver(tarea: str) -> str:
    notas = bucle(
        "Fase EXPLORAR. Investiga el repositorio. No propongas soluciones todavía. "
        "Termina con notas: archivos implicados, comportamiento actual, tests relevantes, dudas.",
        tarea, LECTURA)
    plan = bucle(
        "Fase PLANIFICAR. Escribe un plan de 3 a 7 pasos. Cada paso indica el cambio y el comando "
        "que lo verifica. Incluye 'Fuera de alcance' y 'Riesgo'.",
        f"<tarea>{tarea}</tarea>\n<notas>{notas}</notas>", set())
    print(plan)
    if input("¿Apruebas el plan? [s/N] ").strip().lower() != "s":
        return "Plan rechazado."
    return bucle(
        "Fase IMPLEMENTAR Y VERIFICAR. Sigue el plan paso a paso. Tras cada paso ejecuta su "
        "verificación. No hagas cambios fuera del plan. Termina solo con todo en verde y "
        "resume qué cambiaste y la salida de los comandos de verificación.",
        f"<tarea>{tarea}</tarea>\n<plan>{plan}</plan>", set(HANDLERS))

if __name__ == "__main__":
    print(resolver("slugify no convierte tildes ni la ñ. Arréglalo."))

Dos decisiones merecen explicación. Cada fase arranca con un historial nuevo y recibe solo el resultado de la anterior (notas, plan). Eso mantiene el contexto limpio: los cuarenta resultados de búsqueda de la exploración no viajan a la implementación, solo su resumen. Y la planificación no tiene herramientas: no hace falta, y así no puede «planificar» editando.

Errores frecuentes

  • Exploración sin salida. Si la fase no termina con notas, la planificación trabaja a ciegas. Pide un formato concreto.
  • Planes sin verificación. «Arreglar el parser» no se puede comprobar. «El test X pasa» sí.
  • Verificar solo el test nuevo. El cambio puede romper otra cosa. La última verificación es siempre la suite completa.
  • Aprobar sin leer. El plan existe para que lo revises. Si lo apruebas por inercia, has añadido un paso sin beneficio.

Ejercicio

EjercicioPistas

Extrae las rutas del plan con una expresión regular sobre patrones como src/.../*.py. Pasa ese conjunto a un handler envuelto que compruebe b.input["ruta"] antes de llamar a editar_archivo.

Resumen

  • Separar fases evita editar antes de entender y produce diffs más pequeños.
  • Fuerza la exploración de solo lectura quitando herramientas, no solo pidiéndolo.
  • Un buen plan es corto, dice cómo verificar cada paso y marca qué queda fuera.
  • Pasa entre fases resúmenes, no historiales completos.
  • Verifica con la suite completa y exige la salida real de los comandos.

Para seguir