Pi Durable 快速指南

Earendil 于 2026 年 10 月 1 日随 Pi 1.0 一同发布了 Pi Durable——一个实验性的 MIT 许可 TypeScript 包。

Pi Durable 快速指南
博途PLC工程智能体 | AI智能体博途网关 | 博途PLC程序知识图谱 | 梯形图转SCL | 自然语言生成梯形图 | 自然语言生成SCL | 逆向生成程序块文档 | 梯形图在线查看 | 博途编程文档MCP | AI模型价格对比 | AI工具导航 | ONNX模型库 | Vibe Coding教程 | PLC在线仿真器 | Tripo 3D | Meshy AI

Earendil 于 2026 年 10 月 1 日随 Pi 1.0 一同发布了 Pi Durable——一个实验性的 MIT 许可 TypeScript 包。它的智能体能在进程崩溃中幸存:每一次模型请求、工具调用和压缩(compaction)都是一个会向 SQLite 或 JSONL 存储写入检查点的任务,每个工具都要声明崩溃后的重跑是否安全。在一次 kill -9 测试中,标记为可重放安全的搜索被重新执行,而不安全的预订则被作为"已中断"报告给模型,而不是重复执行。这个包大约 15,000 行代码,同一时刻只有一个进程拥有某个存储。

10 月 1 日,Pi 编码智能体背后的公司 Earendil 发布了 Pi 1.0,以及旁边一个名为 Pi Durable 的实验性包:一个面向"底层进程死掉也继续运行"的智能体的 TypeScript harness。 每一次模型请求、工具调用和压缩都是一个任务,在继续前进之前先向存储写入检查点,这样一个全新的进程打开同一个文件就能接着往下走——哪怕上一个进程死在某次工具调用的半路上。这正是"你在终端里盯着的编码智能体"与"你真正想让它连续跑上几周的 Slack 机器人、分诊机器人或研究智能体"之间缺失的那一块。

我读了公告和包的 README,逐帧回放了 Earendil 的演示录像,然后亲自用 kill -9 杀掉一个 Pi Durable 进程,看它怎么恢复。简短的结论:它很小、对副作用很诚实,而且不是分布式的。我认为这是正确的取舍。

A terminal UI with a task tree: vacation.research #12 running in the background and owning conversation 13, pi.generation #21 waiting on 26, 27 and 28, and three pi.tool tasks all running execute

Earendil 的度假规划器把调研任务交给一个子智能体,它同时跑三个搜索。任务树中的每一行都是一个带有自己检查点的持久化任务。截自 Earendil 的演示录像,重新渲染。

1、Pi 留在终端里,Durable 去往其他所有地方

Pi 是一个单人使用的终端工具,Pi 1.0 也保持了这一点:它挂了,你就看看发生了什么,然后让它继续。Pi Durable 则是给"放着让它跑"的智能体准备的独立包:任何客户端都能访问、几个人可以同时操控、能扛住自身的崩溃。它与 Pi 共享模型层 pi-ai,但它是一个构建智能体应用(编码智能体也在内)的框架,而不是一个新的 Pi。

体积本身就是卖点的一部分。整个包不含测试约 15,000 行,Earendil 估计对 GPT 约合 150,000 token、对 Claude 约合 250,000。这段代码预期的读者是你的智能体:公告里的"试一试"部分让你把智能体指向 packages/durable,让它去读 README 和示例。我喜欢这一点。这是一个尺寸恰好能塞进上下文窗口的框架,而其中 3,000 行的存储后端通常是你的智能体可以跳过的部分。

2、一切都是任务,每个任务都留有检查点

Earendil 有一篇完整的文章讲"harness"是什么意思。对 Pi Durable 而言,它是存储、并行运行多路模型对话的机制、这些模型调用的工具,以及工具运行其中的执行环境。一段对话是一份转录(transcript),一个智能体是带有其设置和工具的模型,而 harness 运行的一切——从调用模型到执行工具——都是一个任务。如果你读过智能体架构通常如何分层,那么最后这部分是新的:智能体循环本身是由持久化任务构成的。

