没有循环,就没有 agent
Claude Code 的整个秘密可以写成一行:while stop_reason == "tool_use"
一个 agent 到底在做什么?
当你在终端里跑 Claude Code、让它「把所有 TODO 注释整理成一份清单」,你看到的是:它自己决定先 grep、再 cat 几个文件、然后输出 Markdown。模型自己不会执行代码——它只会请求执行。真正让它看起来「有手有脚」的,是它外面那一圈 30 行左右的胶水代码。
这一课就是要把那圈胶水拆开看清楚。它叫 agent loop(智能体循环),结构就是:
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
就这样。没了。production 的 Claude Code 在这上面叠了权限、hook、子 agent、worktree 隔离、记忆压缩——但内核还是这四行。
关键直觉:模型每次回复要么「我想调工具」,要么「我说完了」。只要还在前一种状态,循环就继续;一旦进入后一种状态,循环就退出。判据是响应里的 stop_reason 字段。
看 messages[] 一步步长出来
下面这个循环模拟的任务是:「当前目录有哪些文件?然后读一下 package.json」。点 Step,每按一次前进一个循环内的动作。左边是给人看的对话气泡,右边是真正塞给模型的 messages[] 数组——注意看它如何增长。
工具结果必须重新进入消息历史
新手最容易踩的坑,是把「执行工具」当成一个副作用——执行了就完事了。但模型下一轮推理只能看到 messages[],不在里面的东西它就不知道发生过。少 append 一步,整个循环就错了。
错法不是都一样的。常见两种:
- 忘记 append:模型下一轮看不到结果,它会再问一次同一个工具——于是你得到一个无限循环。
- append 了但丢掉
tool_use_id:Anthropic API 会直接报错tool_result must have tool_use_id,循环崩在 API 调用那一步。
同一个任务:「统计项目里有多少个 Python 文件」。两个版本并排跑,看结局差别。
读懂 stop_reason
模型每次返回都会带一个 stop_reason。决定要不要继续循环,就看这个字段:
tool_use— 模型想调工具,继续循环。end_turn— 模型觉得说完了,退出循环。max_tokens— 生成到了 token 上限被截断,退出循环(一般当异常处理,提示用户输出不完整)。stop_sequence— 碰到自定义停止串,退出循环。
写错成「只要没 tool_use 就继续」,或者忽略 max_tokens 直接当作正常 end,都是常见 bug。
动手跑一下
本地克隆 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
然后让它做点真事:帮我数一下这个仓库里有多少 .py 文件。你会看到它发一条 ls -R、看到输出、再发一条 find . -name "*.py" | wc -l、然后告诉你答案。整个过程就是这个 loop 在循环。