一个 while True 循环,为什么可以成为 Agent 的起点?
2026/7/24 23:12:45 网站建设 项目流程

本文是「从零理解 Claude Code:20 个 Agent Harness 机制」系列的第 2 篇。
源码仓库:shareAI-lab/learn-claude-code

Claude Code 能读文件、跑命令、改代码,看起来像是一个住在终端里的开发者。

但把它最核心的逻辑抽出来,代码其实不到 30 行。

没有任务编排,没有工作流画布,没有多 Agent,也没有什么神秘提示词。它做的事情很直接:

模型要调用工具 → 程序执行工具 → 把结果交回模型 → 模型决定是否继续

把这段过程写成代码,就是一个while True

这一篇不讨论复杂架构,只把这件事讲明白:模型为什么会调用工具、工具结果为什么还要发回模型、循环又是怎么停下来的。


一、模型会写命令,为什么还不算 Agent

先看一个很普通的需求:

列出当前目录下的 Python 文件,然后运行 demo.py。

如果把这句话发给普通聊天模型,它通常知道该怎么做:

find.-name"*.py"

但它把命令写出来后,任务就中断了。

因为模型没有终端,也没有文件系统。它不能亲自执行这条命令,更看不到命令输出。

接下来只能由人接手:

  1. 复制模型给出的命令;
  2. 打开终端执行;
  3. 复制输出结果;
  4. 粘贴回聊天框;
  5. 等模型给出下一步。

如果模型接着要求运行:

python demo.py

人就再做一遍。

这时模型负责思考,终端负责执行,人负责在两者之间传递信息。

最小 Agent Loop 的作用,就是把这段人工操作交给程序。


二、先看最终代码

这一章的核心代码如下:

defagent_loop(messages):whileTrue:response=client.messages.create(model=MODEL,system=SYSTEM,messages=messages,tools=TOOLS,max_tokens=8000,)messages.append({"role":"assistant","content":response.content,})ifresponse.stop_reason!="tool_use":returnresults=[]forblockinresponse.content:ifblock.type=="tool_use":output=run_bash(block.input["command"])results.append({"type":"tool_result","tool_use_id":block.id,"content":output,})messages.append({"role":"user","content":results,})

先不用急着逐行看。

这段代码只有一个判断:

ifresponse.stop_reason!="tool_use":return

模型没有调用工具,就结束。

模型调用了工具,就执行工具,把结果追加回消息列表,再重新请求模型。

整个循环的控制权并不在程序手里,而在模型手里。


三、第一步:模型不是直接执行命令,而是请求工具

模型回复时,可能返回普通文本,也可能返回工具调用。

例如,模型希望搜索 Python 文件,它返回的不是终端输出,而是一份工具调用请求:

工具名称:bash 参数: { "command": "find . -name \"*.py\"" }

程序从模型响应中取出工具参数:

forblockinresponse.content:ifblock.type=="tool_use":output=run_bash(block.input["command"])

这里真正执行命令的是run_bash

output=run_bash(block.input["command"])

模型负责说自己想做什么;程序负责真正调用 Shell。

这个边界很重要。

角色工作
模型决定调用什么工具、传什么参数
程序执行工具调用
Shell在操作系统中执行命令
工具结果把真实环境的反馈带回模型

模型不是直接拥有电脑权限,而是通过工具请求间接影响环境。


四、第二步:工具执行完,为什么还要再发回模型

命令执行以后,程序可以拿到输出。

例如:

find.-name"*.py"

返回:

./demo.py ./hello.py

这份结果不能只打印在终端里。

模型必须看到它,才能知道下一步应该怎么做。

所以代码会把工具结果包装成tool_result

results.append({"type":"tool_result","tool_use_id":block.id,"content":output,})

然后追加到messages

messages.append({"role":"user","content":results,})

这里的tool_use_id用来把结果和之前那次工具调用对应起来。

模型之前说的是:

我要调用 bash,执行 find . -name "*.py"

程序返回时,相当于在说:

你刚才请求执行的那条命令,结果在这里。

如果没有这一步,模型就不知道命令到底有没有成功,也不知道输出内容是什么。


五、messages里保存的是整个执行现场

第一次调用模型前,消息列表里只有用户任务:

messages=[{"role":"user","content":"列出当前目录下的 Python 文件,然后运行 demo.py。"}]

随着循环继续,messages会不断追加内容。

最终,它大致会记录成这样:

