从“一次回答”到“多步执行”
语言模型本身只完成一次输入到输出的映射。Agent 能连续工作,是因为宿主程序把输出分类:
- 如果输出是普通消息,视为本轮的候选终点;
- 如果输出是工具调用,执行工具,把结果追加到历史,再次请求模型;
- 如果执行触发审批,暂停并等待人的决定;
- 如果被取消、达到限制或遇到不可恢复错误,终止本轮。
这个宿主程序就是 Agent Loop 的真正执行者。
stateDiagram-v2 [*] --> Assemble: 收集输入与上下文 Assemble --> Sample: 请求模型 Sample --> Final: assistant 消息 Sample --> Authorize: tool call Authorize --> Execute: 允许或无需批准 Authorize --> Waiting: 需要人工批准 Waiting --> Execute: approve Waiting --> Observe: deny Execute --> Observe: stdout / diff / error Observe --> Sample: 仍需后续推理 Final --> Verify: 结果陈述 Verify --> [*]
软件任务需要根据每一步真实结果动态调整,预先生成完整脚本往往不可靠。
Runtime 反复执行采样;工具结果作为新观察追加到对话历史。
模型擅长基于当前证据选择下一步,操作系统擅长确定性执行,两者各司其职。
run_turn 管循环;handle_output_item_done 识别工具调用;工具 future 完成后形成下一轮输入。
循环可能兜圈、消耗上下文或错误提前结束,因此需要预算、取消、压缩和验证。
一次 Turn 内到底发生什么
在 Codex 的术语里,用户提交一次任务后,会产生一个 Turn。一个 Turn 可以包含多次模型采样和多次工具执行。
run_turn捕获本轮配置与运行环境;- 构建模型请求:输入历史、指令、工具 schema、模型参数;
- 流式读取模型响应;
- 对每个完成的输出 item 分类;
- 工具调用被派发并异步执行;
- 工具结果记录回 Context;
- 若
needs_follow_up为真,继续采样; - 无工具调用且拿到最终消息后,完成 Turn。
动手观察一个缩小版循环
下面不是连接真实仓库的 Agent,而是把一次“修复重试 bug”的典型事件按顺序回放。注意每一步都消费前一步的证据。
一次修复任务,运行时里发生了什么?
- 01USER提交任务
- 02RUNTIME捕获执行现场
- 03MODEL提出探索动作
- 04POLICY审批与 Sandbox
- 05TOOL执行并回填结果
- 06MODEL生成最小补丁
- 07TOOL验证行为
- 08RUNTIME完成 Turn
Op::UserInput提交任务
“修复登录失败,并运行相关测试。”
目标进入 Submission 队列;尚未执行任何代码。
$ codex > 修复登录失败,并运行相关测试
工具错误不是程序错误
对传统程序来说,命令返回非零通常意味着失败退出;对 Agent 来说,它还可能是有价值的观察。
例如 rg 没找到目标、测试暴露另一个断言、Patch 因上下文不匹配而失败。Runtime 应把这些信息以结构化、长度受控的方式交还模型,让它决定换关键词、重新读文件或调整 patch。
何时停止
停止条件不是单一的:
| 条件 | 谁决定 | 结果 |
|---|---|---|
| 模型输出最终消息 | 模型 + Runtime | 正常结束 Turn |
| 用户取消 | 人 | 中断当前任务 |
| 审批拒绝 | 人 | 结果回到模型,可能改走安全路径 |
| 上下文接近限制 | Runtime | 压缩历史后继续,或停止 |
| 连接/模型临时错误 | Runtime | 有限重试 |
| 不可恢复错误 | Runtime | 发出错误事件并结束 |
源码中的网络采样路径包含重试处理;工具路径则由 router、registry 和 orchestrator 统一包装错误与事件。
并行工具不等于并行思考
Codex 可以把一组满足条件的工具调用并行调度,但下一次模型采样仍基于它们已返回的结果。并行主要减少独立读取或查询的等待时间,并不会消除共享状态冲突。
一个判断口诀
看到任何 Agent 行为时,依次问:
- 它现在看到了什么?
- 模型选择了什么动作?
- 谁真正执行这个动作?
- 执行结果如何回到上下文?
- 什么证据允许它结束?
下一章把这个循环放进一个具体修改,追踪 从 Prompt 到 Patch 的全过程。