Lección 01 · Fundamentos

Sin bucle no hay agent

Todo el secreto de Claude Code cabe en una línea: while stop_reason == "tool_use"

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

¿Qué hace exactamente un agent?

Cuando ejecutas Claude Code en la terminal y le pides «organiza todos los comentarios TODO en una lista», lo que ves es: decide por sí solo hacer primero grep, luego cat varios archivos y finalmente generar Markdown. El modelo no ejecuta código por sí mismo — solo solicita que se ejecute. Lo que realmente lo hace parecer «autónomo» son las ~30 líneas de código de pegamento que lo envuelven.

Esta lección desmonta ese código de pegamento. Se llama agent loop, y su estructura es:

while response.stop_reason == "tool_use":
    response = LLM(messages, tools)      # 1. ask the model
    execute_tools(response.tool_calls)   # 2. run what it asked for
    messages.append(tool_results)        # 3. feed results back

Eso es todo. Claude Code de producción añade permisos, hooks, subagents, aislamiento de worktree y compresión de memoria encima de esto, pero el núcleo siguen siendo estas cuatro líneas.

Intuición clave: cada vez que el modelo responde, o bien dice «quiero llamar una herramienta» o bien «ya terminé». Mientras esté en el primer estado, el bucle continúa; en cuanto entra en el segundo, el bucle sale. El criterio es el campo stop_reason de la respuesta.

Ver cómo crece messages[] paso a paso

El siguiente simulador recorre la tarea: «¿Qué archivos hay en el directorio actual? Luego lee package.json». Pulsa Step; cada pulsación avanza una acción dentro del bucle. A la izquierda, los mensajes en formato conversacional; a la derecha, el array messages[] real que recibe el modelo — observa cómo crece.

Los resultados de herramientas deben volver al historial

El error más común en principiantes es tratar «ejecutar la herramienta» como un efecto secundario — se ejecutó, listo. Pero el modelo solo puede ver messages[] en la siguiente ronda de inferencia; lo que no esté ahí, para él no ocurrió. Si falta ese append, todo el bucle falla.

Los fallos no son todos iguales. Dos casos habituales:

  • Olvidar el append: el modelo no verá el resultado en la siguiente vuelta y volverá a pedir la misma herramienta — bucle infinito garantizado.
  • Append sin tool_use_id: la API de Anthropic devuelve directamente tool_result must have tool_use_id y el bucle explota en la llamada a la API.

La misma tarea: «cuenta cuántos archivos Python hay en el proyecto». Dos versiones en paralelo — compara los resultados.

Entender stop_reason

Cada respuesta del modelo incluye un campo stop_reason. La decisión de continuar o salir del bucle depende de este valor:

  • tool_use — el modelo quiere llamar una herramienta; continúa el bucle.
  • end_turn — el modelo considera que terminó; sale del bucle.
  • max_tokens — la generación fue truncada por el límite de tokens; sale del bucle (trátalo como error e informa al usuario de que la salida está incompleta).
  • stop_sequence — se encontró una secuencia de parada personalizada; sale del bucle.

Escribir erróneamente «continúa mientras no haya tool_use», o ignorar max_tokens tratándolo como un fin normal, son bugs frecuentes.

Pruébalo en local

Clona shareAI-lab/learn-claude-code:

git clone https://github.com/shareAI-lab/learn-claude-code
cd learn-claude-code
pip install -r requirements.txt
cp .env.example .env  # fill in ANTHROPIC_API_KEY
python agents/s01_agent_loop.py

Pídele algo concreto: Cuenta cuántos archivos .py hay en este repositorio. Verás cómo envía un ls -R, lee la salida, lanza un find . -name "*.py" | wc -l y finalmente te da la respuesta. Todo ese proceso es el bucle en acción.

Interactivo

Widget 1 · Loop Stepper · crecimiento de messages[]

Observa cómo los resultados de herramientas se reinsertan en messages con un formato especial (bloque tool_result con tool_use_id); es la única forma en que el modelo sabe en la siguiente ronda que «la herramienta se ejecutó y el resultado fue X».

Vista conversacionalNo iniciado
Pulsa Step para empezar →
messages[] JSONlength: 0
[]
stop_reason:
Interactivo

Widget 2 · Break the Loop · implementación correcta vs olvidar el append

Observa cómo con solo omitir una línea messages.append(tool_results), el agent deja de serlo — se convierte en una máquina que olvida el turno anterior en cada vuelta.

while True:
    resp = LLM(msgs, tools)
    msgs.append({"role":"assistant",
                 "content": resp.content})
    if resp.stop_reason != "tool_use":
        return
    results = execute(resp.tool_uses)
    msgs.append({"role":"user",
                 "content": results})
Simulación
while True:
    resp = LLM(msgs, tools)
    msgs.append({"role":"assistant",
                 "content": resp.content})
    if resp.stop_reason != "tool_use":
        return
    results = execute(resp.tool_uses)
    # BUG: forgot to append results
    # msgs.append(...)
Simulación
Interactivo

Widget 3 · Spot the stop_reason · identifica 4 respuestas reales

Cada una es un fragmento de respuesta real del modelo. Pulsa «Continuar» o «Salir» y comprueba tu respuesta al instante.

Correctas 0 / 4