存储是可插拔的。Pi Durable 自带会持久化的 SQLite 和 JSONL 后端、一个不持久化的内存后端,以及一套一致性测试套件,供你在键值存储或 Postgres 上编写自己的后端。SQLite 和 JSONL 的代码不使用任何 Node API,因此加一个小小的适配器就能跑在 Bun 上或 Cloudflare Durable Object 里面。在 SQLite 上只有工作集留在内存中,而且由于压缩会把活跃转录保持在上下文窗口内,即使消息达到数万条,内存也保持平稳。

工具通过执行环境触达文件和 shell。自带的是本地 Node;你写一个远程执行环境,harness 就可以跑在一台机器上,而它的工具所操作的文件和 shell 住在另一台机器上。

3、杀掉进程,几乎什么都不丢

演示中最重要的一刻也是最不戏剧化的一刻。规划器的子智能体正在调研天气、博物馆和火车。天气和博物馆完成了。然后进程死在了火车搜索的半路上,有人在 shell 里敲下 vacation --continue。

The last frame of the dead TUI, with one pi.tool task still running, followed by a shell prompt where vacation --continue has been typed

进程死在搜索途中,只剩火车搜索 pi.tool #28 还在运行。截自 Earendil 的演示录像,重新渲染。

一个新进程打开同一个存储,找到未完成的任务,并从各自的最后一个检查点继续。被切断的模型请求会重新发送,部分回答保留在转录中并标记为已中止。排队的消息依然在排队。在演示里,已完成的天气和博物馆搜索保持完成,对话和它的任务树原样恢复,只有火车搜索重新跑了一遍。

最后这一步不是自动的。一个被切断的工具调用是否重跑,由该工具自己决定。

4、重放是每个工具做出的承诺

Harness 无法知道重跑你的代码是否无害,所以每个工具自己说了算,而且默认是"不"。我想用真实的 kill -9 而不是录像来看这一点,于是在 Pi 仓库的检出中针对这个包写了一个短脚本。它使用 Pi 内置的 faux provider——一个脚本化的模型替身——所以不涉及任何 API key。这个"模型"同时请求两次工具调用:一个可以安全重跑的搜索,和一个要扣款的预订。

const search = defineTool({
    name: "search",
    description: "Search train times",
    parameters: Type.Object({ route: Type.String() }),
    replay: "safe", // only reads, so a rerun after a crash is fine
    execute: async (args) => { /* two seconds of searching */ },
});
const book = defineTool({
    name: "book",
    description: "Book and pay for a seat",
    parameters: Type.Object({ train: Type.String() }),
    // No replay: an interrupted booking is reported to the model, never repeated.
    execute: async (args) => { /* two seconds of charging the card */ },
});

const job = { type: "input", content: "Find a train to Graz and book it", requestId: "job-42" } as const;
await root.submit(job, context);

我启动它,在两个工具都还在运行时于 1.5 秒处杀掉它,然后在同一个 SQLite 文件上重新启动:

A terminal: the first run starts search and book and is killed with kill -9; the second run resubmits job-42 and gets submission 7 back, runs the search again to completion, and records the book result as an error saying the tool was interrupted and may have partially run

第二个进程为 job-42 取回了 submission 7,重新跑完搜索,然后把预订作为一个错误交给模型,而不是再次扣款。

发生了三件事,全部与文档一致。requestId 让提交变成了精确一次(exactly-once):重新提交 job-42 返回了原来的 submission,而不是启动第二个任务。搜索从头重跑了一遍。预订没有;模型收到的结果是 Tool book was interrupted and may have partially run,由它来决定下一步怎么办。在我这次运行中最终回答是脚本写死的;换成真实模型,这个决定就由模型来做。

