普通聊天程序调用一次模型就返回答案。Agent 不同:模型可以请求工具,宿主执行工具,再把结果交还模型,让模型根据外部世界的新状态继续决定下一步。
最小结构并不复杂:一个消息列表、一个 Bash 工具和一个循环。
用户请求进入 messages
↓
模型返回文本或 tool_use
├── 没有 tool_use → 返回最终文本
└── 有 tool_use → 宿主执行 Bash
↓
追加 tool_result
↓
下一轮messages 是循环的工作状态
每轮模型调用都接收当前 messages。用户请求、模型回复和工具结果依次进入这个列表,因此模型能看到自己刚才请求了什么,以及工具实际返回了什么。
user: 请查看当前目录
assistant: tool_use(name="bash", id="call_1", command="ls")
user: tool_result(tool_use_id="call_1", content="...")
assistant: 当前目录包含……最后一条 user 工具结果不是人重新输入,而是 API 协议中承载工具结果的消息角色。
模型只提出调用,宿主才执行工具
工具定义告诉模型工具名称、用途和输入结构。模型生成 tool_use 只是结构化请求,不会自动运行 Shell。宿主必须找到实现、校验输入、执行操作、收集输出与退出状态,再生成关联的 tool_result。
模型负责选择动作,Harness 负责决定动作能否以及如何发生。
调用 ID 闭合工具协议
每个工具调用都有稳定的 tool_use_id。返回结果时必须携带同一个 ID。当一条响应包含多个 tool_use block 时,也要逐个执行并分别返回对应结果。
即使命令没有输出,也应返回合法内容,不能让调用悬空。工具失败同样应转换成模型可处理的结果,而不是直接破坏整个消息循环。
停止条件检查实际内容块
最直接的退出条件是当前响应中不存在 tool_use。此时模型只返回文本,循环把它交给用户。
不要把“模型暂时没有调用工具”误解为“目标一定完成”。s17 会加入目标完成判断;s01 只建立协议闭环。
Bash 让示例很短,也让边界很危险
Bash 几乎可以完成所有本地操作,因此适合展示 Agent Loop;它也意味着模型可能读取文件、修改目录、联网或执行破坏性命令。上游课程明确把 s01 定位为教学实现,而不是安全沙箱。
最低限度应当:
- 在临时目录运行实验;
- 不放入凭据和重要文件;
- 设置超时并保留退出码;
- 不把命令字符串视为可信输入;
- 在真实产品中加入权限、隔离和审计。
s03 会加入执行前权限判断,s04 再把权限提取成 Hook。cwd 只能限定默认工作目录,不能阻止绝对路径、Shell 跳转或子进程访问其他位置。
最小实现没有解决什么
| 问题 | 后续机制 |
|---|---|
| 多种工具如何注册 | s02 Tool Use |
| 哪些操作允许执行 | s03 Permission |
| 如何插入审计和拦截逻辑 | s04 Hooks |
| 长任务如何保持计划 | s05 TodoWrite |
| 上下文如何隔离和压缩 | s06、s08 |
| 如何判断长期目标真正完成 | s17 Goal Loop |
最小循环的价值不是功能完整,而是提供稳定骨架:后续能力都尽量接入这个循环,而不是创造不兼容的执行协议。
建议覆盖的测试
- 纯文本响应会立即结束循环;
- 一个和多个工具调用都获得对应结果;
- 空输出和非零退出码不会破坏消息结构;
- 未知工具与参数错误返回错误结果;
- 每个
tool_result都引用真实的tool_use_id; - 命令超时后子进程能被清理。
小结
Agent Loop 是模型决策与宿主执行之间的反馈循环。messages 保存工作状态,tool_use 表达动作意图,tool_result 带回观察结果,没有工具调用时结束当前轮次。Bash 只是最小示例,不是安全设计。