返回文章列表

Pi 技术分析系列 · 开篇 · 下篇
分析日期:2026-10-17

认识 Pi(上):整体架构与任务执行介绍了 Pi 的整体架构,并沿一次任务梳理了启动、模型请求、工具执行和事件收尾。三个核心对象的分工由此明确:Agent 驱动运行,AgentSession 组织编码会话策略,SessionManager 保存历史并构建上下文。

本篇接着讨论这些对象怎样支撑持续工作。历史需要选择和转换,才能成为下一次模型请求的材料;工具、扩展和模型适配需要接入已有流程;任务跨越进程生命周期时,还需要可保存的执行状态与恢复规则。

我们从“历史怎样成为上下文”开始,再把能力接入、持久运行和验证体系连接起来,最后归纳上下篇呈现出的设计取舍,并给出后续专题的问题地图。

1. 历史如何成为上下文:消息、分支、投影与压缩

1.1 三种表示服务于三个问题

完整历史需要解释“发生过什么”;运行时消息需要表达“应用现在处理什么”;模型请求需要表达“模型这次应该看到什么”。Pi 为这些问题保留了不同表示。

SessionEntry 是历史记录。除消息外,它还可以保存模型切换、思考等级、压缩、分支摘要、标签和上下文编辑等信息。记录带有 id 与 parentId,因此历史能形成树,而不只是一个不断增长的消息数组。

AgentMessage 是运行时消息。它包含模型消息,也允许应用扩展自定义类型。编码助手可以记录命令执行、压缩摘要和自定义消息,而不要求每一种应用概念都直接成为供应商协议的一部分。

Message 是 ai 的统一模型消息契约,包含 system、user、assistant 和 toolResult。供应商适配器再把这种统一表示转成实际 API 请求。3

flowchart TB
    H[SessionEntry 历史树] -->|选择当前分支| P[会话投影]
    C[压缩记录与上下文编辑] -->|影响投影结果| P
    P -->|生成| A[AgentMessage 数组]
    A --> T[transformContext]
    T --> CONV[convertToLlm]
    CONV --> M[统一 Message 数组]
    M --> API[供应商请求表示]

图 1:历史到模型请求的数据转换。箭头表示数据加工;它不意味着原始历史被每一步直接覆盖。

1.2 分支与压缩改变的是请求视角

SessionManager 用 JSONL 保存记录,并用父子关系和当前叶节点确定活动分支。构建上下文时,系统先沿当前分支选择记录,再根据压缩和上下文编辑生成消息投影。

flowchart LR
    R[共同历史] --> Q[某次用户输入]
    Q --> A[回答 A]
    Q --> B[回答 B]
    B --> N[当前后续消息]
    N --> V[当前上下文沿 B 分支构建]

图 2:会话树的示意。图中的边表示历史继承关系;选择 B 分支后,请求上下文不会自动混入 A 分支的回答。

压缩会引入摘要和保留范围,使后续模型请求不必携带全部旧消息。历史文件仍然保留用于追溯的记录。上下文编辑也通过对应记录影响投影,而不是简单把当前内存数组当作唯一事实。

因此,压缩不只是“从数组前面删除一些消息”。它同时涉及完整历史、当前分支、被保留内容和发给模型的摘要,必须维持这些表示之间的对应关系。

当前编码会话在每次请求前,通过 prepareRequest 从 SessionManager 构建正式投影,并保留当前可执行工具集合。随后通用循环应用上下文转换,再调用模型。这让请求所见内容有一个明确来源,而不是任意沿用某次 UI 更新后的数组。2

1.3 转换边界怎样隔离应用概念

以下代码节选来自 streamAssistantResponse(),保留了从运行时消息到模型请求的关键顺序:

let messages = context.messages;
if (config.transformContext) {
    messages = await config.transformContext(messages, signal);
}

// Convert to LLM-compatible messages (AgentMessage[] → Message[])
const llmMessages = await config.convertToLlm(messages);

const llmContext = normalizeContext({ messages: llmMessages });

来源:Agent 循环中的上下文转换。

transformContext 仍在应用消息层工作;convertToLlm 则负责把应用消息映射为模型消息。例如,命令执行记录可以转成用户消息,压缩摘要可以转成带有说明文字的上下文,明确排除的内容不会进入请求。

工具也有两种对应表示:运行时保留可执行工具,消息中的 system 记录保存模型可见的工具声明及变更。declareToolChanges() 用声明差量表达工具集合变化,使“模型被告知可以调用什么”和“宿主能够执行什么”有可追踪的联系。