这是对持久化执行(durable execution)中最古老问题的诚实回答。replay: "safe" 让工具变成至少一次(at-least-once),默认则让它至多一次(at-most-once);harness 之外的副作用要想恰好发生一次,只有当对端是幂等的才行。Earendil 那个更长的例子——一个并行扣款多张银行卡、某张被拒后退款其余的结账流程——出于同样的原因把一个幂等键(idempotency key)传给了银行。

钩子(hooks)遵循同样的规则。它们可以介入任务,包括模型请求、工具调用和压缩这些内置任务;由于钩子在崩溃后可能再跑一次,做出决定的钩子要把决定存进一个 memo——随任务一起保存的小值,首次写入者胜。下面这个来自公告的例子放在扩展的 hooks 列表里,把部署放在 Slack 审批后面:

hook(ToolTask, {
    beforeTool: async (call, api, context) => {
        if (call.name !== "deploy") return undefined;
        // After a restart, the hook finds the stored answer instead of asking again.
        let approved = await api.memo<boolean>("approval:deploy", context);
        approved ??= await api.memo("approval:deploy", await askInSlack(call), context);
        return approved ? undefined : { block: "Nobody approved the deploy." };
    },
})

没有 memo 的话,答案回来之后、部署开始之前发生的崩溃会让你的团队被第二次@。有了它,审批能挺过进程重启。

5、子智能体、分叉和操控都只是对话

Pi Durable 没有内置子智能体;Earendil 说构建它们"只需要几行代码"。工具在调用中获得 harness 的 API,于是它可以创建一个属于自己的对话,给它一个更小的模型(例子里是 gpt-6-luna)和自己的指令,提交工作然后等待。子智能体计算自己的开销,UI 可以把它显示在发起它的那次调用下面。崩溃之后,一个重放安全的子智能体工具能找到自己的子智能体并继续等待答案,而在一次非重放安全的调用中发生的崩溃会连同子智能体一起中止。

对话和任务构成同一棵所有权树。中止一个任务会自底向上中止它所拥有的一切,这样每一部分先清理自己的副作用。任务默认是前台的,所以 Esc 能停掉它们;后台任务——比如演示里的调研作业——在对话空闲后仍继续运行。

由于子智能体是一个真实的对话,你可以在它工作时切进去和它说话:

The subagent’s view in the planner TUI, with the trains search printing source 8 of 15, a steer message queued that reads skip the trains, we’re driving, and the status line labelled subagent 13

在子智能体 13 内部,搜索进行中,一条操控(steer)消息排在运行中的工具调用后面。截自 Earendil 的演示录像,重新渲染。

任何客户端都能这么做。UI 需要的一切都是已提交的状态,所以第二个客户端可以很晚才附着到一段运行中的对话上,先拿到当前视图、然后只拿增量,并用 whenBusy: "steer" 发送一条消息。分叉同样廉价:对话可以在任意条目处分叉另一段对话,并且不用复制就能看到父对话的历史。Earendil 的例子是一个 Slack 频道作为一段对话、每个 thread 作为在其所回复消息处的分叉,两者同时运行,而 thread 允许搜索但不允许部署:

const thread = await channel.fork(answered.answer!, { ownership: { kind: "ownerless" } }, context);
await thread.configure({ tools: { remove: [deploy] } }, context);

每段对话存储自己的智能体,从模型和思考等级到工具和工作目录,于是主智能体旁边的审阅者可以在自己的检出里跑一个更便宜的、只读工具的模型。应用状态——比如待办清单或计划——活在有类型的 JSON 文档里,与转录一起提交,所以两者不可能不一致,而且每个文档都写明了分叉会继承什么。

甚至代码也能在一个运行中的智能体下面更换。用你自己的加载器加载一个扩展的新版本,并用相同的名字 registry.install(),它就一步替换掉旧的;已经跑起来的调用在旧代码上跑完,下一次调用用新的。对话存储的是扩展的名字、从来不是代码,所以重启之后它们会绑定到新进程安装的东西上。

