/ By 煎鱼 / #harness #agent #源码解读 / AI 入口

pi 源码解读|pi 是一个怎样的 Agent Harness?

从 pi-mono 的包边界、AgentSession、ResourceLoader、Extension 与 SessionManager 出发,解释 pi 的可编程边界,以及把它放进 Local First Agent OS 时哪些能力仍需自己构建。

“pi 能不能装几个插件,就变成 Hermes 或 OpenClaw 那样的个人 Agent?”

这个问题很容易把 pi 看错。它的终端界面、文件工具和多模型登录都很像一个完整 Coding Agent;而 Extension、Skills 和 Packages 又看起来什么都能接。于是直觉会变成:底座已经有了,剩下只是找插件。

源码和文档给出的边界恰好相反:pi 有意把核心做小,把工作流特有的行为推给扩展、Skill、Prompt Template 和 Package;它默认不内置 MCP、sub-agent、权限弹窗、Plan Mode、to-do 或后台 Bash。pi 的设计说明写得很直白。所以它可以长成一个 Agent OS,却不会因为安装几个扩展自动成为一个。

先说结论:

pi 更适合被理解为“可编程的 Agent Kernel + Coding Agent 参考实现”。它已经负责模型调用、单 Agent loop、工具、上下文加载和会话树;跨渠道、长期记忆、任务调度、评测闭环、跨 Session 协作和 Graph Runtime 仍是上层 Control Plane 的责任。

这不是在说 pi 功能少。相反,恰好是因为它把边界切得干净,才适合当一个需要长期演化的 Agent 系统的底座。