这一层设计的价值是把应用历史与供应商协议隔开。代价是转换本身必须得到验证:错误过滤可能使模型丢失信息,错误排序可能破坏调用和结果的关系,错误声明可能让模型请求一个宿主不能执行的工具。

2. 能力如何接入:工具、扩展、模型与交互界面

2.1 工具连接声明与真实动作

一个工具同时面向模型和宿主。模型看到名称、说明和参数 schema;宿主掌握 execute()、取消信号、执行结果以及显示所需的信息。

flowchart TB
    DEF[名称、说明与参数 schema] -->|声明给模型| M[模型生成 toolCall]
    M --> FIND[按名称查找工具]
    FIND --> VALID[准备和校验参数]
    VALID --> BEFORE[beforeToolCall]
    BEFORE --> EXEC[execute]
    EXEC --> AFTER[afterToolCall]
    AFTER --> RESULT[工具结果消息与事件]

图 3:通用 Agent 的正常工具路径。查找失败、参数无效、hook 阻止和取消会走对应错误分支,未在图中展开。

以 read 为例,工具实现需要解析路径,检查可读性,区分文本与图片,对文本处理偏移量和行数,再限制返回内容的大小。图片还需要根据模型能力与图像处理结果生成对应内容。对模型来说,它得到的是统一工具结果;具体文件系统操作仍由工具实现承担。4

这种划分让执行循环可以调度不同工具,但也说明工具契约不能只包含一个函数名。参数描述影响模型如何发起调用,schema 决定宿主如何解释输入,结果形态影响下一轮模型能够理解什么。

2.2 Skill、模板与 Extension 改变的层次不同

机制

主要载体

对运行的影响

Skill

指令文本与相关资源

向模型提供某类工作的知识或步骤;/skill:name 可展开对应内容

Prompt Template

可展开的提示词文本

把命令与参数组织成输入内容

Extension

宿主执行的代码

注册工具、命令和模型能力,处理事件,改变输入、上下文或交互

Skill 影响模型如何工作,Extension 则可以直接参与执行系统。两者都能改变最终行为,但作用位置和生命周期不同。例如,一份 Skill 可以要求模型遵循某种检查步骤;一个 Extension 可以在工具执行前检查调用并阻止它。

ResourceLoader 负责发现和加载资源,ExtensionRunner 负责调度扩展处理器。发现资源与执行扩展分开,使资源来源、重新加载和运行期事件可以分别处理。5

2.3 MCP 扩展工具来源,Code Mode 改变调用组织方式

MCP 提供连接外部工具服务的协议。Pi 的 MCP 能力负责建立连接、发现工具、发送调用,并把返回内容转成模型可以消费的文本或图片等形式。对 Agent 循环而言,这些外部能力仍然可以表现为工具。

Code Mode 关注的是另一件事:让模型生成一段 JavaScript,由脚本组合工具调用。脚本运行在 QuickJS 环境中,通过注入的工具能力完成工作。嵌套调用的中间结果不必逐条进入模型上下文,模型主要接收脚本输出和返回结果。6

例如,脚本可以读取几个配置文件,筛选所需字段,再返回一份汇总。这样一次模型决策能够组织一组程序化操作。相应的复杂度进入脚本执行与嵌套工具管理,包括超时、取消、错误、结果大小和调用可见性。

因此,MCP 和 Code Mode 可以协作:前者提供工具来源,后者组织调用过程。它们不会自动替代会话层对历史、上下文和运行状态的管理。

2.4 统一模型接口怎样容纳供应商差异

编码会话注入 Agent 的 streamFn 会调用 ModelRuntime.streamSimple()。模型运行时准备认证和请求参数,再调用相应 Provider。独立使用 ai 时,也可以通过 createModels() 创建模型注册与调用入口。

flowchart TB
    L[AgentLoop] -->|调用 streamFn| R[ModelRuntime 或 Models]
    R -->|选择模型与准备认证| P[Provider]
    P -->|统一消息转协议请求| API[供应商 API 实现]
    API -->|流式响应转统一事件| R
    R -->|AssistantMessageEventStream| L

图 4:模型接入的请求与响应路径。ModelRuntime 是编码会话中的模型管理实现;其他宿主可以使用 Models 接口组织自己的接入。

Provider 将模型目录、认证和 API 实现组织在一起。API 层负责实际协议,包括请求参数、传输、内容块与流式事件之间的映射。以上层统一消息和事件为边界,Agent 循环就不必直接解析每一家供应商的响应格式。7

统一接口也不意味着所有模型完全相同。模型能力、思考等级、输入限制和传输差异仍然需要在相关层表达。这里的设计问题,是把共同行为放在哪里,并让不可消除的差异有清楚的位置。

