Lección 03 · Planificación

Deja que el agent gestione su propio progreso

«The agent can track its own progress — and I can see it.» Haz que el modelo lleve su propia lista, y luego usa un mecanismo simple para que no olvide actualizarla.

⏱ ~10 min · 📝 3 widgets interactivos · 🧑‍💻 Basado en shareAI-lab · s03_todo_write.py

Self-planning estructurado

Cuando Claude Code trabaja suele necesitar varios pasos: grep para encontrar referencias → leer archivos → modificar código → ejecutar tests. Si dejas que el modelo «avance a su aire», verás que los primeros pasos van bien, pero a mitad camino empieza a olvidar y acaba dejando el trabajo a medias.

La solución de s03 es darle una herramienta de lista de tareas: el modelo llama a la herramienta todo para agregar ítems, y TodoManager valida la estructura, la persiste y devuelve la vista actual. Esto tiene dos ventajas:

  • El modelo se ve obligado a explicitar qué va a hacer — solo escribirlo ya le ayuda a ordenar sus ideas.
  • El humano puede ver qué está pensando. La experiencia de debugging mejora diez veces.
# Vista del TODO: cada ítem es un objeto estructurado
[ ] #1: grep "TODO" across src/
[>] #2: read src/app.py and list comments     # en progreso
[ ] #3: generate summary markdown
[ ] #4: write to TODO_LIST.md

(0/4 completed)

Una regla dura: solo un in_progress a la vez

TodoManager.update() incluye esta validación:

if in_progress_count > 1:
    raise ValueError("Only one task can be in_progress at a time")

Parece estricto, pero está ayudando al modelo. Si permitiéramos tres «en progreso» a la vez, el modelo abriría frentes en todas direcciones sin terminar ninguno. Al forzar avance de una tarea a la vez, tiene que completar cada una antes de pasar a la siguiente.

El siguiente widget te permite actuar como el modelo y enviar distintos payloads de todo para ver cuáles pasan la validación y cuáles son rechazados.

Nag reminder: ¿tres turnos sin actualizar? Un toque

Incluso con la herramienta todo disponible, el modelo a veces «olvida» actualizar la lista — hace un montón de cosas, pero el in_progress sigue clavado en el ítem 2. El truco de s03 es un contador muy simple:

rounds_since_todo = 0
while True:
    response = LLM(messages, tools)
    ...
    used_todo = any(b.name == "todo" for b in tool_uses)
    rounds_since_todo = 0 if used_todo else rounds_since_todo + 1
    if rounds_since_todo >= 3:
        results.append({"type":"text", "text":"<reminder>Update your todos.</reminder>"})

Al llegar a 3, se inyecta un reminder en el mensaje de usuario del siguiente turno. El modelo reacciona instintivamente llamando a todo. Esta es la forma de convertir una restricción blanda («por favor mantén el todo actualizado») en un estímulo forzado mediante ingeniería.

¿Cómo se llama este patrón?

En el ámbito del diseño de agents, se conoce como structured self-planning with soft nudges — dale al modelo un estado estructurado que tiene que escribir, complementado con reminders oportunos. El código real de Claude Code usa un patrón similar pero más conservador (baja frecuencia, tono neutro).

¿Por qué no poner «actualiza el todo en cada paso» en el system prompt? Se puede, pero la obediencia del modelo a instrucciones generales en el system prompt decae conforme crece la conversación. Convertir la instrucción en «reminders reinyectados continuamente» tiene un efecto mucho más estable.
Interactivo

Widget 1 · Kanban · evolución del todo por turno

Pulsa Step y observa cómo el modelo mueve tareas de pending a in_progress y luego a completed. Fíjate en que in_progress nunca tiene más de un ítem a la vez.

[ ] pending
[>] in_progress
[x] completed
Listo para empezar…
Interactivo

Widget 2 · Validation · ¿qué payloads de todo pasan la validación?

Cuando el modelo llama a la herramienta todo, pasa un array items. TodoManager ejecuta una serie de validaciones: text no vacío, status válido, máximo un in_progress, total ≤ 20. Decide si cada payload pasa o es rechazado.

Correctas 0 / 5
Interactivo

Widget 3 · Nag Counter · ¿qué pasa tras 3 turnos sin actualizar el todo?

Pulsa Next Turn y observa si el contador dispara la inyección de reminder. Cada turno elige aleatoriamente si el modelo llama o no a todo — igual que en la realidad.

rounds_since_todo: 0