跳到主要内容
赞助推荐 搬瓦工怎么选:三网直连 CN2 GIA-E
架构设计

Pi Durable:Agent 崩溃后,任务还能接着跑

25 分钟阅读阅读()
赞助推荐 团队协作里的 AI 办公工作台

Pi Durable 持久化 Agent Runtime 架构:记录意图、保存检查点、重启续跑

大多数 Agent 看起来很聪明,底层却很脆。

赞助推荐 一人公司 · 创业装备库
赞助推荐 一人公司 · 创业装备库

它可以连续调用十几个工具,可以把任务拆给多个子 Agent,也可以在终端里工作几个小时。但只要进程被杀、容器重启、网络中断,或者机器合盖休眠,刚才那条执行链往往就断了。接下来通常要靠人翻日志、补上下文,再说一句:“从刚才的地方继续。”

这类系统更像一段运行时间较长的脚本。它有推理能力,却没有可靠软件该有的恢复语义。

Earendil 在发布 Pi 1.0 的同时,开源了实验性项目 Pi Durable。作者把它定义为一个面向长期运行、可恢复、可动态修改、支持多人协作的 Agent Harness。读完项目介绍和 README,再顺着任务、存储、工具重放与文档状态的实现看下去,最值得关注的并非又多了一个 Agent 框架,而是它把 Agent 的运行过程重新建模成了一套持久化状态机。

一句话概括它的价值:模型仍然可以是概率性的,但执行过程开始拥有明确、持久且可审计的提交与恢复边界。

这一步很可能会影响下一代 Agent 基础设施的形态。不过,Pi Durable 当前仍明确标注为 Experimental,API 可能随版本变化。它展示了一条很有分量的路线,还没有替所有生产问题给出最终答案。

传统 Agent 的问题:状态活在进程里

常见 Agent Loop 大致长这样:

用户输入
  → 调用模型
  → 模型决定调用工具
  → 执行工具
  → 把结果交回模型
  → 生成下一步或最终答案

这个循环适合一次问答,也适合有人盯着的编码助手。一旦任务跨越几分钟、几小时甚至几天,问题会迅速冒出来:

  • 模型请求发出去后进程崩了,这次请求该不该重发?
  • 工具已经执行,但结果还没写入会话,重启后会不会再执行一次?
  • 子 Agent 完成了两个分支,第三个分支中断,恢复时怎样避免全部重来?
  • 用户在 Agent 忙碌时追加一句“先查 staging 日志”,这条指令应该插在哪里?
  • 会话被压缩以后,旧消息、应用状态和正在运行的任务怎样保持一致?
  • 多个网页、Slack 客户端或控制台同时观察一个 Agent,看到的状态以谁为准?

如果这些状态只存在于 Promise、内存对象和 WebSocket 连接中,进程结束就等于事实消失。补一个数据库只能保存聊天记录,无法自动获得可靠的恢复能力。系统还需要知道:什么已经提交,什么正在执行,什么允许重放,什么必须交给模型重新判断。

Pi Durable 的切入点就在这里。它把 Harness 定义为“存储,加上并行运行一个或多个模型会话所需的机器”。会话、模型轮次、工具调用、压缩、子任务和应用状态,都进入同一套提交与恢复机制。

核心设计:先提交,再对外可见

Pi Durable 的规范给出了一条很硬的规则:

Session 原子提交不可变条目、完整任务记录和由 Chord 跟踪的文档。只有已经提交的状态才可被观察。

这句话决定了整个系统的气质。

在很多 Agent 应用里,界面先收到一段流式输出,数据库稍后再补写;工具开始运行后,任务状态才异步落库;应用数据和对话记录分别写入不同系统。正常路径很顺,崩溃路径却充满“到底发生过没有”的灰色地带。

Pi Durable 把可观察性放在持久化之后:

输入 / 决策 / 状态变化
        ↓
原子提交到 Storage
        ↓
更新内存中的已提交视图
        ↓