2.5 界面消费事件,而不是承担模型循环

交互模式把用户输入交给会话,并订阅会话事件。文本增量更新回答组件,工具事件更新进度与结果,界面再请求 TUI 渲染。TUI 提供组件、布局、输入处理和屏幕更新能力。8

Print 模式可以只输出最终文本;JSON 模式可以输出事件;RPC 模式允许宿主程序控制会话。它们对显示和控制的要求不同,但都可以建立在会话能力上。

这使交互方式能够变化,而运行规则有相对集中的实现位置。与此同时,流式事件也成为公开接口的一部分:它们的含义、顺序和完成边界会影响宿主怎样展示进度,以及什么时候认为任务已经结束。

3. 持久运行体系:Durable、Chord 与 Env

3.1 保存对话与恢复工作是两个问题

假设工具已经执行了一个外部动作,但程序在记录结果前退出。重启后,系统可能知道模型曾要求调用这个工具,却不知道动作是否完成。

会话历史能够帮助恢复上下文,但要恢复未完成工作,还需要保存任务意图、执行阶段、检查点和恢复规则。这是 Durable 所关注的问题。当前版本将其标记为实验性 API;它拥有独立于 CLI Agent 循环的运行机制。9

3.2 持久运行中的几个基本概念

概念

含义

为什么需要

Harness

打开存储并运行会话与任务的运行时

统一管理调度与生命周期

Conversation

一条会话及其不可变历史记录

确定输入、上下文和工作的归属

Submission

提交给会话的输入或写入请求

表示一个已接收、可以等待结果的请求

Entry

一条不可变历史记录

保存用户输入、模型回答、工具结果等事实

Document

在提交中修改的类型化 JSON 状态

保存配置、输入队列、运行状态与业务状态

Task

带检查点的持久状态机

让工作能够等待、继续和恢复

Commit

原子提交边界

把相关记录、文档变更和任务状态一起保存

一个输入通常会驱动生成任务。生成任务请求模型,必要时创建工具任务并等待结果,然后交接给后续生成工作,直到输入得到回答。

flowchart TB
    SUB[接收 Submission] --> C1[提交用户 Entry 与运行状态]
    C1 --> G1[GenerationTask<br/>准备并请求模型]
    G1 --> C2[提交 assistant Entry 与工具任务]
    C2 --> T[ToolTask<br/>执行工具工作]
    T --> C3[提交工具结果与任务状态]
    C3 --> G2[后续 GenerationTask]
    G2 --> C4[提交最终回答并完成 Submission]

图 5:包含一次工具调用的 Durable 运行示意。箭头表示工作推进;提交节点标出持久状态边界。运行过程还可以保存流式进度,并非只有图中的最终结果才会提交。

GenerationTask 从 prepare 阶段开始,在明确阶段中处理上下文、模型请求和后续工作。工具调用则成为独立 ToolTask,具有自己的状态与归属。等待工具完成可以表现为可保存的任务状态,而不只是留在进程内存中的一个 Promise。10

3.3 恢复依赖检查点与重放约定

工具执行前,Durable 会先提交执行意图,其中包含最终参数和重放策略。如果进程在执行途中退出,恢复路径会检查记录下来的策略与当前工具声明。只有两者都允许安全重放,工具才会重新执行;否则形成中断错误结果。10

flowchart TB
    I[提交工具意图<br/>参数与 replay 策略] --> E[执行外部动作]
    E --> R[提交工具结果]
    E -.->|执行阶段进程中断| RE[重启后读取检查点]
    RE --> SAFE{已保存策略与当前工具<br/>都声明 safe?}
    SAFE -->|是| AGAIN[重新执行工具]
    SAFE -->|否| INT[生成 interrupted 结果]
    AGAIN --> R

图 6:工具执行中断后的恢复分支。重放意味着重新执行,不意味着系统已经自动证明外部动作只发生一次。

如果工具会发送消息、创建资源或提交支付,仅仅把 replay 设为 safe 并不会消除重复副作用。工具仍然需要幂等键、结果查询或其他业务约束。持久运行时提供的是保存意图和实施恢复规则的位置。

Durable 还用任务所有权表达父子工作关系。前台子任务会参与父任务的等待和取消;后台任务可以成为不同的生命周期边界。这让子 Agent、并发子任务和取消传播能够围绕已保存状态组织,但具体语义需要与任务定义和所属关系一起分析。

存储后端也有差别。Memory 适合不落盘的运行;JSONL 使用追加文件;SQLite 和 Cloudflare Durable Object SQLite 提供对应的持久存储适配。是否能抵抗进程退出、主机故障或掉电,仍取决于后端与刷新配置,不能仅凭“使用 Durable”给出同一种保证。9

