Pi Durable 详解:让 Agent 在崩溃后继续工作

2026 年 10 月 1 日,Earendil 在发布 Pi 1.0 的同时介绍了实验性软件包 Pi Durable。它面向长期运行、能跨进程重启、可从多个界面访问的 Agent 应用。本文依据 官方介绍《Pi Durable》 与 当前仓库 README 整理;截至 2026 年 10 月 10 日,README 仍明确标注 Experimental,API 可能随版本变化。

一句话说,Pi Durable 把对话、模型调用、工具调用和应用状态都放进可持久化的执行系统里。进程中途退出后,程序重新打开同一份存储,便能找回未完成的工作,并按规则继续。但这不代表任何外部操作都可以无条件重跑。

1. 先分清 Pi Coding Agent 和 Pi Durable

Pi Coding Agent 主要是人在本地或远程终端里操作的编码助手。程序退出后,使用者可以查看会话并指示它继续。Pi Durable 则是搭建 Agent 应用的框架:一个长期运行的服务可以同时承载多个对话,让 Slack、网页或其他客户端接入,在崩溃和重新部署之后恢复工作。官方明确说,Pi Durable 不取代 Pi Coding Agent;它与后者共享 pi-ai 等组件及简洁、可改造的设计取向。

维度 Pi Coding Agent Pi Durable
主要场景 人在终端里使用编码 Agent 开发长期运行、跨界面接入的 Agent 应用
会话和工作 以交互式编码会话为中心 对话、模型轮次、工具调用和自定义流程都纳入持久任务系统
进程中断 使用者查看并继续 重开存储后由任务调度器恢复未完成工作
多人接入 不是核心场景 多个客户端可观察并引导同一对话
产品形态 可以直接使用的编码工具 需要开发者配置存储、环境、工具和应用入口的框架

如果你读过 [[Pi/Pi Architecture:拆解 Coding Agent 的内部架构|Pi Coding Agent 架构]],可以把 Durable 理解为在“模型与工具循环”外,进一步明确持久状态、任务调度、故障恢复和多客户端订阅的应用层。这个理解是架构对照,不表示两者内部实现完全相同。

2. 核心执行模型:Harness、Conversation、Task、Commit

官方对 Harness 的定义很直接:它包含存储,以及并行运行一个或多个模型对话所需的机制;工具和工具运行的环境也由它提供。核心对象可这样理解:

应用客户端提交输入
↓
Harness 管理多个 Conversation
↓
每个输入触发 Generation Task
↓
模型给出答案或 Tool Call
↓
Tool Task 执行;结果入库;必要时继续下一轮模型调用
  • Conversation:一份由不可变条目组成的对话记录。它保存所选模型、思考级别、扩展、工具、额外指令和工作目录。一个 Harness 可以同时运行多个 Conversation。
  • Task:能保存检查点的工作单元。模型请求、工具调用、上下文压缩都是内建 Task;应用也可以定义自己的 Task。
  • Commit:原子写入边界。一次提交可同时追加对话条目、更新应用文档、创建任务;要么都成功,要么都不写入。
  • Execution Environment:工具实际访问文件、Shell 或其他资源的地方。Harness 可在一台机器上运行,工具的环境可通过适配器放在另一台机器上。

因此,“Agent 恢复”并不是重新把所有历史消息喂给模型,然后希望模型猜出上一步。系统保存了具体任务及其状态,重启后知道哪些任务尚未完成。参见 README 的概念和恢复章节。

3. 崩溃后究竟恢复到哪里

Pi Durable 在每个任务步骤之间保存检查点。进程退出后,应用重新以相同存储打开 Harness,调用 resume(),调度器会继续未完成的任务。未完成的输入仍在队列里;客户端用同一个 requestId 重试输入,会拿到原有 Submission,而不是再提交一次。

不同工作有不同恢复策略:

中断位置 恢复行为
模型请求途中 重新发送请求;先前的部分回答保留在记录中并标记为中止
声明 replay: "safe" 的工具途中 可以重新运行该工具
未声明安全重放的工具途中 不自动重做;把“调用被中断”及已保存输出告知模型
排队的用户输入 仍留在队列;可用 requestId 找回原提交

这里要把两种“只执行一次”分开:requestId 保证的是重复提交同一输入不会产生第二个 Submission;它不保证外部副作用只发生一次。 比如模型调用了支付服务,支付成功后进程却在本地记账前崩溃,仅靠 Harness 无法知道钱是否已扣。外部接口仍应使用幂等键、状态查询或补偿操作。官方自定义付款任务示例就在扣款时使用基于 Task ID 的幂等键,并在中止时定义退款步骤。官方崩溃恢复与任务示例

还有一个细节:部分回答和工具输出为了流式展示会定期提交,README 写明默认至多每 100 毫秒提交一次。崩溃可能丢失尚未提交的最后一小段流式进度;已提交的记录仍保持一致。README 的观察会话说明

4. 并发、多会话与多人接入

一个 Harness 可以并行跑很多 Conversation,而每个 Conversation 可选不同模型、工具权限、指令和工作目录。对话也可以从某条历史消息处分叉:子对话看到父对话到该点为止的历史,不必复制整份记录。官方举例,一个 Slack 频道是主对话,某条消息下的讨论线程则从该消息分叉;两边能同时继续。

子 Agent 不是 Durable 内建的一类特殊对象。 应用可以把它实现为由某个工具任务创建和拥有的 Conversation。子对话独立运行、恢复和计费;父工具可以等待它完成。如果父任务中止,所有权关系决定子任务是否连带中止。后台 Task 则可继续运行,让主对话先进入空闲状态,例如等待明天触发的提醒。官方多会话、子 Agent 与任务所有权说明

