跳转到内容

学完 Pi 后应建立的基本认知

这是一份学习验收清单:能否解释运行机制、预测异常路径、定位责任 module,并把设计原则迁移到自己的后端系统。

源码快照:Pi ccb9464114c2542aad444054f590f06b03a324ac(2026-08-28)。相关章节:TS/JS 知识、当前架构、Commit History 架构演进。

“我见过这个 class”不算掌握。一个可迁移的认知至少包含四层:

层次 你应该能做什么 不足的表现
解释 用自己的话说明机制、顺序和 owner 只能复述类型名或目录名
预测 给定输入、失败或取消,预测事件和状态变化 只会描述 happy path
诊断 根据症状定位 provider、loop、tool、session 或 host 一出问题就从整个仓库全文搜索
设计 抽取可迁移原则,并说明语言与运行时差异 把 TypeScript 代码逐行翻译成 Go

1. Agent Loop 不是核心价值,运行协议才是

Section titled “1. Agent Loop 不是核心价值,运行协议才是”

最容易实现的是循环本身:请求模型,执行工具,再请求模型。真正困难的是定义每个阶段的可观察语义:

  • assistant partial 何时成为最终消息;
  • 工具参数何时校验,hook 何时能够阻止 effect;
  • 多个工具的事件和结果按什么顺序出现;
  • steering 与 follow-up 在哪个安全点进入;
  • error、aborted、length 和正常停止怎样收尾;
  • 最后一个事件与整个运行真正 settled 是否相同。

Pi 的一手入口是 packages/agent/src/agent-loop.ts :: runLoop, streamAssistantResponse, executeToolCalls 和 packages/agent/src/types.ts :: AgentEvent。

要点: Agent 是一个带外部 effect 的协议状态机;while 只是它的一种 implementation。

2. 字节、frame、事件、消息和会话不是同一层

Section titled “2. 字节、frame、事件、消息和会话不是同一层”

Pi 同仓库里至少能看到三种传输形态:

  • provider HTTP SSE;
  • coding-agent 本地 RPC 的 stdin/stdout JSONL;
  • pi-protocol 的 length-prefixed CBOR。

它们都可能从 chunk 开始,却不能混成一种“流”。概念分层如下;其中 runtime validation 是取决于具体信任边界的分支,不是当前所有路径都必经:

flowchart LR
    Bytes[arbitrary byte chunks] --> Framing[framing / line buffering]
    Framing --> Value[JSON or CBOR value]
    Value --> Gate{runtime schema here?}
    Gate -->|yes| Validation[runtime validation]
    Gate -->|no: parse / typed assumption| Event[domain event]
    Validation --> Event[domain event]
    Event --> Message[aggregated message]
    Message --> Session[session history]

Chunk 边界不等于字符、JSON 行、SSE event 或 CBOR frame 边界;EventStream 也不是 Node Readable 或 Web ReadableStream 的别名。

当前 pi-protocol 在 CBOR decode 后执行 TypeBox 校验,Agent 工具参数也在 effect 前校验;pi-messages 与 coding-agent JSONL 路径则主要是 JSON parse 后按预期类型继续处理,不等价于统一的 runtime schema validation。诊断时必须以具体路径为准。

关键来源:

  • packages/ai/src/api/pi-messages.ts
  • packages/coding-agent/src/modes/rpc/rpc-client.ts
  • packages/protocol/src/framing.ts
  • packages/protocol/src/codec.ts
  • packages/ai/src/utils/event-stream.ts

要点: 诊断“流坏了”之前,必须先指出坏的是 transport、framing、decode、validation、domain event 还是 aggregation。

AgentTool<typeof schema> 能让工具实现获得静态参数类型,但模型返回的 JSON、网络消息和配置文件不会经过 TypeScript 编译器。

Pi 的工具路径同时保留两类保障:

  1. 编译期层: Static<typeof schema> 与 AgentTool<typeof schema> 约束可信代码之间的类型关系;
  2. 运行时层: TypeBox schema 是 JavaScript 值,validateToolArguments 在 effect 发生前用它克隆、转换并检查外部参数。

来源:packages/ai/src/types.ts :: Tool、packages/agent/src/types.ts :: AgentTool、packages/ai/src/utils/validation.ts :: validateToolArguments。

要点: 静态类型改善可信代码之间的协作,运行时 schema 才能检查不可信输入;as SomeType 不会转换或验证任何值。

4. Tool 同时跨越模型协议与宿主 effect

Section titled “4. Tool 同时跨越模型协议与宿主 effect”

一个 tool 不是普通函数。它至少有两张脸:

  • 给模型看的名称、描述和参数 schema;
  • 给宿主执行的函数、取消、进度、错误和并发策略。

