Concitech AI · 深度长文

2026/09/08 21:16

Pi 正在重写 Agent 底层:一个 while loop,已经撑不起长任务

Pi 的实验分支正在把 Agent Harness 改造成一套可恢复的执行系统。它关心的不是再加几个工具,而是进程崩溃、工具副作用、并行任务、上下文成本和模型与 Harness 的适配问题。

今天的 Coding Agent,表面上已经可以连续工作几十分钟,甚至几个小时。

但只要把运行时间继续拉长,问题就会完全变样。

进程可能崩溃,网络可能中断,模型请求可能已经扣费却没有写入会话,工具可能已经改了文件却来不及返回结果,用户还可能在 Agent 工作时插入新指令。与此同时,上下文越来越长,缓存命中率越来越低,多条任务也开始争抢同一份会话状态。

这时,Agent 最难的部分已经不再是那段调用模型、执行工具、再调用模型的循环。

更棘手的问题是:一次已经被系统接受的任务,能不能可靠地执行到结束?如果中途崩溃,系统究竟应该重试、继续,还是承认自己不知道工具是否已经产生了副作用?

Pi 最近在 harness-v2/j4 实验分支公开了一份超过 3000 行的设计文档,名字很直接:Durable AgentHarness design。

它给出的答案,是把 Agent Harness 从一个普通的应用循环,改造成一套接近数据库和任务调度器的持久化执行系统。

需要先说明,这不是 Pi 已经正式发布的 Harness v2。文档所在的不是主分支,设计末尾仍有大量实现任务未完成。它更像一份正在施工的底层蓝图。

但这份蓝图值得看,因为它把下一阶段 Agent 工程会遇到的问题列得足够具体。

Agent Loop 为什么不够了

一个最简单的 Agent Loop,大致是这样:

接收用户消息
调用模型
如果模型要求使用工具,就执行工具
把工具结果交回模型
重复,直到模型给出最终答案

短任务里,这套结构完全够用。

可一旦任务持续数小时,任何一步都可能成为崩溃边界。

例如,Agent 要执行一条会修改生产配置的命令。系统先调用工具,工具已经成功修改配置,但进程恰好在保存工具结果之前崩溃。重启后,会话只看到模型发起了工具调用,却看不到工具结果。

这时如果直接重放,配置可能被修改第二次;如果不重放,任务又永远停在半完成状态。

这不是提示词能解决的问题,而是分布式系统里很熟悉的“副作用已经发生,但结果没有成功提交”问题。

Pi 的设计文档因此提出了一条核心规则:

在执行外部效果之前,先持久化意图;效果完成之后,再写入结果。

也就是说,Harness 必须先记录“接下来要执行哪个工具、使用什么参数、结果应该写到哪个预分配 ID”,然后才能真的启动工具。

如果进程在两者之间崩溃,恢复程序至少知道这里存在一个未完成意图,可以依据工具的重放策略决定下一步:安全重试、补一个合成结果,或者停止并交给人处理。

这很像数据库的预写日志,但不能把它简单等同于完整的 ACID 事务。Pi 明确承认,它不提供多条记录的原子提交,也不能保证外部副作用只发生一次。工具和 Hook 如果会调用 HTTP 接口、修改文件或发消息,仍然需要自己提供幂等能力。

这个边界没有假装 Agent 可以消除现实世界的不确定性。它做的是记录不确定性,并为恢复和审计留下依据。

一次用户请求,先变成一个“持久操作”

在这个设计里,用户输入不再只是一条内存里的消息,而是一个被系统正式接受的 operation。

prompt() 返回成功之前,完整输入已经写进对应 lane 的操作日志。即使进程下一秒崩溃,重启后也能找到这次请求,并继续完成后续写入。

每次 operation 最终必须进入明确状态:

completed
failed
aborted

压缩和会话导航还可以被 Hook 拒绝,因此多一个 declined。

这相当于把系统的设计单位从“聊天轮次”换成了“可恢复操作”。

过去我们问的是:模型这一轮回复了什么?

现在要问的是:系统接受了什么工作,这项工作进行到了哪个持久化边界,还剩哪些外部效果没有得到确认?

四层状态,不能再混在一份聊天记录里