6、压缩在智能体继续聊天的同时进行

在许多 harness 里,长对话会撞墙:智能体停下来总结,你干等。在 Pi Durable 里压缩和其他任务一样是一个任务。按 Earendil 例子里的设置,一旦对话距离上下文窗口还剩 49,152 个 token(reserveTokens 的 16,384 加上 backgroundTokens 的 32,768),对较旧消息的摘要就在后台启动,并落在下一个轮次边界上。对话只在最后 16,384 个 token 里等待它——再往下一次请求就装不下了。如果 provider 仍然以请求过长为由拒绝,harness 压缩后重试一次;你也可以在智能体工作的同时带着自己的指令手动压缩。

The main conversation answering a question about Sachertorte while a user message reads two sentences, please, and the task tree shows pi.compaction #39 summarizing alongside two running pi.generation tasks under the label Compacting (manual)

一次手动压缩、一个新的回答和子智能体的模型请求同时在跑。截自 Earendil 的演示录像,重新渲染。

什么都不会被删除。较旧的消息留在存储里,所以 reset() 可以从一份交接笔记开始全新的上下文,而你自己写的 search_history 工具仍能搜索更早之前的消息。构建一个"把工作交接给自己的"智能体,需要的就这么多。

The planner’s final answer, a Vienna weekend plan with a day 1 route from St. Stephen’s Cathedral to the State Opera and Albertina and a day 2 route through Upper Belvedere and Naschmarkt, with no live tasks left and a note that the conversation was compacted

子智能体的报告作为一条消息到达,压缩后的主对话把它变成一份计划,而且计划仍然写着坐公交而不是开车。harness 保证的是操控消息送达了,不是模型如何理解它。截自 Earendil 的演示录像,重新渲染。

7、它解锁了什么

下面是我对"智能体循环默认持久化之后会发生什么"的解读。

智能体变成服务,而不是会话。 Earendil 说接下来几周会展示它自己用 Pi Durable 构建的小工具,比如一个 Slack 机器人或 GitHub 分诊机器人。就是这个形态:用一个便宜的子智能体给 issue 打标签、重新部署也不丢队列的机器人;因为截止时间被存了下来所以明天还会响的提醒;比笔记本入睡活得更久的一次调研运行。

活在边缘的智能体。 不依赖 Node API 的存储意味着 harness 可以放进 Cloudflare Durable Object 里,工具通过远程执行环境触达外界。定时器只在打开了 harness 的进程里触发,所以一个被驱逐的对象需要一个 Durable Object alarm 来唤醒它并调用 resume()。我仍然预期"每个对话树一个 Durable Object"会是人们在它上面构建的第一个严肃的托管模式。

可以共享的智能体。 几个人同时观察和操控同一段对话、从另一个客户端中途加入,这在默认就支持,而不是事后附加的功能。一个全团队都能戳一下的 on-call 智能体,如今主要剩 UI 工作。

8、它到哪里为止

这是实验性的,README 说 API"在版本之间可能不经通知就变化"。在它上面构建,就要准备好追着改动跑。

这里的"持久"指扛过崩溃,不是分布式运行。同一时刻一个进程拥有某个存储、其他客户端附着到它上面,但没有任何机制强制这一点:README 说没有跨进程锁,所以加锁或租约是你自己的事(度假演示就对它的会话目录加了一个 lockfile)。滚动部署时要留意,那里新旧进程会交叠。进程死了总得有东西把它重启,而这个包里没有任何东西能把一个 harness 摊到多台机器上。

崩溃就是进程崩溃。SQLite 后端以 synchronous = NORMAL 运行,所以提交能挺过进程死亡,但如果宿主机或它的电源没了,最新的一次提交可能丢失;JSONL 需要 { fsync: true } 才会在每次提交前刷盘。丢掉记录某次工具调用开始的那次提交,那个调用就会重跑,不管是不是重放安全。