Pi 在 pi-ai Tool 上增加 AgentTool.execute、progress 和 executionMode,coding-agent 再提供具体文件与 shell effect。beforeToolCall 位于参数校验之后、effect 之前,适合做策略拦截;afterToolCall 可以改写返回给模型的结果,却不能让已经发生的外部写入自动回滚。

要点: tool seam 既是扩展点,也是生产宿主可以放置权限、审计、幂等和副作用策略的控制点。当前 Pi 路径能证明 schema、hook、取消、进度和并发契约存在,不能据此声称企业权限、审计或幂等已经实现。工具注册成功也不等于工具被授权执行。

5. 异步代码至少有启动、完成、发出和提交四种顺序

Section titled “5. 异步代码至少有启动、完成、发出和提交四种顺序”

Pi 并行工具路径能同时出现:

  • 预检按 source order 串行,参数错误、hook 拦截或取消产生的 immediate result 会在此阶段发出结束事件;
  • 通过预检的工具在 Promise.all 中并发启动;
  • 真正开始执行的工具,其 tool_execution_end 按实际完成顺序发出;
  • 最终 ToolResultMessage 按模型原始调用顺序写入上下文。

来源:packages/agent/src/agent-loop.ts :: executeToolCallsParallel 及 packages/agent/test/agent-loop.test.ts 的顺序用例。

这解释了为什么“原始调用列表中排在后面的工具先完成”可能是 UI 事件事实,却不应该改变可重放 transcript。

要点: 看到并发问题时,不问一句笼统的“是否有序”,而要分别问谁启动、谁完成、谁观察、谁最终提交。

AbortController.abort() 会通知观察同一个 signal 的 provider、tool、hook 或 listener。它不能强杀任意 Promise,也不能撤销已经写入文件、发送邮件或扣款的 effect。

Pi 的 Agent 为一次 active run 拥有 AbortController,把 signal 传进 loop;工具是否及时停止,取决于工具 implementation 是否主动响应。

对应到 Go,context.Context 也只表达取消与 deadline。goroutine 是否退出、外部请求是否终止、数据库事务是否回滚,仍由实现负责。

要点: cancellation 解决“停止继续工作”,idempotency、transaction 或 compensation 才处理“已经做过什么”。

7. 状态必须有唯一 owner 和明确生命周期

Section titled “7. 状态必须有唯一 owner 和明确生命周期”

这里的 owner 指给定生命周期内的权威写入者,不是“定义了 interface 的 module”。缓存或 UI 可以保存 projection;storage interface 规定规则,具体 backend 才拥有持久化事实的写入责任。

Pi 中至少同时存在模型请求的 partial message、单次 run 的上下文、进程内 Agent transcript、产品级 AgentSession 状态、跨进程 session 记录和 UI projection。它们生命周期不同,不能统称为一份“会话状态”。完整 owner 与来源见Pi 当前架构的状态所有权表。

要点: 缓存、projection 和事实源必须分开。发现两个 module 同时修改“同一状态”时,先确认它们是否其实拥有不同生命周期的副本。

8. Event 是解耦手段,也是一份需要维护的协议

Section titled “8. Event 是解耦手段,也是一份需要维护的协议”

Agent Loop 不直接 import TUI,而是发出 AgentEvent;Agent 先归约自身状态,再通知 listener;AgentSession 再负责持久化与产品事件。

这降低了下层对宿主的认识,但没有消除约束:

  • event 名称和 payload 是兼容承诺;
  • listener 的 await 顺序影响 settlement;
  • 慢 listener 会形成延迟;
  • 重复、缺失或乱序 event 会破坏上层 projection;
  • 敏感 payload 不能因“只是事件”就默认写入日志。

要点: event-driven 不等于无耦合,只是把调用耦合变成协议耦合。

判断一个拆分是否有效,要看:

  • module 是否隐藏了调用者原本需要处理的复杂度;
  • interface 是否比 implementation 更小、更稳定;
  • seam 是否真的有多个 adapter 或测试替换;
  • 一个修复是否主要停留在 owner 内部;
  • 删除 module 后,复杂度会消失还是扩散回调用者。

Pi 的 StreamFn、AgentTool、session storage 和 telemetry 都有实际 adapter 或测试面;ExtensionAPI 能力真实但很宽;coding-agent 仍直接依赖 core 与 pi-ai;compat 仍保留全局 fallback。这些事实应一起评价,而不是只画三层框图。

要点: 好架构不是“没有依赖”,而是依赖方向、控制权、状态 owner 和变化 locality 都可解释。

10. 可持久化会话不等于可恢复执行

Section titled “10. 可持久化会话不等于可恢复执行”

保存 transcript 能让用户重新打开对话,却不能自动回答:

  • 进程死在工具调用前、调用中还是调用后;
  • effect 是否已经提交;
  • 重试会不会重复写入;
  • 哪个 worker 拥有运行权;
  • 哪个操作可以安全 replay;
  • 未结束的 run 从哪个程序计数器恢复。