UI、订阅者和后续任务才能看到

它要求:

  1. 一次 Session commit 中的记录与文档写入要么全部成功,要么全部失败;
  2. 文档更新只有在存储提交成功后才发布;
  3. 外部副作用不能放进 Session 的数据库事务;
  4. 存储错误会让当前 Session 失效,需要重新打开,避免带着半可信状态继续运行。

这里借鉴了数据库和 Durable Execution 的成熟思想,同时保留了 Agent 的交互结构。聊天记录不再只是调试日志,而成为可持久查询、可分叉、可定位,并能参与恢复判断的执行事实。

Task:把每一步工作变成可恢复状态机

Pi Durable 将模型生成、工具调用和上下文压缩都建模为 Task。扩展也可以定义自己的 Task。每个 Task 包含输入、检查点、所有权和最终结果。

任务执行一段逻辑后,通过 commit() 保存下一阶段:

await runtime.commit(
  () => ({
    status: "running",
    checkpoint: { phase: "tick", n: n + 1 },
  }),
  context,
);

进程退出以后,新进程打开同一个 SQLite 或 JSONL 存储,扫描未完成任务,再从最后一次已提交的 checkpoint 继续。等待、定时器和任务依赖也属于这套状态机。

这里有几个前提:新进程能访问同一份仍然完整的持久存储,安装了兼容的 Task definition 和 Extension,并重新启动调度。默认 SQLite/JSONL 后端主要覆盖进程级崩溃;断电、宿主机损坏和跨设备迁移还取决于 fsync、复制、备份与外部存储架构。MemoryStorage 自然也无法跨进程恢复。

这比“捕获异常后 retry”多了一层关键语义。retry 关心某个函数再跑一次;durability 关心整个工作流在任意中断点之后,还能判断已经完成了什么、接下来应该运行什么。

项目中的恢复示例很直观:一个计数任务运行到 2 时关闭 Harness,再用同一 SQLite 文件重新打开。第二个 Harness 无需人工拼接上下文,会继续完成 3、4、5。示例用 memo 先记录“2 已经打印过”,以避免恢复时重复输出。

这种顺序仍有代价:若 memo 已提交、实际输出尚未发生时进程崩溃,这一次输出可能永远缺失。真实业务副作用仍需使用幂等键、外部回执查询或补偿机制。

工具恢复:安全重放与中断上报必须分开

Agent 真正难处理的地方往往是工具。搜索接口重复调用一般问题不大,部署、扣款、发消息和创建工单则完全不同。

Pi Durable 要求工具明确声明重放策略:

const searchIssues = defineTool({
  name: "search_issues",
  replay: "safe",
  execute: async (args) => {
    // 只读操作,中断后可以重跑
  },
});

const deploy = defineTool({
  name: "deploy",
  execute: async (args) => {
    // 没有声明 safe,中断后不会自动再部署一次
  },
});

工具调用的意图会在执行前提交。若进程在调用中间死亡:

  • 只有中断前记录的策略和恢复时解析到的当前工具实现都允许安全重放时,工具才会重新执行;
  • 未声明安全重放的工具不会被偷偷再跑一遍,模型会收到 interrupted 结果,再决定查询外部状态、补偿或请求人工介入;
  • 已经流出的工具输出可以保留,帮助后续判断执行到了哪里。

这条边界很重要。Durable 并不自动让外部世界拥有 exactly-once 语义。支付、发布、部署这类操作仍要依赖幂等键、外部状态查询或补偿事务。Pi Durable 提供的是一致的承载结构,让开发者可以明确表达这些策略。

项目对输入提交也提供了 requestId:客户端在超时后重试相同请求,会拿到原 submission,避免同一条用户输入被重复接纳。它解决的是提交层面的去重,与外部工具的业务幂等不能混为一谈。

所有权树:子 Agent、并行任务和取消终于有了统一模型

很多多 Agent 系统擅长“启动更多 Agent”,却很少讲清楚谁负责结束它们。

