跳到主要内容

有界 Agent 循环

工具循环(Tool Loop)让模型能够观察结果、选择下一步行动并持续迭代。这对调试和多步骤任务非常有用,但风险在于:一个错误的假设可能引发连锁反应,导致大量无效写入、反复重试以及上下文窗口的迅速膨胀。

核心原则很简单:模型只负责提议,Harness(执行框架)决定什么可以执行,以及结果是否算作成功。

常见的循环形态​

模式适用场景典型失败模式
会话内工具循环交互式排查上下文被旧日志和重复猜测填满
新上下文任务循环跨越多个模型窗口的长任务任务台账与代码仓库状态脱节
实现-验证循环带有有效测试的修复工作Agent 只满足弱测试,而非真实业务契约
评估者-优化器循环重复起草或可量化调优执行者与评估者存在相同的盲点
基准测试循环优化单一标量指标将噪声误判为性能提升

模式本身并不重要,围绕它的停止条件才是关键。

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 校验并执行工具,结果重新进入上下文,会话层负责保存、压缩、重试和排队。最后,外部验收决定这次运行是否真的完成。

探索关联打开关联网络