有界 Agent 循环
工具循环(Tool Loop)让模型能够观察结果、选择下一步行动并持续迭代。这对调试和多步骤任务非常有用,但风险在于:一个错误的假设可能引发连锁反应,导致大量无效写入、反复重试以及上下文窗口的迅速膨胀。
核心原则很简单:模型只负责提议,Harness(执行框架)决定什么可以执行,以及结果是否算作成功。
常见的循环形态
模式本身并不重要,围绕它的停止条件才是关键。
Harness 必须掌控的部分
预算与停止机制
在运行前设定硬性限制:步数、墙钟时间、模型调用次数、工具使用次数,以及允许访问的文件或服务范围。当达到限制、用户取消、关键证据缺失,或重复失败表明当前策略无效时,必须立即停止。
重复调用断路器(Breaker)很有用,但阈值需匹配操作类型。重试读取操作与重试支付或部署的风险等级完全不同。
工具校验
在模型外部验证工具名称、参数 Schema、路径、权限及副作用。优先使用直接参数数组而非 Shell 字符串插值,同时严格检查危险标志和路径语义。始终将工具输出视为不可信输入,而非新的指令。
结果验证
模型的最后一句话不是完成信号。验证器可以是测试套件、Schema 检查、产物精确比对、浏览器断言或人工审查。验证逻辑应针对预期行为,且具备足够的独立性,防止 Agent 通过删除或弱化检查来“通过”验证。
持久化状态
将完整对话记录和原始工具输出保存在活动 Prompt 之外。对于长任务,维护一份精简的任务记录,包含:
- 目标与允许的操作范围;
- 已修改的文件或产物;
- 已运行的检查及其结果;
- 应避免的失败路径;
- 未决问题与下一步行动。
这份记录有助于在新上下文中恢复工作,但更新依据应是观察到的事实,而非模型的自信程度。
取消与清理
长时间运行的工具需要超时设置、进程跟踪和清理机制。取消 UI 操作时,必须同时终止底层的子进程或后台任务。否则,看似已停止的 Agent 可能仍在后台向磁盘写入数据。
最小控制循环
以下为伪代码;具体工具和模型接口因 Harness 实现而异。
for step in range(max_steps):
if cancelled() or budget.exhausted():
return STOPPED
context = select_working_context(state)
proposal = model.propose(context)
if proposal.is_final:
return verify_final(proposal, state)
checked = policy.validate(proposal.tool_call)
if not checked.allowed:
state.record_denial(checked.reason)
continue
result = tools.execute(checked.call, timeout=checked.timeout)
state.record(result)
if repeated_failure(result, state):
return NEEDS_REVIEW
实际实现还需处理异常、幂等性规则、并发控制及日志脱敏。这段伪代码的价值在于明确了关注点的位置:这些控制逻辑应包裹在模型调用之外,而不是写在 Prompt 里要求模型“小心一点”。
真实进展的标志
失败集合缩小、出现新的区分性测试、合理的 Diff 变小或歧义得到解决,这些才是进展。重复表述相同的诊断、在没有证据的情况下增加抽象层,或仅仅运行更长时间,都不是进展。
最好的循环通常是能可靠完成任务的最小自主循环。只有在测量到的失败确实需要时,才引入并行工作者、规划层或自我批评机制。
上面的通用控制过程,可以与下面记录了版本的 Pi 实现对照。配置和扩展如何分工见 Pi Harness 案例,跨进程恢复见长任务 Agent。
Pi Agent Loop 真正如何工作
这期 Pi 架构视频把 Agent Loop 画成“模型调用工具,再把结果交回模型”。这个方向没有错,但对照 Pi 当前的 agent-loop.ts 和 AgentSession,真实过程其实有三层:会话先准备请求,底层循环负责模型与工具,外层会话再处理持久化、压缩和重试。
一、模型调用之前已经发生了很多事
用户输入不会直接交给模型。AgentSession.prompt() 会先让 Extension 有机会处理或改写输入,再展开显式调用的 Skill 和 Prompt Template。如果 Agent 正在运行,新消息必须进入 Steering 或 Follow-up 队列,而不是直接插入当前工具调用中间。
随后会检查模型和鉴权,并判断旧上下文是否需要先压缩。before_agent_start 事件还可以追加自定义消息或修改本轮系统提示。直到这些步骤完成,用户消息才进入底层 Agent Loop。
二、底层其实有内外两个循环
内层循环负责一轮轮模型调用。每轮开始前,Pi 可以刷新系统提示、工具、模型和思考级别,也会在安全的回合边界插入 Steering 消息。上下文随后经过转换,变成当前模型供应商接受的消息格式,再开始流式生成 Assistant Message。
如果响应包含工具调用,Pi 会执行工具,把每个结果变成 toolResult 消息,再进入下一轮模型调用。如果没有工具调用,也没有等待中的 Steering,内层循环才会停下。
外层循环处理 Follow-up。它只在当前工具链和 Steering 都已经耗尽后取出新消息,然后重新进入内层循环。因此 Steering 是“下一次模型调用前纠偏”,Follow-up 是“当前任务本来要结束时再继续”。
根据 Pi 0.84.3 的键位说明,Enter 将消息放入 Steering,Alt+Enter 放入 Follow-up;Windows 和 WSL 默认用 Ctrl+Q 提交 Follow-up。Alt+Up 可以把排队消息取回编辑器(Windows 和 WSL 默认使用 Alt+Q),Escape 则中止运行并恢复尚未处理的消息。
三、工具调用不是模型说执行就直接执行
Pi 会先确认工具是否存在,再准备并校验参数。beforeToolCall 可以阻止调用;真正执行后,afterToolCall 还可以改写结果或把成功变成错误。工具可以并行执行,但标记为 Sequential 的工具,或全局顺序模式,会强制串行。
还有一个容易忽略的保护:如果模型因为输出长度限制而截断,Pi 不会执行其中看似合法的工具参数,而是把这些调用全部标记为失败,让模型重新提交。否则,半截 JSON 可能碰巧通过校验,却执行了错误操作。
四、Loop 停止不等于任务成功
底层 Loop 的停止条件很朴素:没有更多工具调用,没有 Steering,也没有 Follow-up,或者出现错误、取消及外部停止条件。此时 Pi 发出 agent_end。
这只表示 Agent 不再继续调用模型,不表示代码正确、文件符合预期或用户目标已经完成。测试、Schema、页面断言或人工验收仍然属于 Loop 外部。模型输出一句“完成了”,和真实成功是两件事。
五、压缩和重试属于会话层
Pi 的 压缩机制 并不是简单地“每轮看一次 Token”。它会在新 Prompt 前、工具结果写入后的下一轮模型调用前,以及底层 Agent Run 结束后检查上下文。超过阈值时,旧消息被总结,近期消息继续保留;如果发生可恢复的上下文溢出,Pi 会压缩后重试一次。
消息同时被写入 树状 JSONL Session。这棵树保存的是对话和工具结果,不是文件系统快照。切换会话分支不会撤销已经写入磁盘的修改。
真正应该记住的 Loop
Agent Loop 不是“让模型一直思考直到完成”,而是一套明确的交接:会话准备上下文,模型提出动作,Harness 校验并执行工具,结果重新进入上下文,会话层负责保存、压缩、重试和排队。最后,外部验收决定这次运行是否真的完成。