当前 coding-agent 的 SessionManager 解决产品历史;agent-core 的新 session contract 定义 entry、record、lane 和 open operation;AgentHarness 则提出 durable host interface,但多数执行操作在当前快照仍抛出 HarnessNotImplemented。

要点: durability 是具体状态机和 effect protocol,不是把 messages 写进数据库后的自动属性。

11. 源码、测试、示例和历史回答不同问题

Section titled “11. 源码、测试、示例和历史回答不同问题”
证据 最适合回答 不能单独证明
当前 implementation 今天代码怎样执行 为什么选择这个设计、真实部署是否可用
类型与公开入口 调用者看见什么契约 implementation 是否完成、时序是否正确
单元测试 已明确编码的行为与回归 未覆盖平台、真实 provider 或 UI
conformance test 多个 adapter 应共享什么行为 每个生产 adapter 的性能与运维质量
example 预期调用方式 示例一定被持续运行或覆盖所有错误
Commit History 设计如何出现、修复或回退 作者未写明的完整动机
E2E / 故障注入 集成路径在特定环境是否成立 所有环境与未来版本都成立

要点: “有 interface”“有测试文件”“有示例”“构建通过”是不同强度的证据,不能互相替代。

12. Pi Runtime 不等于企业 Agent 平台

Section titled “12. Pi Runtime 不等于企业 Agent 平台”

Pi 很适合研究模型协议、Agent Loop、工具、会话和扩展,但企业生产还需要业务 Run 的所有权、队列或 durable workflow、多实例并发、幂等 effect、短期身份、审批、审计、SLO 和持续评估。

仓库中的 remote protocol、server interface、session backend 或 durable harness 方向是相关素材,却不能仅凭存在这些 package 就宣布企业生产闭环完成。这个边界将在从 Pi Runtime 到企业生产 Agent中单独研究。

要点: Agent 决定不确定条件下的下一步,生产底座保证任务不丢、effect 不乱、权限不越界、结果可审计。

综合案例:两个工具、一次取消和一次崩溃

Section titled “综合案例:两个工具、一次取消和一次崩溃”

这是一个用于推理训练的假设场景,不是 Pi 的事故复盘。

假设模型在同一条 assistant message 中调用:

  • read_report:只读、80 ms 完成;
  • publish_report:有外部写 effect、20 ms 时已提交,100 ms 才返回结果。

50 ms 时用户点击取消,随后进程崩溃。

正确分析不能只说“Promise 被取消”:

  1. 两个调用先经过 schema 校验和 beforeToolCall;
  2. 若都允许并行,它们可能同时执行;
  3. publish_report 的外部 effect 在取消前已经发生,AbortSignal 不能回滚;
  4. read_report 是否停止取决于 implementation 是否观察 signal;
  5. tool end event、最终 transcript 和持久化 entry 可能处于不同阶段;
  6. 重启后只有 transcript 不足以判断 publish 是否应重试;
  7. 生产实现需要 idempotency key 或查询外部 effect 状态,再决定重放、补记结果还是人工介入。

这个案例同时检验协议、顺序、取消、effect、状态 owner 和 durability 六条认知。

以后阅读 Pi 或其他 Agent 框架,可以先回答:

  1. 这段代码处理的是值、事件、消息、turn、run 还是 session?
  2. 输入是否来自不可信边界,运行时在哪里验证?
  3. 唯一状态 owner 是谁,其他副本如何同步?
  4. 启动、完成、观察和提交顺序分别是什么?
  5. 失败编码为异常、结果值、事件还是持久化状态?
  6. 取消能停止什么,不能撤销什么?
  7. 外部 effect 的幂等键和提交点在哪里?
  8. interface 是否有第二个 adapter 或独立测试面?
  9. 当前证据是 implementation、scaffold、测试还是设计说明?
  10. 这个结论是当前源码事实,还是必须回到历史才能回答的原因判断?

如果下面问题仍有三个以上无法回答,应回到对应章节定向补读,而不是重新通读仓库:

  1. 一条 assistant delta 如何变成最终消息,再变成产品会话记录?
  2. 为什么工具参数有 TypeScript 类型仍必须运行 schema validator?
  3. 为什么 Promise.all 的结果顺序不能代表工具完成顺序?
  4. agent_end 与 waitForIdle() 的完成点为什么可能不同?
  5. steering 和 follow-up 分别在哪个排水点进入?
  6. AbortSignal、幂等、事务和补偿分别解决什么问题?
  7. 至少列出五份状态及其唯一 owner。
  8. 至少指出三个真实 seam、各自的 adapter 和测试面。
  9. 为什么 ExtensionAPI 能力强,却仍有宽 interface 成本?
  10. 为什么当前 AgentHarness 不能按公开类型直接视为完成品?
  11. 当前源码与 Commit History 分别能回答什么?
  12. Pi runtime 与企业生产底座的责任分界在哪里?