Pi 全面指南:Agent 层级、Durable 原理、接入方式与 Hermes 对比
原始资料:Pi Durable 发布说明、Pi 官方文档、Hermes 原始入口(非官方)。Hermes 的技术核验采用 Nous Research 官方文档 与官方仓库。Nous Research 的官方域名说明明确指出,.org 等相似域名不属于官方;安装入口使用 hermes-agent.nousresearch.com。
AI 参与说明(Agent:Codex):本文依据官方文档、Pi
v1.5.0与 Hermesv0.23.0的公开源码整理,核验日期为 2026-11-08。架构分层与选型建议是本文的工程归纳。执行入口为 Codex Desktop;完整模型标识与 reasoning effort 未取得可核验的任务级运行记录。文中分别注明实际运行的无模型实验与仅按接口核对的示例。
Pi 同时提供可以直接使用的 Coding Agent 和可以嵌入产品的开发包;Pi Durable 则提供另一套面向长期运行应用的 harness。Hermes 更接近已经装配好记忆、Skills、消息入口与定时工作的助手产品。选择它们之前,先确定需要的是现成助手、一个开发 Agent,还是自己产品里的运行基础。
| 英文术语 | 中文名称 | 简要解释 |
|---|---|---|
| Agent Loop | 智能体循环 | 模型提出工具调用,程序执行并反馈,模型据此继续工作 |
| Harness | 运行支撑层 | 组织模型、工具、上下文、会话与执行过程的软件 |
| AgentSession | 智能体会话对象 | Pi Coding Agent 提供的完整会话入口,包含资源、工具和上下文管理 |
| Durable Execution | 持久执行 | 把任务阶段与恢复所需状态保存下来,进程重启后继续调度 |
| Execution Environment | 执行环境 | 文件读写和命令真正发生的地方,例如本机、容器或远程机器 |
| ADE | 智能体开发环境 | 本文指整合项目、Agent、编辑器与任务界面的开发工作台;不是 Pi 定义的协议 |
| RPC | 远程过程调用 | 让一个程序通过协议控制另一个进程或服务 |
| MCP | 模型上下文协议 | 让 Agent 接入外部工具和资源;要区分客户端与服务端方向 |
| ACP | 智能体客户端协议 | 让编辑器与 Agent 交换会话、消息、工具活动和权限请求 |
| Idempotency | 幂等性 | 同一操作重复请求时,不重复产生业务效果 |
1. 先把产品与角色层级分开
Pi Coding Agent is an application and an embeddable harness. Pi Durable is a separate durable harness. 两者共享 pi-ai,Pi Durable 没有把 Coding Agent 的全部会话、CLI 和扩展原样包起来。Pi 包清单、Durable 依赖
| 层级 | 解决的问题 | Pi 中对应的部分 | 宿主还需负责什么 |
|---|---|---|---|
| 模型访问 | 如何对不同 Provider 发请求、处理流与 Tool call | @earendil-works/pi-ai | 业务上下文、工具执行和整个产品 |
| Agent Loop | 如何执行工具、反馈结果、继续推理及取消 | @earendil-works/pi-agent-core | 会话持久化、资源、界面与运行环境 |
| 完整开发会话 | 如何读写项目、维护 Session、加载扩展、压缩上下文 | @earendil-works/pi-coding-agent 的 SDK | 产品界面、用户隔离、部署与权限策略 |
| 终端应用 | 开发者直接交互、观察和干预任务 | 同一包的 pi CLI | 项目与模型配置,必要的系统隔离 |
| 持久应用运行 | 多个 Conversation、Task、Document 和恢复调度 | @earendil-works/pi-durable,依赖 pi-ai 与 Chord | 业务工具、入口、认证、存储归属、唤醒与运维 |
| 产品工作台 | 项目、编辑器、多 Agent 管理、账号与协作 | 使用上述组件构建的 IDE、ADE 或 Web 应用 | 这一层通常由接入者实现 |
这张图表达主要依赖与嵌入选择,不代表每层都必须安装:
flowchart TB
User[人 / 外部程序] --> CLI[Pi CLI]
Host[IDE / ADE / 自建产品] --> Session[pi-coding-agent SDK]
CLI --> Session
Session --> Core[pi-agent-core]
Core --> AI[pi-ai]
Session --> AI
App[长期运行的业务应用] --> Durable[pi-durable]
Durable --> AI
Durable --> Chord[Chord 文档状态与上下文]
AI --> Provider[云端或本地模型服务]
“Pi 是 Agent 还是框架”取决于使用哪一部分。直接执行 pi,使用的是 Agent 应用;在程序里调用 SDK,复用的是同一套开发会话;只使用 pi-ai,还需要自己组织工具执行与循环。
本文以 Pi v1.5.0 和 Hermes v0.23.0 为源码基线。Pi npm 包名采用 @earendil-works/,Node.js 要求为 >=22.19.0。旧文章里的包名、MCP 支持和终止事件不能直接套用到当前版本。Pi package.json、Hermes release
2. Pi 实际怎样完成一项工作
假设输入是“修复这个项目的失败测试”。Pi 加载项目上下文、可用工具和历史消息,把请求交给模型;模型返回 read 或 bash 等 Tool call;Pi 校验并执行,追加 Tool result,再请求模型。模型可能继续编辑、运行测试,最后给出答复。模型选择动作,宿主程序执行动作,二者各有边界。工作原理、Agent Loop 源码
sequenceDiagram
participant U as 人 / 宿主
participant S as AgentSession
participant M as Provider
participant T as Tool / Execution Environment
U->>S: Prompt 与工作目录
S->>M: 上下文与工具定义
M-->>S: Tool call
S->>T: 校验后执行
T-->>S: 结果或错误
S->>M: 追加 Tool result
M-->>S: 后续 Tool call 或最终答复
S-->>U: 文本、工具活动与最终状态
会话记录、上下文与运行状态不同
Session 默认保存为 JSONL,记录形成可分支的树。/tree 可以回到历史节点继续,/fork 可以建立新会话;送给模型的上下文只选取当前分支,并应用 Compaction。完整历史留在会话记录里,不会每次全部发送。Session 格式、Compaction
这解释了两个限制:分支切换不撤销已经写入的文件;Compaction 用摘要替换部分模型上下文,会损失细节,不能视为无限、无损的记忆。pi -c 能继续一个保存的会话,也不意味着中断的任意 Shell 命令会自动安全重跑。
四种定制手段怎样选
| 机制 | 适合放什么 | 一般何时选择 |
|---|---|---|
Context Files,例如 AGENTS.md | 项目结构、命令、稳定约束 | 希望每次在该项目工作都知道 |
| Prompt Templates | 可重复的审查或分析请求 | 输入结构固定,仍由人发起 |
| Skills | 按需读取的专业流程、资料与脚本 | 信息较多,只有匹配任务时才需要 |
| Extensions | 工具、命令、事件钩子、权限逻辑、界面 | 需要执行程序或改变运行行为 |
Pi Packages 负责分发这些资源。Skill 文本能指导工作,Extension 代码能改变程序行为;二者都不能仅凭描述获得宿主没有开放的工具或系统权限。Skills、Extensions、Packages
默认 Pi 没有固定的 Subagent 编排或 Plan mode;这些工作流可以通过 Extension 或 Package 实现。因此,评估“Pi 能不能多 Agent 分工”时,要继续核对具体扩展的委派、并发与结果交付方式。项目说明
3. 从终端开始使用 Pi
在 Node.js 满足要求的环境中,安装本文固定版本:
npm install -g --ignore-scripts @earendil-works/pi-coding-agent@1.5.0
pi --version
pi --help
进入自己的项目目录后执行 pi,在交互界面用 /login 配置 Provider,用 /model 选择模型。Pi 支持 API Key、部分订阅登录及本地模型服务;可用登录方式受 Provider、账户和版本限制。登录一个模型 Provider 不等于购买 Pi 云端运行服务。Quickstart、Providers
第一次可先关闭扩展、MCP 和 Skill 加载,把模型可调用的工具限制为文件读取与搜索:
pi --no-extensions --no-mcp --no-skills --no-prompt-templates \
--no-approve --tools read,grep,find,ls -p \
"Explain the repository structure and test commands. Do not modify files."
单独的 --tools read,grep,find,ls 会保留已连接的 MCP 工具,因此这里同时关闭 MCP;--no-approve 表示本次不信任项目资源。这个配置收窄了模型的工具入口,Pi 仍以启动账户的权限运行,工作目录也不会限制读取范围。处理陌生项目时,系统级隔离仍需由容器、VM 或其他 Sandbox 提供。CLI 工具选择、Security
确认调查结果后,可以交互完成一个范围较小的修改,检查真实 diff 和测试结果。常用的会话操作是:
pi # 交互工作
pi -c # 继续最近的会话
pi -r # 选择历史会话
pi --no-session -p "Explain this folder"
--no-session 只控制会话是否保存,不关闭文件工具、网络或资源加载。工具清单与 Extension 加载设置也不能替代进程隔离。CLI、Security
本次在 Node.js 26.0.0 中安装固定版本 1.5.0,已运行 pi --version、离线且使用独立配置的 --help,并用真实 pi-agent-core 配合模拟模型完成“提出 double(21) → 执行工具得到 42 → 第二轮答复”的验证:两次模型接口调用、一次工具执行,最终发出 agent_end。这些检查没有调用真实 Provider;可复制的无 API Key 循环实验见框架架构文档,其中代码保留其 1.1.0 基线。
MCP 已经是当前 CLI 的内置能力
Pi 1.5.0 的 CLI 以 builtin Extension 提供 MCP,支持 stdio 与 Streamable HTTP;旧 HTTP+SSE transport 不在支持范围内。常见配置文件为用户级 ~/.pi/agent/mcp.json 与项目级 .pi/mcp.json,也可通过交互命令管理。Tool 可直接暴露、按需发现,或通过 Codemode 调用。MCP 文档、MCP 包
这里的方向是“Pi 作为 MCP Client 调用外部工具”。它没有因此自动成为其他 Agent 可调用的 MCP Server。SDK 默认也不会装配 CLI 的 MCP、Codemode、Tool Search,需要宿主显式加载相应 Extension,并按示例绑定生命周期。SDK 的 Codemode/MCP 示例
4. 本地还是云端:分别看三个位置
The harness, model inference, and tool execution do not have to run on the same machine. 决定部署时,要分别回答:Pi 进程在哪里,模型在哪里,工具操作哪一台机器。Pi SDK、隔离运行、本地模型
| 组合 | Pi / harness | 模型推理 | 文件和命令 | 适合的任务 |
|---|---|---|---|---|
| 常规本地开发 | 笔记本 | 云端 Provider | 笔记本项目 | 人在场的编码、写作、数据处理 |
| 本地推理 | 笔记本或本地服务器 | 本机或局域网模型服务 | 本地项目 | 需要控制模型数据流向,且硬件与 Tool calling 能力合适 |
| 远程开发 | 通过 SSH 登录的 VM | 云端或 VM 可达的模型服务 | VM 工作目录 | 长构建、远端依赖、多人共用开发基础设施 |
| CI / 批处理 | Job 容器 | Provider | Job 的隔离检出 | 单次审查、迁移或报告;任务通常随 Job 结束 |
| 自建在线产品 | Node.js / Bun 应用服务或后台进程 | Provider | 自定义本地、容器或远程工具 | Web/ADE 产品;要自己管理生命周期与用户边界 |
Pi 本身不要求 GPU;使用云端模型时,计算资源主要用于工具和开发任务。选择本地模型才需要另外评估权重、显存、上下文和工具调用能力。Pi 已提供 llama.cpp 接入,但下载模型、运行推理服务和选择可完成任务的模型仍是独立工作。
“在本地运行 Pi”也不意味着所有数据都留在本地。云端 Provider 会收到被纳入请求的项目内容和工具结果,MCP Server 可能接收工具参数;反过来,云端运行 harness 也不要求所有工具都在云端执行。成本应分开计算模型 Token、运行环境、存储和第三方工具;MIT 许可不免除这些服务费用。Provider 接口、Pi License
5. 谁能接入 Pi:看宿主能力,而不是 Agent 名称
接入 Pi 要看宿主具备什么能力。能加载 npm 包的程序、能控制子进程的宿主,以及能对接适配器的编辑器,分别有不同入口。
| 接入者 | 入口 | 得到什么 | 关键边界 |
|---|---|---|---|
| Shell、CI、定时脚本 | 最终文本 | 一次调用,不提供完整双向控制 | |
| 需要进度的程序 | JSON mode | JSONL 事件流 | 不是一个 JSON 对象,也不约束模型答复格式 |
| Python、Go、Rust、桌面后端 | Pi RPC 子进程 | 双向命令、事件、会话管理 | 管理进程、事件、取消、扩展审批与异常退出 |
| TypeScript / Node.js / Bun 产品 | Coding Agent SDK | 直接访问 AgentSession | 与宿主同进程,需明确资源和工具加载 |
| 自己已有 Agent Loop 的应用 | pi-ai | 多 Provider 模型访问 | 保留自己的 Loop,不必重复嵌入一个完整 Agent |
| 需要自定义 Loop 的应用 | pi-agent-core | 状态、工具执行与循环事件 | 自己构建会话存储和产品层 |
| 支持 ACP 的编辑器 | 第三方 Pi ACP adapter | 编辑器原生 Agent 体验 | Pi 原生 RPC 与 ACP 不是同一协议 |
| 另一个 Agent | 宿主注册委派 Tool,再调用上述入口 | 把 Pi 作为子任务执行者 | 工具、权限、上下文与预算需要显式约定 |
CLI Integration、SDK、Core README
自动化与工作台:不要抓取终端画面
pi -p "Summarize the changes"
pi --mode json "Review this repository" > events.jsonl
pi --mode rpc --no-session
JSON mode 完成本次运行后退出;RPC 才持续接收命令。RPC 的标准输入输出每行一个 JSON record,例如发送:
{"id":"request-1","type":"prompt","message":"Explain the test setup"}
prompt 的成功响应表示已受理,不表示工作完成。宿主须继续消费事件;当前版本以 agent_settled 判断 Pi 的自动重试与排队工作已经结束,不能在第一个 agent_end 就关闭进程。JSON mode 的模型失败也不一定反映为非零进程退出码,需要检查事件内容。RPC Protocol、JSON Event Stream
这类接口适合 ADE:工作台负责聊天、Diff、项目和任务界面,Pi 负责内部开发会话。工作台仍要处理 Extension 发起的询问、失去连接后的状态重建、会话切换,以及工具实际作用的工作目录。
TypeScript 产品:从完整 SDK 开始
以下是按 1.5.0 API 核对的完整最小示例;需要已经配置有效 Provider,本次没有用真实模型执行。保存为 embed.mjs,在测试项目安装主包后运行:
npm install --save-exact --ignore-scripts @earendil-works/pi-coding-agent@1.5.0
node embed.mjs
import { createAgentSession } from "@earendil-works/pi-coding-agent";
const { session } = await createAgentSession({ cwd: process.cwd() });
const unsubscribe = session.subscribe((event) => {
if (event.type === "message_update" &&
event.assistantMessageEvent.type === "text_delta") {
process.stdout.write(event.assistantMessageEvent.delta);
}
});
try {
await session.prompt("Explain this repository's test setup.");
} finally {
unsubscribe();
session.dispose();
}
预期会流式输出分析,并按配置保存 Session。默认工厂会发现工作目录资源、读取设置与凭据、开放默认工具。服务端不能直接把这段代码当成多租户隔离方案:应按用户显式提供 resourceLoader、工具、设置、凭据和存储,必要时将每个工作空间放进单独进程或容器。SDK 完整控制示例
IDE、现有 Agent 与适配器
Zed 的 Pi 目录使用社区维护的 pi-acp bridge,把 ACP 接到 Pi RPC。它是具体可用的接入路径,也说明“IDE 支持 ACP”不等于“自动理解 Pi RPC”。Adapter 自身的能力限制也需检查,例如 IDE 传入的 MCP Server 配置不能假定已透传给 Pi。Zed Pi 目录、pi-acp
已有 Codex、Claude Code 或其他 Agent 的宿主,如果允许执行子进程,就可以把 Pi CLI 包装为委派工具;支持 MCP Server 的宿主,也可以调用自己写的 Pi 服务适配器。这是可实现的组合方式,不代表这些产品原生提供“切换为 Pi 后端”的开关。嵌套两个 Agent Loop 会增加 Token、权限和结果判定成本;只需增加一个普通工具时,不必再嵌一整套 Pi。
6. Pi Durable 增加的是什么
普通 Session 主要回答“之前说过什么”;Durable Execution 还要回答“哪个任务已经开始、哪一步提交过、重启后应继续什么”。Pi Durable 通过存储、任务状态机与恢复调度回答后一个问题。Durable README
接入者通常是加载 npm 包的 JavaScript / TypeScript 应用,例如 ADE 后端、桌面应用或团队机器人。它们调用 Harness.open()、安装 Registry,再创建 Conversation、提交输入。其他语言可以调用应用自己提供的 HTTP/RPC 服务;Pi CLI 的 RPC 入口不会自动变成 Durable 服务。把已有 Codex、Claude Code 等进程纳入持久任务,也需要自行定义启动、查询、取消与恢复协议;Durable 不会接管其内部调用栈或已有会话。Public harness surface
| 对象 | 保存什么 | 为什么需要 |
|---|---|---|
| Conversation | Entries、Agent 配置与关联状态 | 一个长期对话及其分支 |
| Entry | 不可变的用户输入、模型答复、工具结果等 | 重建 Transcript 与追溯过程 |
| Document | 有类型的 JSON 状态,例如计划、用量与队列 | 让业务状态和运行状态有共同的提交边界 |
| Task | 输入、阶段、Checkpoint、等待关系与结果 | 恢复模型请求、工具调用与自定义任务 |
| Submission | 一次输入的受理及最终状态 | 重试、查询和等待同一项工作 |
| Registry | 当前进程安装的扩展、工具和 Task 定义 | 把存储里的名称解析为可执行实现 |
默认先将 Commit 写入存储,再对外发布相应状态。一个事务可以同时写 Entry、更新 Document 和创建 Task,使应用状态与该次记录保持一致。业务数据库、外部 HTTP 服务和操作系统文件并不会自动加入这个事务。状态与存储设计
流式预览有明确例外:Partial answer 和工具输出默认约每 100 ms 更新一次视图,这些进度可通过 Deferred commit 先显示、后落盘;默认最晚约 1,000 ms 写入,或在读取该文档的后续 Commit 中一并保存。因此,界面上刚出现的 Token 或工具输出可能随崩溃丢失,不能把逐 Token 预览视为全部已经持久化。Entry、Task 状态转换等不符合延迟条件的变更仍先存储再发布。Deferred commits
flowchart TB
Input[提交 input 与 requestId] --> Admit[Commit<br/>受理记录与待执行任务]
Admit --> Scheduler[调度 Task]
Scheduler --> Step[执行当前 phase]
Step --> Save[Commit<br/>Checkpoint / 结果 / 后续任务]
Save --> View[发布已提交的状态]
Save --> Next{仍有工作?}
Next -->|是| Scheduler
Next -->|否| Done[Submission 完成]
Crash[进程中断] --> Reopen[新进程打开同一存储]
Reopen --> Recover[resume<br/>识别未完成任务]
Recover --> Scheduler
崩溃后具体恢复什么
| 中断位置 | 恢复行为与应用责任 |
|---|---|
| 输入已经受理,客户端没收到响应 | 在同一 Conversation 中用同一 requestId 找回原 Submission,避免重复受理 |
| 模型请求进行到一半 | 中断尝试被记录;按重试策略重新请求,不是从模型内部断点继续采样 |
| Tool 执行中 | 默认视为被中断;明确标为 replay: "safe" 的 Tool 才允许重放 |
| 自定义 Task 的 phase 中 | 从最近提交的 Checkpoint 调度,该 phase 的程序可能从头执行 |
| 正在等待时间或子任务 | 等待条件保存在状态中,重启后重新调度;需要宿主进程或平台负责唤醒 |
| 外部服务已操作成功,本地尚未提交 | 存在不确定窗口;靠外部幂等键、查询、补偿或人工确认处理 |
恢复也有终止条件。1.5.0 的 restartLimit 默认为 5,防止同一阶段反复使进程崩溃而无限重启;模型请求中断另外计入重试预算。Storage 出错会终止 Harness,存储适配器的短暂错误重试和宿主重新打开工作由接入者负责。Failures and Recovery、Errors
Deduplicating a submission does not make every external effect exactly-once. 例如工具已发出邮件,进程在保存成功结果前崩溃;简单重放会再发一封。replay: "safe" 是工具作者对可重放性的声明,不会让邮件系统自动去重。应用需要选择能接受幂等键的外部接口,或存储足够信息以核对已发生的效果。
多会话、多人和长期上下文
多个 Conversation 可并行运行,也可从历史 Entry 建立 Fork。Task 与 Conversation 有 ownership 关系,前台任务的取消可以向子任务传播;后台任务可在当前对话回到 idle 后继续。Subagent 由应用组合 Conversation 与 Task 实现,不是内置固定的多 Agent 角色体系。Forks、Abort and Subagents
Compaction 可以在后台总结旧上下文,在适当的 turn 边界采用摘要,旧记录仍留在存储中。它支持对话长期延续,但模型每次看到的上下文依然有上限,摘要也不是无损记忆。reset / handoff 能开始新上下文,并保留旧记录供应用提供历史检索。Compaction
客户端通过 conversation.watch() 获取当前视图,再接收 Chord operations。1.5.0 应使用这个接口;发布说明中的旧事件接口不能直接照搬。默认视图先给最近 50 条 Entry,增长超过 100 条时裁回 50 条;重连从当前视图继续,通过 ConversationTranscript 的 loadEarlier()、fillGap() 和 resume 补齐历史。entries() 的 Cursor 用于分页持久历史,并不保证重放每次 Token 变化;慢客户端收到的操作还可能合并为最新状态。要做多用户应用,认证、Conversation 访问控制与传输层仍由产品负责。Conversation view、Transcript 实现
可修改的运行时与版本兼容
Durable 的 Extension 可以组合 Tool、系统提示片段、Hook、包装器和 Task。Hook 可检查工具参数、阻止操作或修改结果;这些接口与 Coding Agent 的 Extension/Tool 类型不同,现有 CLI 扩展不能假定原样兼容。Code Mode 还允许用确定性脚本组合工具,通过保存的嵌套调用结果重放并继续;它不保存 JavaScript 栈,也不会把脚本调用的远程服务或 Shell 一并隔离。Extensions、Code Mode
Registry 可以替换同名 Extension。已经开始的调用继续使用原实现,后续调用解析新的定义;重启后则使用新进程安装的代码。状态保存的是名称与数据,不是程序快照。Reload
这使在线调整工具成为可能,也要求维护者处理旧 Task、Document 与新代码之间的兼容。Document 的 version 改变时,由应用提供 migrate(value, fromVersion),按访问惰性迁移;读取只在内存转换,写访问才保存新版本 Base。存储版本较新,或存储版本较旧但没有迁移函数,都会拒绝对应的类型化访问。Task 则有自己的 version 与 migrate(input, checkpoint, fromVersion);缺少定义、版本不适配或迁移失败的未完成 Task 会处于 Blocked 状态,等待可兼容的定义,而不是被自动重置。Document versions、Task scheduler
底层 SQLite Schema 另有顺序迁移;1.5.0 支持到 Schema version 2,比当前程序支持的版本更新时会拒绝打开。应用数据迁移、数据库 Schema 升级与 Extension 热替换是三件事。升级前应固定依赖、备份,并在存储副本上确认旧 Task 和 Document 都能恢复;降级也不能只替换 npm 包。SQLite migrations、Migration runner
7. Durable 应该部署在哪里
Pi Durable 是 npm 库,不是一个替你托管所有任务的云平台。当前有 Memory、JSONL、SQLite、IndexedDB 等存储入口,并提供 Cloudflare Durable Object 的 SQLite adapter;不同环境可通过适配器提供执行能力。Storage、Environment
| 部署形态 | 可行做法 | 必须另外解决 |
|---|---|---|
| 本机长期任务 | Node.js 进程、SQLite 与本机工具 | 睡眠期间不工作;进程重启、持久目录与备份 |
| VPS / VM / 容器 | 服务进程、持久卷、可控工具环境 | 进程监督、存储挂载、网络与密钥、更新兼容 |
| Cloudflare Durable Object | 对应 SQLite adapter 与平台 alarm | 唤醒、运行时限制、远程 Shell/文件工具,而非本机 NodeExecutionEnv |
| 浏览器 | IndexedDB 与浏览器可用的工具 | 页面关闭后没有持续执行者;需配合后台宿主实现长期任务 |
| 无状态 HTTP / Serverless Function | 接收请求后投递到有持久状态与唤醒的执行者 | 不能仅把 SQLite 文件放在临时目录就宣称实现持久执行 |
Node 的 NodeExecutionEnv 可以访问本机文件和 Shell,不能直接搬进没有 Node 文件系统与进程 API 的环境。把 harness 放在 Durable Object、把命令执行放在远程隔离机器,是需要产品自行装配的方案,不是安装包后自动获得一台开发 VM。
一个存储同一时间只能有一个拥有写入权的运行进程;当前没有跨进程锁。可以让多个客户端连接这一拥有者,不能随意让多个副本同时打开同一 SQLite 文件提供高可用。默认 SQLite 使用 WAL 与 synchronous=NORMAL:它的进程崩溃恢复能力也不等于主机断电后最新提交绝不丢失。Storage、宿主磁盘与备份策略共同决定实际耐久性。Storage 约束
官网发布说明中的“run anywhere”和“long conversations”应按这些条件理解。Pi Durable 1.5.0 仍标为 Experimental,API 可在版本间变化;用于重要业务前应固定版本、备份存储,并实测中断恢复与升级。
一个不调用模型的恢复实验
下面的实验实际在 Node.js 26、三个 1.5.0 包上运行过,不需要 API Key。它先把 finish 阶段及其输入 prepared: 7 写入 SQLite,再以退出码 23 结束进程,不调用 close();第二次运行重新打开数据库,从保存的阶段继续。这个例子展示任务调度状态的恢复,而不只是在聊天记录里找回上一次消息。Task 定义与 Checkpoint、持久等待
在一个空的练习目录安装依赖,将后面的代码保存为 recovery.mjs:
npm install --save-exact @earendil-works/pi-durable@1.5.0 @earendil-works/pi-ai@1.5.0 @earendil-works/chord@1.5.0
node recovery.mjs start
# 上一条故意以 23 退出;单独执行下一条,不要用 && 连接
node recovery.mjs resume
import { readFile, writeFile } from 'node:fs/promises';
import { BACKGROUND_CONTEXT as context } from '@earendil-works/chord/context';
import { createModels } from '@earendil-works/pi-ai/models';
import { createRegistry, defineExtension, defineTask, Harness } from '@earendil-works/pi-durable';
import { openNodeSqliteStorage } from '@earendil-works/pi-durable/storage/sqlite/node';
const Job = defineTask({
name: 'demo.job', version: 1,
initial: () => ({ phase: 'prepare' }),
phases: {
prepare: async (_task, runtime, ctx) => {
console.log('prepare');
await runtime.sleepUntil(runtime.now() + 1000,
{ phase: 'finish', prepared: 7 }, ctx);
},
finish: async (task, runtime, ctx) => {
console.log('finish from stored checkpoint:', task.state.checkpoint.prepared);
await runtime.commit(() => ({ status: 'terminal', outcome: {
status: 'completed', result: task.state.checkpoint.prepared * 6,
}}), ctx);
},
},
abort: (_task, runtime, ctx) => runtime.commit(() => ({ status: 'terminal',
outcome: { status: 'aborted' } }), ctx),
});
const registry = createRegistry();
registry.install(defineExtension({ name: 'demo', tasks: [Job] }));
const harness = await Harness.open(await openNodeSqliteStorage('./recovery.sqlite'),
{ models: createModels(), registry }, context);
if (process.argv[2] === 'start') {
const root = await harness.root(context);
const id = await root.commit(tx => tx.createTask(Job, {},
{ ownership: { kind: 'conversation' } }), context);
await writeFile('./task-id.txt', String(id));
harness.resume();
while ((await harness.getTask(id, context)).state.status !== 'sleeping') {
await new Promise(resolve => setTimeout(resolve, 5));
}
console.log('checkpoint persisted; exiting without close()');
process.exit(23);
} else {
const id = Number(await readFile('./task-id.txt', 'utf8'));
console.log('before resume:', (await harness.getTask(id, context)).state);
const settled = await harness.waitForTask(id, context);
console.log('outcome:', settled.state.outcome);
await harness.close(context);
}
首次运行和重启后的实际输出如下;until 是每次运行都会变化的毫秒时间戳:
prepare
checkpoint persisted; exiting without close()
before resume: {
status: 'sleeping',
checkpoint: { phase: 'finish', prepared: 7 },
until: 1794145043229
}
finish from stored checkpoint: 7
outcome: { status: 'completed', result: 42 }
通过标准是第二个进程不再打印 prepare,读取同一个 Task 的 Checkpoint,最终得到 42。waitForTask() 本身会启动调度,因此恢复分支不必另写 resume()。task-id.txt 只保存本实验要查询的任务编号,恢复状态保存在 SQLite 中;产品通常将此编号纳入自己的持久业务记录。重复试验应换一个空练习目录,避免把旧任务混入输出。
另一个使用 Faux Provider 的无网络实验验证了提交去重:关闭并重开同一数据库,在同一 Conversation 重试同一 requestId,返回的 Submission ID 相同,用户 Entry 数量仍为 1。这里没有实测真实模型流中断、Cloudflare 部署、断电或外部副作用;退出码 23 也只是可控地模拟进程结束,不能据此宣称邮件、支付或 Shell 命令恰好执行一次。
8. Hermes:装配完成的长期助手
Hermes Agent 由 Nous Research 开发,采用 MIT 许可,有自己的 Agent Loop、会话存储、工具、记忆和用户入口。它使用哪个模型,与产品叫 Hermes 是两回事;可以连接 Nous Portal、其他 Provider 或兼容端点,并不强制只用 Hermes 系列模型。官方仓库、Architecture
默认运行链路是 CLI、Gateway 或 API 接收输入,创建或取得 AIAgent,组织上下文与模型请求,分发工具,保存结果。Gateway 进一步处理平台身份、Session 路由与消息投递;Cron 发起计划中的 Agent 工作。终端工具支持本地、Docker、SSH、Daytona、Modal、Vercel Sandbox 等后端,但某个后端隔离命令,不代表整个 Gateway、插件和所有工具都在同一边界内。Tools Runtime、Security
“会学习”具体意味着什么
| 机制 | 实际保存与复用的内容 | 边界 |
|---|---|---|
| Persistent Memory | MEMORY.md 的环境与经验,USER.md 的用户偏好 | 有容量限制,由 Tool 写入;模型说“记住了”不等于真的保存 |
| Session Search | SQLite/FTS5 中的历史会话与检索结果 | 找回记录需要检索,不是每次把全部历史放进 Prompt |
| Skills | 可以创建、修改和再次读取的操作流程 | 可复用经验不等于结果已验证;错误步骤也可能被保存 |
| Review / nudges | 提醒模型整理知识、审查并改进已有流程 | 是产品工作流,不是持续在线训练模型权重 |
默认 Memory 在 Session 开始时作为固定快照进入系统 Prompt,中途写入磁盘的更新到下一 Session 才进入这一块,Tool response 可以提供即时状态。可以用 memory.write_approval 与 skills.write_approval 要求先审阅再保存;后台 Review 也会消耗模型资源。Profile 之间应隔离记忆与配置;两个独立 Agent 进程不应共享同一 Hermes home 并发写入。需要共享记忆时,应使用有意设计的外部 Provider。Memory 的快照与写入边界
本地、VPS 与消息平台
在人在场的桌面工作中,可以直接用 Hermes CLI 或 Desktop;希望离开笔记本后继续通过 Telegram、Discord、Slack 等入口工作,通常把 Gateway 放在持续在线的机器上。模型请求、命令执行后端和 Gateway 所在机器仍可分开。Modal 等后端的文件系统快照不保存活进程,也不会自动唤醒已经关机的 Gateway 宿主;执行环境休眠与消息监听进程持续在线是两件事。使用云端 Provider、外部记忆或浏览器服务时,不能照宣传语推断所有数据只留在本机。执行后端与持久化条件
以下为按官方文档核对的安装与入门流程,本次没有在研究环境安装 Hermes 或执行真实消息平台登录。安装脚本跟随当前上游,若要求严格复现 0.23.0,应采用该 tag 对应的源码和依赖流程:
curl -fsSL https://hermes-agent.nousresearch.com/install.sh -o install-hermes.sh
less install-hermes.sh
bash install-hermes.sh
hermes setup
hermes
需要消息入口时,再按 Gateway 指南配置平台凭据、允许访问的用户与服务进程。仅开放 Bot 或 HTTP 端口,不等于实现了多用户权限隔离。Installation、Gateway
9. Hermes 也能接入 Agent、IDE 与 ADE
Hermes v0.23.0 已有三个面向完整 Agent 的程序接入协议,另可直接嵌入 Python AIAgent。它不仅能给人聊天,也能成为另一个产品的执行后端。Programmatic Integration
| 入口 | 面向谁 | 适合什么 |
|---|---|---|
ACP:hermes acp | 已支持 ACP 的编辑器 | 会话、消息流、Tool 活动、权限请求与取消 |
| TUI Gateway JSON-RPC:stdio / WebSocket | 自定义桌面、Web 与 TUI 工作台 | Session、Slash Command、Steering、审批与细粒度事件 |
| API Server:HTTP + SSE | OpenAI-compatible 前端、其他语言、自动化 | /v1/chat/completions、/v1/responses 和有状态 /v1/runs |
Python run_agent.AIAgent | Python 服务与脚本 | 同进程调用 Hermes 核心 |
官方 mcp-serve 插件 | 能连接 MCP 的外部 Agent | 消息桥接和明确允许的部分 Tool;不是完整 Hermes Loop |
Hermes 没有 Pi 的 --mode rpc 标志;相似功能通过自己的协议暴露。OpenAI-compatible HTTP 在这里表示接口格式兼容,背后运行的是带工具的 Hermes,不是一个普通基础模型。只支持文本补全的客户端不会自动获得每个审批、暂停、Steering 或 Session 功能,应按具体协议能力接入。
mcp-serve 尤其容易误解:它是受限的消息和工具桥接面,不是“把 Hermes 全部能力挂成一个 MCP Tool”。先用 hermes plugins install mcp-serve 安装官方插件,MCP Client 再启动 hermes mcp-serve。默认只提供会话与消息桥接;加 --tools 才开放 Web、Browser、Vision、Skills 等白名单工具,终端、文件、代码执行和子 Agent 委派均不在开放范围内。该插件在独立的官方插件仓库维护,不在 Hermes v0.23.0 的 plugins/ 下。v0.23.0 MCP 接入说明、官方插件源码
Hermes 还可以显式启用 Codex App-Server Runtime:把部分 Provider 的实际工具循环与执行边界交给 codex app-server,保留 Hermes 的会话、Gateway、记忆和 Skill review 外壳。它是可选模式;默认 Hermes Loop 中的 delegate_task、memory、session_search、todo 不能直接经这一模式的无状态 MCP 回调使用,后台 Review 则仍可维护记忆与 Skills。这个例子说明“助手产品在外,另一个 Agent runtime 在内”是可行组合;不能反过来推断 Hermes 已内置 Pi 后端。可选 Runtime 文档
10. Pi、Pi Durable 与 Hermes 怎样比较
| 维度 | Pi Coding Agent | Pi Durable | Hermes Agent |
|---|---|---|---|
| 首要交付 | 精简开发 Agent 与嵌入式 Session | 构建长期 Agent 应用的持久 harness | 可直接使用的长期助手产品 |
| 默认交互 | Terminal;也有 SDK、JSON、RPC | 程序 API;入口由应用实现 | CLI、Desktop、Gateway、API、ACP |
| 开发定制 | Extensions、Skills、Packages、SDK | Registry、Tool、Task、Document、Hook | Skills、Plugins、工具与消息适配器 |
| 会话 | JSONL 分支树与 Compaction | 持久 Entries、Documents、Submissions 与 Task 状态 | SQLite 会话、检索、记忆与平台路由 |
| 持久执行重点 | 保存开发会话;人或宿主处理重启 | Checkpoint、恢复、任务等待、重放与 ownership | 产品级队列、API admission、计划任务与状态恢复 |
| 多 Agent | 按 Extension 或宿主组合 | 按 Conversation 与 Task 组合 | 内置委派及面向用户的 Bot 工作方式 |
| 长期记忆 | Context Files、Skills,其他机制按扩展组合 | 提供状态与历史基础,记忆策略由应用设计 | 现成 Memory、Session Search 与 Skills 工作流 |
| 外部工具 | CLI 内置 MCP Client | 自定义 Tool / Execution Environment | MCP Client 与多类内置工具、执行后端 |
| 多端与多人 | 宿主实现 | 共享已提交状态与 watch;产品实现身份权限 | 现成消息平台、会话路由与授权机制 |
| 部署工作量 | 直接 CLI 较低;产品嵌入随需求增加 | 较高,要做应用与运行基础 | 单人助手较低;复杂部署仍需运维 |
Hermes 的恢复能力不能略去
Hermes 0.23.0 已有持久 admission、API 重试记录与计划任务恢复设计。Canonical API 运行中宿主重启、执行结果不确定时,工作保留 unknown 状态,后续同 Session 的排队工作等待显式处理;处理未知状态不会自动重跑原任务。Cron 同样会保留未决执行供操作员复核;在消息发送已认领但结果不确定时,也不会自动重发。它不是只有聊天记录存盘;Pi Durable 也不是唯一能够恢复任务的选项。Hermes API 文档、Cron 恢复
两者的区别在抽象面:Pi Durable 直接把 Task、Checkpoint、事务与 ownership 提供给应用作者;Hermes 主要把这些问题纳入已经设计好的助手、Gateway 和 API 行为。Hermes 文档同样没有给出跨数据库、跨外部服务的全局 exactly-once 保证。需要按具体入口核对去重、排队、恢复与未决状态,而不是给整个产品贴一个“可靠”标签。
按任务选择
以下为工程建议,不是上游的排他性限制:
| 你的目标 | 优先从哪里开始 | 第一个应验证的结果 |
|---|---|---|
| 自己在项目里写代码,喜欢终端与可定制工作流 | Pi CLI | 一次小修改、测试和 diff 能否完整完成 |
| 做开发工作台,想复用成熟的编码 Session | Pi SDK 或 RPC | 流式事件、取消、审批、会话恢复与工作目录正确 |
| 已有自己的 Agent,只想统一模型调用 | pi-ai | 目标 Provider 的流、Tool call 与错误是否正确 |
| 做团队协作或长任务产品,要自己控制恢复语义 | Pi Durable | 杀进程后只继续应继续的任务,副作用不重复 |
| 想尽快拥有跨平台、有记忆和定时工作的助手 | Hermes | 平台身份、Memory/Skills 保存、计划与消息投递正确 |
| IDE 已支持 ACP,希望少写适配层 | Hermes ACP,或评估 Pi ACP bridge | 目标编辑器的权限与 Tool 活动能正确往返 |
| 任务只是固定 API 调用或确定性脚本 | 普通脚本或现有工作流 | 不引入模型也能满足需求时,先保留简单方案 |
Pi Durable 是独立的应用开发入口,可以直接学习。一次 Pi 工作要跑几个小时,也未必需要 Durable:持续在线的进程、隔离工作目录和人工可恢复会话可能已经足够。需要无人值守恢复、多个客户端、持久等待或可编程任务树时,再评估 Durable 的任务模型。
三个具体的接入场景
开发工作台可让浏览器只负责输入、消息流和 Diff,Node/Bun 后端用 Pi SDK;非 JavaScript 后端则管理 Pi RPC 子进程。每个项目明确工作目录与资源,实际开发命令放在受控环境中。验收重点是改动产物、取消、审批和 Session 切换都能正确关联到同一任务,而不是只看到模型输出。
团队 Issue 处理机器人可以用 Pi Durable:Webhook 进入应用后,以 Conversation 内唯一的事件标识作为 requestId,把调查、等待审批和后续操作组织成 Task。重部署后继续调度未完成的工作;向代码托管平台写评论或发起变更时,仍需记录外部标识并检查是否已经成功。Conversation 的去重范围与外部写入的去重范围应分别设计。
跨平台个人助手可以先用 Hermes:Gateway 接收消息,Memory 保存稳定偏好,Skill 保存常用流程,Cron 发起计划工作,再由配置的工具后端执行。若目标是直接使用这些能力,现成产品能减少装配工作;若要重新定义每一步状态、恢复与协作方式,再评估自行构建 Pi Durable 应用。这三种架构是基于上述接口的设计示例,本次未部署它们。
11. 接入前怎样验证关键边界
对 Pi CLI / SDK,先验证工具实际访问的目录、资源加载范围、Provider、取消与会话清理;再验证事件流的最终状态。对 Pi Durable,增加以下有具体结果的检查:
- 提交后立即断开客户端,用同一
requestId重试,应取回同一受理记录。 - 在模型响应与工具执行中分别杀掉进程,检查恢复路径和已保存的中断状态。
- 让工具在外部操作成功后、本地 Commit 前中断,确认应用能去重或识别不确定结果。
- 重启有持久等待的任务,检查宿主唤醒后会继续;宿主离线期间不应声称仍在执行。
- 在测试存储上升级 Task/Document 定义,确认旧任务可以继续或明确停在可处理状态。
- 用第二个身份访问同一 Conversation,确认产品的读取、Steering 和 Tool 操作权限均有效。
对 Hermes,则应分别验证默认 CLI、Gateway、API 或 ACP 的实际使用路径;不能用一次 CLI 成功替代 API 的断线恢复,也不能用命令后端的 Docker 隔离替代 Gateway 和插件权限检查。多用户、多个 Profile 与记忆写入的行为需要独立核对。
Pi 的 Project Trust 是资源加载决策,工作目录是工具默认位置;它们都不是 OS Sandbox。Durable 解决任务状态,Memory 保存经验,MCP/ACP 连接能力,均不自动提供文件系统或网络隔离。无人值守执行应按真实工具和数据流配置独立用户、容器、VM 或受控远程执行环境。Pi Security、Hermes Security
关联阅读
- Pi Coding Agent 入门与社区玩法:更细的终端操作、Session、Skills 与社区扩展。
- Pi Agent 框架架构:包边界、Agent Loop 与接入选择:保留
1.1.0源码基线与无需 API Key 的 Agent Loop 验证。 - Cindy 源码分析:一次用户输入如何进入 Harness:一个工作台怎样分别对接 Pi、Codex 和 Claude Code。
- Rakazo 源码分析:持久 Agent、任务恢复与多端协作:另一个围绕 Pi 与外部副作用构建持久产品的实例。
参考资料与版本
- Pi
v1.5.0:本文的 Pi 发布版基线;官网latest会继续变化。 - Pi Durable
v1.5.0README:Task、恢复、存储、watch 与扩展的完整条件。 - Pi Durable 发布说明,2026-10-01:解释设计动机,不能替代后续发布版 API。
- Hermes
v0.23.0:本文的 Hermes 发布版基线。 - Hermes 官方文档:安装、平台、模型与产品使用入口。