数据快照

  • 最后核验:2026-08-12
  • 源码与文档:earendil-works/pimain 分支及 pi Latest 文档
  • 阅读边界:本文讨论 pi 公开的单进程运行时、CLI 和扩展接口;不比较不同 Agent 的任务成功率,也不把扩展示例等同于 pi 的默认能力
  • 名称说明:npm 包仍使用 @earendil-works/* 命名;下文“pi-agent-core”指该运行时包,不把它和整个 CLI 混为一谈

#先把产品外壳和运行时内核分开

把 pi 当成“又一个命令行 Coding Agent”并不完全错,只是观察位置太靠外。pi-coding-agent 是用户直接运行的程序;它把终端交互、文件工具、资源发现和 Session 接到一起。但当前 SDK 允许应用直接创建 AgentSession,用于嵌入自定义 UI、自动化管道、子 Agent 工具与程序化测试。SDK 的定位已经说明它不只为终端而存在。

可以先用下面这张图建立地图。

图 1:从 pi-mono 包边界看,CLI 只是 Agent Runtime 的一个宿主;本文根据公开包名与 SDK 接口绘制

flowchart TB
  U["终端、Web 或自定义应用"] --> C["pi-coding-agent"]
  C --> S["AgentSession 与 ResourceLoader"]
  S --> A["pi-agent-core\n单 Agent loop"]
  A --> M["pi-ai\n模型与流式协议"]
  A --> T["内置或自定义 Tools"]
  S --> R["Extensions、Skills、Prompts、Context"]
  S --> P["SessionManager\nJSONL 会话树"]
  C --> UI["pi-tui / pi-web-ui"]

图中最重要的是 AgentSession,不是 CLI。它持有 Agent、模型、消息、上下文压缩和事件订阅;调用方既可以 prompt() 等待一次任务结束,也能在流式过程中 steer()followUp()。这意味着“界面”与“Agent 执行”已经分开,CLI 只是其中一个调用方。AgentSession 的公开接口可以直接核对这些职责。

这也是本文用 Kernel 形容 pi 的原因:Kernel 不是完整操作系统,更不是最终产品;它给上层提供一组稳定而受约束的执行能力。这个类比只用于说明分层,不意味着 pi 提供了 Linux 那样的进程隔离、驱动模型或安全边界。

#pi-agent-core:单 Agent loop 是内核,不是工作流引擎

Agent 最小闭环并不神秘:模型读到当前消息和工具描述,决定生成文本还是工具调用;工具结果写回消息;只要仍有工具调用,循环继续。

// 分析性示例:展示职责,不是 pi 源码原样摘录
while (agent.hasNextStep()) {
const reply = await model.stream(messages, tools);
messages.push(reply);
for (const call of reply.toolCalls) {
messages.push(await tools.execute(call));
}
}

pi 的 AgentSession 把这个循环包进更高一层的运行状态:消息历史、模型选择、thinking level、压缩、流式事件与中止。对于一个“读代码—修改—跑测试—继续修”的任务,这就是足够关键的底座。你不必先造一次模型流式协议、工具结果格式和取消语义,才能写上层行为。

但它的边界也在这里。单个 Agent loop 即使会反复调用工具,也仍然不是 Graph Runtime:它没有把 Planner、Coder、Reviewer、Judge 表达成一等节点,也没有为节点间共享状态、条件边、fan-out/fan-in、checkpoint 和恢复提供通用的图语义。

因此,下面两件事不能混为一谈:

  • 在一次 pi Session 内,用 Extension 在测试失败后追加一条“分析失败原因”的消息;
  • 运行一张持久的“规划—并行实现—验证—人工审批—回滚”执行图。

前者是改造 loop,pi 已经提供入口;后者是编排多个 loop,需要上层 Graph Controller,或交给专门的图框架。把 pi Agent 当作图里的一个 Node 通常很自然;把单 Agent loop 本身叫成完整 Graph Engine,就把“可以组合”误写成“已经提供”。

#pi-ai:统一模型层是路由插口,不是路由策略

pi 的模型层承接不同供应商的鉴权、模型定义和流式调用。当前文档列出的内置或可配置路径覆盖订阅型 Provider、API Key Provider、Cloud Provider、llama.cpp,并支持通过 models.json 配置 Ollama、LM Studio、vLLM 等兼容端点。Provider 文档给出的重点是“怎样接入”,不是替你决定“此刻该用哪个模型”。

如果需要非标准 API、企业 OAuth 或代理,Extension 可以调用 pi.registerProvider() 注册完整 Provider;自定义流式实现最终仍围绕 pi-aistreamSimple 协议组织。Custom Providers 文档给出了这一层的接口和边界。

这让一个 Local First 系统可以把本地与远端模型接到同一个 Agent Runtime;但“根据任务难度、数据敏感度、预算和延迟选择 Qwen、Codex 或 Claude”仍是一条路由策略。它可以放在 Provider 之前、Extension 里,或放在上层 Control Plane,取决于策略是否要跨 Session、跨渠道或被审计。pi 提供的是插槽,不是默认的策略委员会。

#ResourceLoader 和 Extension:可编程性真正从这里开始

如果只看 pi.registerTool(),容易把 Extension 理解成“给模型多塞几个函数”。实际接口宽得多。

createAgentSession() 通过 ResourceLoader 提供 Extension、Skill、Prompt Template、Theme 和 Context File;默认 Loader 会发现全局与项目内资源。SDK 的加载说明明确列出了这一点。对 Coding Agent 来说,这意味着同一个 loop 可以根据项目加载不同的工具、工作约束与交互层,而不用修改 Agent Core。

Extension 的工厂函数拿到 ExtensionAPI 后,至少可以订阅事件、注册工具、命令和快捷键;文档还提供 UI、动态工具、Provider 与 Session 相关能力。Extension API 概览是最可靠的接口清单。

表 1:pi 的扩展点与 Agent OS 上层能力的分工;pi 列基于 2026-08-12 的公开 SDK 与 Extension 文档

需求pi 已提供的插口仍需要由系统定义的部分
增加工具customToolspi.registerTool()工具目录、权限模型、凭据治理与审计
改变单 Agent 行为生命周期事件、消息注入、工具拦截成功标准、重试预算、评测标准与终止协议
接入本地/私有模型models.jsonregisterProvider()路由规则、成本/延迟策略与 fallback
加载项目知识Skill、Prompt、Context File、AGENTS.md知识更新、冲突处理、长期记忆的提取与遗忘
改变终端体验命令、快捷键、TUI UI APIWeb/消息渠道、多用户身份和组织权限
管理会话SessionManager 与 Session Runtime跨 Session 检索、用户画像、事实校验与保留策略

以“测试失败后自动进入反思”为例,Extension 能在工具事件后检查结果,并追加新的约束或工具;官方示例也展示了危险 Bash 先经过确认的拦截方式。工具事件和拦截示例说明了这个方向。

但“能拦截”不是“已经安全”。一个真正的权限系统还要定义:谁能授权、授权多久、哪些对象可写、网络和文件系统的隔离在哪里做、日志能否证明实际副作用。pi 的扩展能力是实现这些策略的接缝,不应被误解为默认完成了安全治理。

#Session 是可分叉的工作记录,不是长期记忆

pi 的会话不是一段简单的聊天文本。当前格式把 Session 存成 JSONL,条目以 id / parentId 形成树,因此可以在同一文件中切换分支;SDK 同时公开 SessionManager.create()open()inMemory()forkFrom() 等入口。Session File FormatSDK 的会话管理章节都能验证这点。

这对 Coding Agent 特别有用:一个错误的调试方向可以 fork,新的路径不用重新丢掉所有已有上下文;Context compaction 也能把长会话缩到模型还能继续工作的范围内。

不过这仍然不等于“长期记忆”。Session 保留的是一次执行的原始过程和分支关系;长期记忆至少还要回答另外几件事:

  • 什么信息值得从一段 Session 提炼为稳定事实;
  • 哪些事实属于项目、用户、团队或某个渠道;
  • 新证据与旧记忆冲突时,谁可以覆盖、怎样保留依据;
  • 检索到的记忆怎样标注来源、时效与置信度;
  • 删除、过期与隐私边界怎样处理。

把 Session 文件直接当知识库,短期很省事,长期会得到一个很大的“以前说过什么”的文件夹。它能帮助回放,却不能自动回答“现在还应不应该相信”。

#从 pi 向上长出 Agent OS,缺的是 Control Plane

把前面的模块放回最初的问题,答案就很清楚:pi 并不缺一个新的 Prompt,也不缺更多 Tool;它缺的是一个跨多个执行单元做决策的上层。

图 2:把 pi 放进 Local First Agent OS 时,pi 是执行层,Control Plane 负责跨任务语义;本文解读

flowchart TB
  CH["TUI、Web、飞书、Telegram、Cron"] --> CP["Control Plane\n身份、任务、权限、预算"]
  CP --> G["Graph Controller\n路由、并行、重试、停止"]
  G --> PI1["pi AgentSession"]
  G --> PI2["pi AgentSession"]
  PI1 --> TOOLS["Tools / MCP / Sandbox"]
  PI2 --> TOOLS
  CP --> MEM["Memory\n提炼、检索、遗忘"]
  CP --> EVAL["Judge / Eval / Observability"]
  MEM --> PI1
  EVAL --> G

这张图没有试图把所有东西都塞进 Extension。原因很简单:一旦 Scheduler、渠道身份、跨任务 Memory、评测历史和 Graph 状态都只存在于某个终端 Session 的扩展里,它们就很难独立观察、恢复和治理。

一个较稳妥的分层是:

  • pi 负责执行:模型交互、工具调用、单任务上下文、项目资源发现和可扩展 CLI/SDK;
  • Graph Controller 负责编排:创建哪些 Session、哪些可以并行、结果怎样汇总、失败回到哪里、何时要求人工确认;
  • Memory Layer 负责事实:从记录中提炼可复查的记忆,而不是把全部消息再次塞回 Prompt;
  • Eval 与 Policy 负责停止和边界:测试通过不一定代表任务完成,模型说“完成”也不应替代权限和验收;
  • Channel Layer 负责产品入口:把消息、定时任务、Web UI 或设备事件规范化成可以提交给 Control Plane 的任务。

这也是为什么“pi + 插件 = Hermes”不够准确。插件当然能实现很多单点能力,甚至可以做 sub-agent、MCP 或权限门;而 pi 的 Package 机制也确实能把 Extension、Skill、Prompt 和 Theme 打包分发。但一个 Agent OS 的困难不在于把能力装进去,而在于这些能力怎样共享身份、状态、权限、预算和完成证据。

#源码阅读顺序:先追执行边界,再追产品能力

如果目标是做二次开发,建议不要从 CLI 的命令列表开始。更高效的顺序是:

  1. 先读 pi-agent-coreAgentSession:确认一次模型回复、工具调用、消息追加、取消与压缩怎样构成单任务 loop。
  2. 再读 ResourceLoaderDefaultResourceLoader:确认项目的 Extension、Skill、Prompt 与 AGENTS.md 在什么范围、什么顺序加载。
  3. 然后读 Extension 事件与 registerTool():找清可插入的生命周期位置,以及工具、Provider、UI 与 Session 的实际能力。
  4. 接着读 SessionManager 的 JSONL 树与 Session Runtime:区分“原始会话持久化”“分支”和“长期 Memory”。
  5. 最后才设计 Graph Controller、Memory、Policy 和 Eval:这些不应由某个局部 Hook 的方便程度反推架构。

这个顺序的好处是,每一步都先确定 pi 已经承诺的接口,再决定自己的系统要在哪一层加能力。否则很容易先写出一个“自进化 Extension”,最后才发现跨 Session 的状态、失败恢复和评测数据都无处安放。

#总结:pi 不是成品 OS,但正因如此值得作为底座

pi 的价值不在于替你打包了多少“聪明功能”,而在于它把最难复用、最容易被 CLI 外壳遮住的部分做成了可调用的运行时:AgentSession、工具、模型层、资源加载、Extension 和树状 Session。

它能支持 Loop engineering:你可以改变单 Agent 在何时观察、验证、重试或停止。它也能成为 Graph engineering 的执行节点:上层把 Planner、Worker、Judge 和人工审批组织起来,再让每个节点使用 pi 的 loop 和工具。但这两句话都不等于 pi 已经带着完整的 Graph、Memory、Channel、Scheduler 和治理系统。

所以,若目标只是尽快得到一个个人助手,选择已经把渠道、任务和记忆打包好的产品往往更省力;若目标是构建一个可本地部署、可评测、可演化的 Agent OS,pi 的克制反而是一种优势。下一篇可以继续沿着这条边界,具体追 ResourceLoader 与 Extension 如何把“外部能力”接进一次 Agent Session。

#参考资料

Share this post

Mermaid 图表
100%