Pi 把会话状态拆成四部分:

  1. Conversation Tree:追加写入的对话树,保存消息和工具结果。
  2. Lanes:指向对话树不同位置的命名指针,每条 lane 独立推进。
  3. Operation Logs:每条 lane 自己的执行意图、尝试次数、工具状态和结束状态。
  4. Global Facts:会话级元数据,使用最新写入值生效,同时保留历史。

这里最值得注意的是 lane。

它有点像 Git 分支加 worktree:多条 lane 可以从同一个历史节点开始,然后各自向前发展。文档举的例子是,一个 Slack 频道可以是一份 session,每个讨论串是一条 lane;一个 Subagent 也可以在父任务的同一份 session 中开出另一条 lane。

不同 lane 可以使用不同模型、思考强度和启用工具,也可以并行运行。但每条 lane 同一时间只允许一个 operation,整份 session 仍坚持单写入者。

这个限制看起来保守,却解决了大量状态竞争问题。Pi 没有在第一版里挑战多写入者、跨进程复制和分布式一致性,而是先保证一份会话日志只能产生合法的顺序。

它也在重新处理 Token 和缓存

长任务的另一个现实问题是成本。

Harness v2 文档规定,同一条 lane 发给模型的上下文,在正常情况下只能从尾部增长。因为只要在旧上下文中间插入内容,后面的 KV Cache 就可能全部失效,重复计算的 Token 成本会迅速增加。

所以,用户在 Agent 工作过程中追加的指令、配置变化和其他写入,并不一定立刻插进历史中间,而是等到预设的 checkpoint 再进入模型可见上下文。

压缩则被视为一次主动、可控的缓存失效:牺牲旧前缀的缓存,换取之后更短的上下文。

这和“把消息塞进数组,快超限时做一次摘要”的处理方式不同。上下文结构、持久化状态和缓存成本,在这里属于同一个执行协议。

“剪掉再落盘”是另一项实验,不是 Harness v2 已有功能

围绕这份设计,还有一项很容易被混在一起传播的实验:工具结果剪枝后,把完整内容写入磁盘,只在上下文中保留头尾、文件路径和精确偏移量。

它针对的是大段编译日志、搜索结果和 API 返回值。模型先看到一份短预览,需要中间细节时,再通过 grep、sed 等命令读取落盘文件。

该方案的社区 PR 报告称,在 19 次使用 GLM-5.3 和 DeepSeek V4 Flash 的会话中:

指标 PR 报告的变化
未缓存 Prefill 下降 72% 至 88%
每次请求的上下文占用 下降 26% 至 35%
会话中持久化的字符量 下降 81% 至 93%

这些数字很亮眼,但必须加上三个限定条件。

第一,它来自 PR 作者自己的测试,不是 Pi 官方基准,也没有独立复现。

第二,这个 PR 没有合并。它因为 Pi 的贡献者审批流程被机器人自动关闭,并非 Harness v2 文档已经承诺的组成部分。

第三,它对 DeepSeek Harness 的对比需要更准确地理解。DSH 的工具结果剪枝会让模型当前看到的中段消失,但完整原始事件仍保留在 append-only session log 中。Pi 社区方案的额外价值,是给模型提供一个可以主动读取完整结果的文件路径和偏移量。差异在于“模型能不能方便地找回”,不是“底层有没有保存原始日志”。

这项实验还不能证明 Pi 已经获得确定的 Token 优势,但它给出了一个实用的接口原则:大结果保存在可寻址的外部存储中,上下文只留下索引、预览和恢复方法。

模型和 Harness,并不是两个独立模块

最近 Pi 的另一个问题,也说明了为什么 Harness 不能只被看成模型外面的一层薄壳。

有开发者发现,新版 Claude 在 Pi 的 edit 工具中偶尔会生成 Schema 之外的字段,例如:

requireUnique
matchCase
oldText2
newText2

Pi 维护者后来做了更多重采样实验。在一组固定上下文中,Claude Opus 4.8 的失败率达到 20%;去掉历史中的 thinking blocks 后降到 8%;开启严格工具调用后降到 0。与此同时,部分模型和新鲜的单轮编辑请求又无法复现这个问题。

讨论最终没有得到一个已经被证明的单一根因。较可信的解释包括:嵌套 edits[] Schema 增加了生成难度,长 Agent 历史强化了错误模式,以及模型对自己熟悉的编辑工具形状形成了较强先验。