Pi Durable 把 Task 和 Conversation 组织成所有权树:

Conversation
└── Generation Task
    ├── Tool Task A
    │   └── Child Conversation(子 Agent)
    └── Tool Task B
        └── Child Tasks

取消会沿所有权树向下传播,并按子任务优先的顺序执行终止流程。真正的退款、回滚或资源释放仍需由各 Task 的 abort handler 明确定义。父任务只有在自己拥有的工作全部结束后才算真正结束。前台任务属于当前会话的工作,用户中止会影响它;后台任务可以继续运行,适合提醒、长时间搜索或跨轮次任务。

作者强调 Pi Durable 没有内置“subagent”这个特殊对象。子 Agent 就是一段由工具创建并拥有的 Conversation。这个决定很干净:

  • 子 Agent 继承同一套持久化和恢复语义;
  • 它有独立 transcript、模型、工具集、成本统计和工作目录;
  • UI 可以把子会话挂在发起它的工具调用下面;
  • 工具重放时,可以通过 owner task 找回原来的子会话,避免再创建一个。

这样一来,多 Agent 不再需要单独维护一套生命周期系统,它只是 durable task graph 的一种组合方式。

Conversation:并发、分叉和多人介入

一个 Harness 可以同时运行多条 Conversation。每条会话保存自己的模型、思考等级、扩展、工具、指令和工作目录。

Conversation 还可以在某个历史 Entry 上 fork。分支只看到父会话到该位置为止的历史,不需要复制整段 transcript。Slack 频道和 Thread 是作者给出的典型场景:频道是一条主会话,某条回复可以从对应 Entry 分叉成线程,两边继续并发运行。

多人协作也建立在已提交状态之上。新客户端加入时先拿到当前视图,包括 transcript、流式回答、运行中的工具、排队消息、Agent 配置和 usage;随后只订阅增量变化。客户端断线重连,不需要依赖某个进程内的临时事件流还原现场。

这里的 multiplayer 指多个客户端连接到同一个拥有 storage 的 Harness,并不表示多个 Harness 实例可以同时写同一存储。流式 partial 和工具输出也存在提交粒度:客户端只看到已经提交的部分,崩溃恢复边界落在最后一次 progress commit,而非进程消失前实际生成的最后一个字节。

更有意思的是 steer:Agent 正忙时,用户可以提交一条 whenBusy: "steer" 的消息,让它在当前工具批次之后调整方向。对于长任务,这比“等它做完再纠正”更接近日常协作。

无限长会话的关键不只是自动摘要

长会话最终都会碰到上下文窗口。Pi Durable 将 compaction 也做成 Task,并允许它在后台提前运行。只有下一次请求确实放不下时,会话才等待摘要完成。

旧消息不会因为压缩而从存储中删除。摘要只是模型下一阶段看到的上下文入口;完整 Entry 仍保留在存储中,宿主应用可以通过扫描 API 或自建索引实现历史搜索。也可以通过 reset() 或 handoff 开启新上下文,再让工具回查历史。

这带来两个好处:

  • 交互路径可以保持轻量,不必把全部历史塞给模型;
  • 审计和应用逻辑仍保留完整事实,不会把摘要误当成唯一真相。

当然,“无限长”指存储和会话生命周期可以持续延伸,不代表模型拥有无限准确的记忆。摘要损失、历史检索质量和跨阶段目标漂移依旧存在,只是现在有了更清晰的工程处理位置。

Durable Document:让应用状态和对话一起提交

Agent 应用通常还有会话之外的状态:Todo、计划、工单、审批结果、沙箱信息、表单进度。把这些状态放在另一套数据库事务里,很容易出现“对话说已经完成,业务表却没更新”的分裂。

Pi Durable 提供 typed JSON Document,并允许 Entry、Task 与 Document 在同一个 commit 中更新。文档还能定义 fork 语义:

  • asOf:复制父会话在分叉 Entry 所属 commit 时的值;
  • current:复制 fork commit 执行时父会话最新的已提交值,复制后父子独立演化;
  • initial:不复制父状态,子会话首次访问时从初始化值创建。