3.4 Chord 与 Env 分别补上什么

Chord 提供类型化对象契约、可观察 JSON 状态、取消 Context、跨进程连接与热更新等能力。它的完整能力范围比 Durable 的一次模型请求更广;不能把“Durable 依赖 Chord”理解成每次生成都会用到 Chord 的全部机制。11

Env 则将执行环境抽象落实到机器。Durable worker、存储和认证可以留在本地,文件与命令操作通过连接交给远程 daemon。这里既有 TypeScript 客户端,也有在目标机器运行的 Rust 程序。远程环境与本地环境实现同类执行契约,使运行时可以在不改变模型循环目标的情况下更换动作发生的位置。11

flowchart LR
    subgraph HOST[运行时所在机器]
        W[Durable worker]
        ST[存储]
        AUTH[模型认证与接入]
        CLIENT[RemoteExecutionEnv]
        W --> ST
        W --> AUTH
        W --> CLIENT
    end
    subgraph REMOTE[执行所在机器]
        DAEMON[pi-env daemon]
        OS[文件与进程]
        DAEMON --> OS
    end
    CLIENT -->|连接与操作协议| DAEMON

图 7:远程执行的部署示意。箭头表示访问或调用关系;具体传输可以由连接实现提供,SSH 是其中一种使用方式。

Durable、Chord 和 Env 共同展示了另一组设计问题:工作怎样保存、状态怎样被观察、取消怎样传播、动作在哪里执行。它们与编码助手有关联,却不能被简化成给 SessionManager 换一个数据库。

4. 如何验证理解:测试、观测与行为评估

架构分析需要区分“控制流符合约定”和“真实任务完成得好”。Pi 的测试、观测和评估分别提供不同证据。

4.1 模拟模型能够验证哪些事实

通用循环通过 streamFn 请求模型,因此测试可以注入确定的响应,而不依赖在线模型。在现有 echo 工具测试中,第一次模拟响应要求调用 echo,工具返回结果,第二次模拟响应给出最终文本。12

这条测试可以检查:模型是否被调用两次,工具是否收到预期参数,结果是否出现在返回消息中,以及相关结果信息是否经过 hook 处理。它让一次完整工具反馈过程具有可重复的输入与输出。

同一组测试还覆盖上下文转换顺序、工具完成顺序与结果消息顺序、排队消息进入循环的时机,以及结束与继续的边界。这些场景直接对应前文的架构契约。

flowchart LR
    FAKE[确定的模拟模型响应] --> TEST[控制流测试]
    TEST --> CONTRACT[验证消息、事件与状态约定]
    RUN[真实模型与工具运行] --> OBS[观测记录]
    OBS --> DIAG[解释耗时、用量与失败]
    TASK[任务集与评估条件] --> EVAL[行为评估]
    EVAL --> OUT[比较实际任务表现]

图 8:三类验证提供不同证据。连线表示证据产生过程;控制流测试通过,不能替代真实模型联调或任务效果评估。

4.2 Telemetry 提供契约,宿主决定后端

telemetry 提供显式传递的观测上下文、span 契约、类型化 schema 和内存参考实现。它不自带一个默认导出后端,也不依赖全局“当前 span”状态。应用可以提供自己的适配器,接到日志、OpenTelemetry 或其他系统。13

这意味着观测能力可以作为边界契约融入调用过程,而不要求所有使用 Pi 的应用采用同一种平台。真正能够观测到哪些阶段,还需要看相应调用路径是否创建并传递了观测上下文。

4.3 Evals 关注任务表现

evals 用于编码助手的行为评估。当前实现区分宿主环境中的评估与文档增益实验。文档增益实验会在有文档、无文档的隔离条件下运行用例,再配对结果形成报告。13

这里回答的问题已经从“事件顺序正确吗”变成“给定模型、工具和材料,任务结果怎样”。模型、输入样本、环境和评分方式都会影响结论。因此,一篇整体架构分析可以确认评估机制的存在和组织方式,却不能据此声称 Pi 在某类任务上表现更好。

5. 整体设计判断与后续研究地图

结合上篇的执行链与本文的上下文、扩展和恢复机制,可以形成几项初步判断。这些判断是基于源码结构与行为契约的分析,不代表对作者全部设计动机的推断。

5.1 通用循环与产品策略分开,复用以契约为代价

Agent 循环保留模型请求、工具执行、队列和生命周期等共同机制,编码会话通过 hook、资源和事件组织产品行为。这样的结构允许其他宿主复用执行能力。

