Lesson 01 · Base

Without loops, there is no agent

The entire secret of Claude Code can be written in one line: while stop_reason == "tool_use"

⏱ ~10 min · 📝 3 interactive widgets · 🧑‍💻 Based on shareAI-lab · s01_agent_loop.py

What exactly is an agent doing?

When you run Claude Code in the terminal and let it "organize all TODO comments into a list", what you see is: it decides on its own to first grep, then cat a few files, and then output Markdown. The model does not execute code itself - it only requests to be executed. What really makes it look "with hands and feet" is the 30 or so lines of glue code around it.

This lesson is to take apart the circle of glue and see clearly. It's called agent loop (agent loop), and its structure is:

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

That's it. No more. The Claude Code of production has permissions, hooks, sub-agents, worktree isolation, and memory compression stacked on top of this - but the core is still these four lines.

Key intuition: Each time the model responds, it is either "I want to adjust the tool" or "I'm done". The loop continues as long as it is in the previous state; once it enters the latter state, the loop exits. The criterion is the stop_reason field in the response.

See messages[] grow step by step

The task of the following loop simulation is: "What files are in the current directory? Then read package.json". Click Step. Each time you press it, you advance one action in the cycle. On the left is the human-viewed speech bubble, on the right is the actual messages[] array stuffed into the model - notice how it grows.

Tool results must be re-entered into message history

The easiest pitfall for novices is to regard the "execution tool" as a side effect - once it is executed, it is done. But the model can only see messages[] in the next round of inference, and it doesn’t know that things that are not in it have happened. Without the append step, the entire loop would be wrong.

Not all mistakes are the same. Two common types:

  • Forget about append: the model won't see the results next time, it will ask the same tool again - and you get an infinite loop.
  • Append but lose tool_use_id: Anthropic API will directly report an error tool_result must have tool_use_id, and the loop will collapse at the API call step.

The same task: "Count how many Python files there are in the project". Run the two versions side by side to see the difference in endings.

Understand stop_reason

The model will bring a stop_reason every time it is returned. To decide whether to continue the loop, just look at this field:

  • tool_use — The model wants to adjust the tool, continue the cycle.
  • end_turn — The model feels it is finished and exits the loop.
  • max_tokens — When the upper limit of the token is generated, it is truncated and exits the loop (usually handled as an exception to prompt the user that the output is incomplete).
  • stop_sequence — When a custom stop string is encountered, exit the loop.

Writing incorrectly as "continue as long as there is no tool_use", or ignoring max_tokens and directly treating it as a normal end, are common bugs.

Take action and run

Local clone 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

Then make it do something real: Help me count how many .py files there are in this repository. You will see it send ls -R, see the output, send another find . -name "*.py" | wc -l, and then tell you the answer. The whole process is this loop.

Interactive

Widget 1 · Loop Stepper · messages[] growth

Understand: the tool results are stuffed back into messages in a special format (tool_result blocks, with tool_use_id); this is the only way for the model to know that "the tool has been executed and the result is X" in the next round of inference.

对话视图未开始
点 Step 开始 →
messages[] JSONlength: 0
[]
stop_reason:
Interactive

Widget 2 · Break the Loop · Correct vs Forgot to append

Understand: Just miss one line messages.append(tool_results), and the entire agent ceases to be an agent—it becomes a reading machine that forgets the previous one each round.

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})
模拟运行
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(...)
模拟运行
Interactive

Widget 3 · Spot the stop_reason · Judge 4 true responses

Each is a fragment of a real possible model response. Click "Continue" or "Exit" to see whether you are right or wrong.

答对 0 / 4