用户:列出当前目录下的 Python 文件,然后运行 demo.py。 助手:调用 bash 命令:find . -name "*.py" 工具结果: ./demo.py ./hello.py 助手:调用 bash 命令:python demo.py 工具结果: Hello, World!

下一轮请求模型时,这些内容会一起发送。

因此模型不是凭空知道demo.py存在,也不是凭空知道程序已经运行成功。

它是通过前面工具返回的结果,一步一步获得环境信息。

这也是为什么 Agent 不只是一次 API 调用。

一次 API 调用只会产生一段回答;Agent Loop 会让模型根据新信息持续更新下一步动作。


六、一次完整任务是怎么跑完的

还是看运行demo.py这个任务。

第 1 轮:搜索文件

模型调用:

find.-name"*.py"

程序执行命令后,返回:

./demo.py ./hello.py

模型看到结果,知道目标文件存在。

第 2 轮:运行程序

模型继续调用:

python demo.py

程序执行后,返回:

Hello, World!

模型看到输出,知道程序已经正常运行。

第 3 轮:结束任务

这时模型不再需要工具,直接回复:

当前目录中找到了 demo.py,程序已成功运行,输出为 Hello, World!

由于这一轮没有工具调用:

response.stop_reason!="tool_use"

循环执行return,任务结束。

可以把整个过程看成下面这样:

轮次模型做什么程序做什么
第 1 轮请求搜索文件执行find,返回结果
第 2 轮请求运行文件执行python demo.py,返回结果
第 3 轮输出最终回答不再执行工具,结束循环

程序没有预先写死任务一共三步。

模型根据每一轮返回的结果,自己决定是否继续行动。


七、循环为什么不会无限执行

很多人看到while True的第一反应是:它不会一直跑下去吗?

答案是,不会。

循环的出口就是这段代码:

ifresponse.stop_reason!="tool_use":return

只有模型明确请求工具,循环才继续。

模型返回状态程序行为
tool_use执行工具,把结果交回模型
普通文本回复结束循环

因此,程序不需要写死执行顺序。

它不需要规定:

先读文件 再找文件 再运行测试 最后总结

不同任务的路径不一样。

有时模型先看日志,有时先读配置,有时根本不需要工具。循环只负责确保:模型需要行动时,有工具可用;工具运行后,结果能回到模型手里。


八、这个最小版本还缺什么

这段循环足够让模型开始行动,但它还只是教学版。

目前它只有一个bash工具,能做的事情比较粗。

而且代码虽然拦截了少量危险命令:

dangerous=["rm -rf /","sudo","shutdown","reboot"]

但这显然不是完整的权限系统。

真实的 Agent 还要处理很多问题:

  • 文件读写应该用专门工具,还是全部交给 Shell;
  • 哪些操作可以直接执行,哪些操作必须让用户确认;
  • 命令超时和执行失败怎么处理;
  • 上下文太长时,历史消息怎么压缩;
  • 多个 Agent 同时工作时,如何避免互相干扰。

这些会在后面的章节中逐步补上。

但它们都建立在同一个基础上:

模型发起工具调用 → 程序执行 → 结果回传 → 模型继续判断

九、动手验证

如果想自己跑一遍,可以在临时目录中配置好环境后执行:

python s01_agent_loop/code.py

建议先试这几个简单任务:

List all Python files in this directory
Create a file called hello.py that prints Hello, World!
What is the current git branch?

运行时不用急着看模型最后说了什么,先观察终端里打印出的命令。

重点看两个问题:

  1. 模型什么时候开始调用bash
  2. 命令返回结果后,模型为什么会继续调用工具,或者为什么会结束。

把这两个问题看明白,Agent Loop 的核心就已经掌握了。


小结

Claude Code 看起来复杂,但最底层的行动方式并不复杂。

模型不会直接读文件,也不会直接执行命令。它只能提出工具调用请求。

程序执行请求,把真实结果交回模型;模型看到结果后,再判断是否继续。

while True不是为了让程序无限运行,而是为了让模型在任务没有结束前,能够持续获得行动和观察的机会。

下一篇会继续在这个循环上增加能力:当 Agent 不再只有一个bash,而是拥有读文件、写文件、编辑文件和搜索文件等工具时,工具应该怎样组织,才不会把主循环写乱?


参考资料:

  • 最小 Agent Loop:源码与文档
  • learn-claude-code 项目仓库

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询