它背后的 Chord 用操作和基线管理文档状态。对应用开发者来说,更直接的意义是:聊天、任务和 UI 业务状态可以共享一套提交边界。前提是应用主动把相关变更放进同一个 Session commit,拆成两次提交仍会形成两个独立可观察状态。

这让 Agent 有机会从“会说话的工具调用器”进入真正的交互式应用运行时。旅行规划、工单处置、审批流、远程运维等场景,都需要这类状态一致性。

Malleable:运行中的 Agent 可以换能力

Pi Durable 延续了 Pi 对可塑性的重视。Extension 可以包含:

  • system prompt sections;
  • tools;
  • hooks;
  • tool wrappers;
  • 自定义 tasks。

Conversation 持久化的是扩展名和工具名,实际代码由进程中的 Registry 解析。Registry 用同名 Extension 重新安装时,已经运行的调用继续使用启动时的代码,下一次调用则采用新实现。进程重启后,会话也会解析到新版本。

这为宿主应用实现热更新、按会话选择不同工具版本提供了基础。但它同时提出一个版本治理问题:持久化任务可能跨越代码升级,Task definition 的版本兼容、数据迁移和回滚策略必须认真设计。名字稳定不等于行为天然兼容。

升级后若 Task definition 缺失、版本倒退、没有兼容的 migrate(),或迁移失败,未完成任务不会自动猜测新状态,而会保持 blocked。普通 Document 也不会在 Harness 打开时全量迁移,其迁移由后续 typed access 触发。

为什么说它有“跨时代”的味道

“跨时代”很容易写成宣传词。checkpoint、事务和 durable execution 也早有成熟实践。Pi Durable 真正有分量的地方,是把这些机制紧密整合进 Conversation、Tool、Task、Document 和多客户端交互模型。具体可以拆成四点。

1. Agent 的可靠性边界从 Prompt 移到了 Runtime

过去常见做法是通过 Prompt 告诉模型“记住进度”“失败后重试”“不要重复执行”。这些要求无法替代事务、幂等和检查点。Pi Durable 直接在运行时表达提交、重放、所有权和恢复,可靠性不再依赖模型临场发挥。

2. 会话、工作流和应用状态被放进同一抽象

传统 Chatbot 框架偏向消息,工作流引擎偏向确定性步骤,Agent 框架偏向工具循环。Pi Durable 尝试用 Entry、Task、Conversation 和 Document 把三者接到一起。模型调用只是任务图中的一种节点,应用状态也拥有相同的持久化边界。

3. 多 Agent 被还原成任务所有权问题

子 Agent 没有被包装成神秘的新物种。它是一条由 Task 拥有的 Conversation。这个抽象足够小,也足够通用,取消、恢复、计费、观察和 UI 展示都可以沿用现有机制。

4. Agent 可以逐渐接近常驻数字进程

当一次会话能够跨越进程重启、客户端重连和上下文压缩,Agent 的生命周期就不必绑定某个终端窗口。它可以持续处理长期目标,等待外部事件,接受多人介入,并在兼容升级后延续已有工作。若要由另一台设备接管,还需要共享或迁移同一份可靠存储,并重建兼容的代码与执行环境。

这会让 Agent 的产品形态发生变化:从“打开一次、完成一轮”的助手,转向有历史、有任务图、有业务状态、可以长期运营的软件实体。

它还没有解决什么

Pi Durable 很值得关注,但当前阶段不适合被描述成开箱即用的企业级工作流平台。

API 仍在快速变化

README 明确写着 Experimental,版本之间可能无预告变更。文章撰写时,npm 上的 @earendil-works/pi-durable 最新版本为 1.3.0,并要求 Node.js 22.19.0 以上。生产采用前要锁版本、做迁移测试,并准备存储结构与任务定义的升级方案。

单存储仍由单进程拥有