相应的代价是,调用顺序和状态边界必须清楚。哪些 hook 可以改变下一次请求,哪些事件意味着消息已经完成,什么时候能够开始下一项工作,都不能由接入方自行猜测。

5.2 事件连接执行与交互,也增加了顺序要求

相同运行过程能够服务终端、JSON 和 RPC,说明事件是重要的连接方式。工具完成事件与结果消息分开,也是为了同时满足显示与上下文需求。

但事件不是无成本的解耦。异步订阅、持久化时机、工具结果顺序和空闲判定,都会影响外部观察到的行为。后续分析需要把事件看作协议,检查它与真实状态的对应关系。

5.3 历史与上下文分开,使长期会话能够改变视角

历史树保存发生过的事情,会话投影决定本次请求看见什么。分支、摘要和上下文编辑因此能在保留追溯能力的同时改变后续生成。

这也把复杂度集中到投影和转换边界。压缩是否保留工具调用关系、分支是否恢复正确模型选择、自定义消息是否被正确转换,都会直接影响后续模型行为。

5.4 持久任务让恢复成为运行模型的一部分

Durable 保存任务意图、检查点与所有权,使恢复和取消可以围绕明确状态展开。这种结构适合研究持续运行、子任务和跨进程恢复。

代价是持久状态本身需要版本、迁移、重放和生命周期规则。持久化范围扩大,并不会自动消除业务副作用的不确定性;它让这些问题具有可以讨论和验证的边界。

5.5 后续文章沿哪些问题展开

作为系列开篇,这组上下篇建立整体位置。后续按以下问题逐篇展开,每篇会把一个机制讲到能够推演其行为,并把结论放回整体架构中。

后续主题

核心问题

对应架构位置

Agent 循环与生命周期

一次运行怎样推进,什么条件下继续或结束?

agent

工具执行与并发

参数、hook、错误、取消和结果顺序怎样协作?

Agent 循环与工具实现

输入队列与运行中干预

steering 和 follow-up 怎样进入已有工作?

Agent 与 AgentSession

会话历史与分支

历史树怎样保存、切换和恢复?

SessionManager

上下文投影与压缩

模型看见哪些信息,摘要怎样影响后续行为?

会话投影、转换与压缩

扩展与资源生命周期

扩展如何注册、运行、重载,并参与会话?

ResourceLoader 与 ExtensionRunner

MCP 与 Code Mode

外部工具怎样接入,脚本怎样组合嵌套调用?

工具接入与编排

模型适配与流式协议

统一消息怎样映射不同供应商,错误和用量如何返回?

ModelRuntime、Provider 与 API

TUI、RPC 与 SDK

同一个会话怎样支持不同交互入口?

会话接口与交互层

Durable 调度与恢复

检查点、等待、重放和所有权怎样决定恢复行为?

Harness 与 TaskScheduler

Chord 与执行环境

状态怎样同步、代码怎样更新、动作怎样转交远程机器?

Chord 与 Env

测试、观测与评估

怎样分别证明机制正确、解释运行、衡量任务效果?

测试、Telemetry 与 Evals

这条研究路线的目标,是逐步建立能够解释整个系统行为的模型:面对一条输入、一次工具错误、一项上下文变化或一次进程中断,能够指出谁负责处理、状态在哪里变化,以及下一步为什么会发生。

研究范围与源码依据

这组上下篇基于本地 Pi 源码,版本固定为 4954d760504c11df9b2cbdc040346107b6a8153d。文中的“当前”均指这个版本。引用使用同一提交的源码链接,避免后续分支变化使结论失去对应依据。

引用

依据

支撑的分析

1

Agent 循环

请求、工具执行、队列、转换与结束协议

2

AgentSession

会话策略、请求投影与事件持久化

3

历史与上下文投影、应用消息转换、模型消息类型

三种消息表示、分支和压缩

4

read 工具

工具声明与文件读取实现

5

资源加载、扩展执行、扩展契约

技能、模板与扩展的接入边界

6

MCP、Code Mode

外部工具和脚本编排

7

模型运行时、Models、Anthropic Provider、协议实现

模型注册、认证与协议映射

8

交互模式、TUI、Print / JSON

事件与交互入口

9

Durable 概念与使用说明

持久运行、存储和恢复边界

10

生成任务、工具任务、调度器

检查点、重放与任务推进

11

Chord、Env

状态基础设施与远程执行

12

Agent 循环测试

echo、消息转换、顺序与队列场景

13

Telemetry、Evals

观测契约与行为评估

回看整体架构与执行链:认识 Pi(上):整体架构与任务执行。