重放安全是你做出的承诺,不是 harness 检查的东西。把一个非幂等的工具标记成 replay: "safe",调用中途的一次崩溃就可能把它跑两遍。设计让这个决定变得显式;它没法保证这个决定正确。

目前只有 TypeScript;Earendil 的 FAQ 说不排除 Rust 移植,但那不是重点。而且 API 是低层的。Earendil 说子智能体"几行代码",公告里的分诊示例连注释大约 50 行。这是"没有魔法"的代价,我认为是正确的代价,但这不是一个五分钟框架。

9、试一试

在一个 Pi 仓库的检出中:

npm install && npm run build
node packages/coding-agent/src/experimental/durable/main.ts
node packages/coding-agent/src/experimental/vacation/main.ts

第一个是跑在 Pi Durable 上的小型编码智能体;第二个是录像里的度假规划器,约 1,300 行 TypeScript,大部分是用编码智能体的组件搭的 TUI 代码。在规划器搜索到一半时杀掉它,再用 --continue 启动,或者它会开一个新会话。构建之后,这个包的大多数示例都可以在 packages/durable 下用我用的同一个 faux provider 运行,所以你不用 API key 就能观察恢复过程。要在你自己的项目里基于它构建:

npm install @earendil-works/pi-durable @earendil-works/pi-ai @earendil-works/chord

Pi 1.0 和 Pi Durable 都是 MIT 许可。如果你一直用重试循环和一个记录"我刚才在哪"的 JSON 文件把智能体缝在一起,这是我第一个会让自己的智能体去读、然后说"读完在它上面重建"的框架。

10、常见问题

Pi Durable 是什么?

Pi Durable 是 Earendil 的一个实验性 TypeScript 包,以 @earendil-works/pi-durable 发布,并于 2026 年 10 月 1 日随 Pi 1.0 一起发布。它是一个用于构建长时间运行的智能体应用的 harness:对话、模型请求、工具调用和应用状态都会先提交到存储再展示任何东西,所以一个在轮次中途死掉的进程可以重启后从最后一个检查点继续。它是 MIT 许可的,位于 earendil-works/pi 仓库中。

Pi Durable 是 Pi 编码智能体的替代品吗?

不是。Pi——编码智能体——仍然是由一个人驱动的终端工具,Pi 1.0 保持了这一焦点。Pi Durable 是一个独立的框架,面向任何需要长时间运行、可从多个客户端访问并挺过崩溃的智能体应用,编码智能体也在其中。它与编码智能体共享 pi-ai 模型层,Earendil 说来自 Pi Durable 的经验会回流到 Pi 中。

Pi Durable 崩溃后如何恢复?

运行的每一步都是一个任务,在继续前进之前先存储一个检查点。当一个新进程打开同一个存储并调用 resume() 时,它会找到未完成的任务,并从各自的最后一个检查点继续。被切断的模型请求会重新发送,部分回答会保留并标记为已中止。工具调用只有在工具声明了 replay: safe 时才会重跑;否则模型会收到一个结果,说该工具被打断、可能已部分执行。requestId 让一次提交在多次重试间精确一次。

Pi Durable 能运行在哪里?

任何有 JavaScript 运行时的地方。它的 SQLite 和 JSONL 存储代码不使用 Node API,因此加一个小小的适配器就能跑在 Bun 上或 Cloudflare Durable Object 里面。工具代码在 harness 进程里运行,但工具通过执行环境触达文件和 shell;自带的是本地 Node,有了你自己的远程执行环境,文件和 shell 可以住在另一台机器上。同一时刻一个进程拥有给定的存储,没有跨进程锁,其他客户端附着到那个进程上。


原文链接: Pi Durable: An Agent Harness That Survives Its Own Crashes

汇智网翻译整理,转载请标明出处