一个忙碌的对话收到新输入时,应用有三种常见选择:默认排到当前运行结束后作为下一轮输入;用 whenBusy: "steer" 在当前工具轮结束后加入正在进行的工作;或用 whenBusy: "reject" 拒绝。多个客户端可同时订阅对话视图,看到已有记录、当前回答、工具输出、排队消息和用量;晚加入的客户端先取当前状态,再接收增量变化。README:Busy Conversations

这里的“多人”指多个使用者或界面访问同一 Agent。它不意味着多个 Harness 进程可以同时写同一份存储;当前存储设计要求一份存储在一个时刻由一个进程拥有。

5. Extension:怎样把框架变成自己的 Agent

一个 Extension 是有名字的一组组件,可以带来系统提示词片段、工具、Hook、工具包装器和自定义 Task。应用把扩展安装进 Registry;每个 Conversation 保存它选择的扩展和工具名称,而不是把代码本身序列化进去。

这带来几种能力:

  1. 按对话配置工具。 主 Agent 能读写文件,审阅 Agent 可以只给只读工具;某个聊天线程允许搜索问题,却不允许部署。
  2. 让提示词随项目变化。 系统提示词片段会在请求前重新组装;变化会在对话记录中留下位置,因此重启或分叉仍能知道模型当时看到什么。
  3. 拦截模型与工具流程。 Hook 可改写请求、阻止工具调用、修改结果,也能参与压缩流程。一个 Hook 在崩溃后可能再次执行,因此已经做出的审批决定应写入任务 memo。
  4. 运行时更换实现。 重新安装同名 Extension 后,正在执行的工具调用继续使用原实现,之后的调用使用新实现;重启后的 Conversation 也会解析新进程安装的实现。

这就是作者所说的 malleable:运行中的 Agent 应用可以演进,而对话与任务状态继续存在。它同时要求开发者认真管理工具语义和变更兼容性,尤其是尚未完成的长期任务。官方 Extensions、Hooks 与 Malleable 章节

6. 长会话:后台压缩、Reset 与 Handoff

Durable 会保留历史消息,但模型每次请求仍受上下文窗口限制。接近限制时,它可以启动后台压缩任务总结较旧内容;对话继续进行,摘要通常在下一轮边界放入上下文。只有下一次模型请求实在放不下时,才必须等待摘要完成。如果提供商仍报上下文过长,Harness 会压缩后再试一次。旧记录不会因压缩而删除。

reset() 更彻底:它开始一段新上下文,可选带一份交接说明;工具也能通过 control: { handoff } 请求相同动作。旧内容退出模型的当前视野,但仍保存在存储中,应用可以提供历史搜索工具。它让长期服务在必要时“给自己写交接,再继续做事”。官方 Compaction 章节

这里与 [[Pi/Pi Compaction:上下文压缩的完整流程|普通 Pi 的 Compaction]] 属于相同的问题域,但触发时机、后台任务和恢复实现要按 Durable 的文档单独理解,不能把两套机制的参数和行为直接混用。

7. 对话之外的应用状态也能持久化

Agent 应用往往还有待办、计划、工单状态或沙箱配置。如果这些信息只在另一个内存对象里,可能出现“对话说任务已完成,待办表却没更新”的不一致。Pi Durable 的 Document 是与 Transcript 一起存储的类型化 JSON 状态;两者可在同一个 Commit 里更新。

Document 还可定义分叉语义:新对话继承父对话在分叉时的值、继承父对话当前的值,或从一个新初始值开始。界面可以订阅已提交的 Document 状态,例如实时显示待办列表。官方 Durable application state 章节

8. 存储与运行边界

Pi Durable 提供内存、SQLite、JSONL,以及 Cloudflare Durable Object SQLite 适配方式。内存后端适合示例与测试,不会跨重启保存;SQLite 使用 WAL,README 指出进程崩溃后提交可恢复,但 synchronous=NORMAL 下断电或主机故障可能丢失最新提交;JSONL 可用 fsync: true 在提交标记前落盘。存储接口也允许自己实现其他后端,并用仓库提供的一致性测试验证。README:Storage

另一个运行边界是:当前所谓“能在任何地方运行”,具体指有 JavaScript 运行时的环境。默认 Node Execution Environment 给工具本地文件与 Shell;如果 Harness 与工具要运行在不同机器上,需要实现或接入合适的远程执行环境。官方 Long runs anywhere 章节

9. 我的判断:什么时候值得采用

如果需求只是一个人在终端中使用编码助手,Pi Coding Agent 已经给出了成品体验。若应用必须长期运行、进程重启后自动续做、同时服务多个用户或界面,并要求工具、对话与业务状态能统一追踪,Pi Durable 的抽象就更贴切。

最值得借鉴的不是“自动重试”四个字,而是它把责任分清了:Harness 持久化任务意图和状态;工具明确声明能否重放;外部服务通过幂等或补偿处理副作用;客户端通过提交 ID 和状态订阅恢复交互。 这几个环节缺一个,长时间运行的 Agent 都可能在故障后重复执行或遗失工作。

目前仍需谨慎的地方也很具体:软件包尚处实验阶段;每份存储仅允许一个拥有者进程;SQLite 的最新提交在主机故障场景下有特定耐久边界;自定义工具的外部副作用仍由应用设计负责。它适合做原型和有明确恢复需求的应用,但不能仅因为名字叫 Durable,就把整个业务流程视为天然“恰好执行一次”。

资料:Earendil 官方介绍 · Pi Durable README · Pi 1.0 发布文章