作者当前设计是一个进程拥有一份 storage,其他客户端连接到该进程。这简化了原子提交和调度,但水平扩展、主节点故障转移、跨地域复制与脑裂处理,需要应用层或新的存储适配器继续解决。

外部副作用仍需要业务幂等

Harness 能记录调用意图,也能区分安全重放与不可重放工具。它无法替第三方支付、部署平台或消息系统创造事务。关键操作依旧要设计 idempotency key、状态查询、补偿和人工审批。

恢复正确不等于决策正确

系统可以准确恢复到最后一个 checkpoint,模型仍可能误解目标、选择错误工具或在摘要后丢失细节。Durability 提升的是执行连续性和状态可信度,不能替代评测、权限控制、可观测性与领域校验。

安全边界取决于宿主应用

Pi Durable 可以给不同会话配置不同工具和执行环境,也支持 Hook 拦截调用。这些是构建安全系统的材料。真正上线时仍需补齐租户隔离、凭据作用域、网络策略、审计、审批和资源配额。

哪些场景最适合尝试

如果任务满足以下两个以上条件,Pi Durable 的价值会比较明显:

  • 运行时间长,可能跨越容器重启或机器休眠;
  • 包含多个并行子任务或子 Agent;
  • 用户需要中途观察、追加指令或接管;
  • 工具副作用有严格的重试边界;
  • 对话之外还有计划、工单、Todo 等业务状态;
  • 同一 Agent 需要从 Web、Slack、终端等多个入口访问;
  • 会话会持续很久,需要后台压缩和历史检索。

适合的早期场景包括 GitHub issue triage、持续研究、值班排障、长周期编码任务、旅行规划、审批辅助和运维协作。

若只是单轮问答、短时脚本或完全确定性的 ETL,直接使用模型 SDK、队列或成熟工作流引擎通常更省事。引入 durable runtime 会带来状态建模、版本迁移和运维成本。

给 Agent 架构设计者的三个启发

即使暂时不采用 Pi Durable,它也给出了三个可以立即使用的设计原则。

第一,外部动作要采用 effect sandwich。 对工具调用,Harness 会先提交调用意图;自定义 Task 若包含外部副作用,也应显式采用“提交意图—执行副作用—提交结果”。进程崩溃后,系统至少知道原本准备做什么,并能判断查询、重放或补偿。

第二,把重试策略放进工具契约。 “失败就重试”过于粗糙。只读查询、安全幂等写入和不可重放动作应当使用不同恢复路径。

第三,为任务建立所有权。 谁创建、谁等待、谁取消、谁负责清理,需要进入数据模型。多 Agent 的复杂度很大一部分来自生命周期失控。

结语

过去两年,Agent 能力提升主要来自更强的模型、更长的上下文和更多工具。下一阶段的竞争会逐渐落到运行时:任务能否跨故障继续,副作用能否安全处理,状态能否被审计,多人能否随时加入,以及系统升级后能否接着工作。

Pi Durable 把这些问题摆在了架构中心。它仍年轻,也留下了扩展、迁移和安全方面的大量工程题,但方向已经足够清晰:Agent 不该把一次进程存活,当成自己全部的生命。

如果这条路线继续成熟,未来谈论 Agent 平台时,durability 很可能会像今天的工具调用、上下文管理一样,成为默认能力,而非高级选配。


参考资料

  1. Earendil Engineering:Pi Durable
  2. GitHub:@earendil-works/pi-durable README
  3. GitHub:Durable specification
  4. npm:@earendil-works/pi-durable
赞助推荐 一键部署 AI 大模型
赞助推荐 一键部署 AI 大模型
赞(0)
未经允许不得转载:80aj » Pi Durable:Agent 崩溃后,任务还能接着跑
赞助推荐 低成本上手 Claude Code 的中转选择
赞助推荐 低成本上手 Claude Code 的中转选择
赞助推荐 一键部署 AI 大模型
赞助推荐 一键部署 AI 大模型