这件事最重要的结论,不是“Claude 被 Claude Code 训坏了”。目前没有足够证据支持这种确定说法。

更可靠的结论是:软件模块化,不等于模型行为也能模块化。

代码里换一个工具 Schema,看起来只是替换了一个接口;对模型而言,工具名字、参数层级、系统提示词、历史消息和错误恢复策略,共同构成了它实际面对的环境。改动任何一项,都可能改变整条轨迹。

因此,Agent 的实际能力不能只用模型名来描述,更接近:

模型 × Harness × 工具 Schema × 上下文策略 × 恢复策略

同一个模型放进不同 Harness,可能表现得像两个不同的产品。

Pi 不是在反对插件,而是在给插件划边界

Pi 一直强调极简核心和可扩展性。Harness v2 没有改变这一点,但它对扩展机制提出了更严格的语义要求。

设计文档把扩展分为两类:

Event:只能观察,不能改变执行
Hook:可以拦截和修改上下文、请求、工具与运行边界

Event 失败不能影响主流程。Hook 如果会影响恢复结果,其输出必须在后续效果开始前被持久化。Hook 自己产生的外部副作用则不在 Harness 的 exactly-once 保证范围内,必须由扩展作者做幂等。

这不是“所有东西都做成插件”或“禁止插件”的二选一。核心问题是,扩展可以改什么、改动何时生效、崩溃后会不会再执行,以及它的结果是否已经进入持久状态。

如果这些边界没有定义清楚,插件在代码层面虽然松耦合,模型行为和恢复状态却可能彼此污染。

这份设计最诚实的地方,是写清了它不做什么

Harness v2 当前明确不处理:

  1. 外部 Hook 副作用的 exactly-once。
  2. 模型流式输出的断点续传。
  3. 同一 session 的多写入者。
  4. 跨节点复制与分布式一致性。
  5. 除旧版 coding-agent v3 会话以外的全面兼容迁移。

实现计划中,工具恢复、自动压缩、溢出恢复、观测完整性和最终后端审计等关键阶段仍未完成。

所以现在最准确的描述,不是“Pi 已经解决了长任务 Agent”,而是“Pi 已经把这个问题拆到了可以逐项实现和测试的程度”。

文档甚至要求在每一个外部效果边界关闭 Harness,重新打开同一存储,再执行两次恢复,用这种机械化方法覆盖所有崩溃前缀。Memory、JSONL 和 SQLite 三种后端还要通过同一套一致性测试。

这种测试思路,比发布一段长时间运行的成功演示更有价值。演示只能证明一次任务跑通,崩溃矩阵才能证明系统知道自己在哪些状态下可以恢复。

下一代 Agent 的竞争,可能发生在模型下面

短任务时代,Agent Harness 看起来只是模型外面的一段胶水代码。

长任务时代,它要同时承担任务日志、状态机、上下文编排、成本账本、并发隔离、崩溃恢复、扩展边界和观测系统。

它越来越像一个小型数据库、任务调度器和操作系统的混合体。

对普通的十分钟编码任务,这套设计可能过重。现有 Pi 的极简循环仍然更直接,也更容易理解和修改。

但如果 Agent 要在后台持续工作数小时,跨越网络波动、进程升级和人工插入指令,可靠性就不能继续依赖“希望这次别崩”。

Agent 工程的评价标准也该跟着扩大了。

过去我们看模型会不会写代码、工具数量够不够多、Benchmark 分数高不高。

接下来还要看:任务被接受后会不会丢,工具副作用是否可判定,恢复会不会重复扣费,上下文能否按需找回,并行任务是否隔离,以及每一次扩展改动会怎样影响模型行为。

一个 while loop 当然还能启动 Agent。

但要让 Agent 稳定地工作一整天,光有循环已经不够了。

参考资料

  1. Pi:Durable AgentHarness design
  2. 相关讨论帖
  3. Pi PR #8172:tool-result pruner + spill extension
  4. DeepSeek Harness:tool-result pruner
  5. Pi Issue #6278:新版 Claude 的 edit 工具参数问题
微信搜一搜:智简 Smart&Concise 公众号二维码

关注公众号

智简 Smart&Concise

微信搜一搜,获取独立开发与 AI 实践更新。