学完 Pi 后应建立的基本认知
这是一份学习验收清单:能否解释运行机制、预测异常路径、定位责任 module,并把设计原则迁移到自己的后端系统。
源码快照:Pi
ccb9464114c2542aad444054f590f06b03a324ac(2026-08-28)。相关章节:TS/JS 知识、当前架构、Commit History 架构演进。
什么叫“建立了认知”
Section titled “什么叫“建立了认知””“我见过这个 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.tspackages/coding-agent/src/modes/rpc/rpc-client.tspackages/protocol/src/framing.tspackages/protocol/src/codec.tspackages/ai/src/utils/event-stream.ts
要点: 诊断“流坏了”之前,必须先指出坏的是 transport、framing、decode、validation、domain event 还是 aggregation。
3. TypeScript 类型不是信任边界
Section titled “3. TypeScript 类型不是信任边界”AgentTool<typeof schema> 能让工具实现获得静态参数类型,但模型返回的 JSON、网络消息和配置文件不会经过 TypeScript 编译器。
Pi 的工具路径同时保留两类保障:
- 编译期层:
Static<typeof schema>与AgentTool<typeof schema>约束可信代码之间的类型关系; - 运行时层: 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。
要点: 看到并发问题时,不问一句笼统的“是否有序”,而要分别问谁启动、谁完成、谁观察、谁最终提交。
6. 取消是协作通知,不是回滚
Section titled “6. 取消是协作通知,不是回滚”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 不等于无耦合,只是把调用耦合变成协议耦合。
9. Package 拆分不等于架构解耦
Section titled “9. Package 拆分不等于架构解耦”判断一个拆分是否有效,要看:
- 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 被取消”:
- 两个调用先经过 schema 校验和
beforeToolCall; - 若都允许并行,它们可能同时执行;
publish_report的外部 effect 在取消前已经发生,AbortSignal 不能回滚;read_report是否停止取决于 implementation 是否观察 signal;- tool end event、最终 transcript 和持久化 entry 可能处于不同阶段;
- 重启后只有 transcript 不足以判断 publish 是否应重试;
- 生产实现需要 idempotency key 或查询外部 effect 状态,再决定重放、补记结果还是人工介入。
这个案例同时检验协议、顺序、取消、effect、状态 owner 和 durability 六条认知。
阅读新代码时的十问
Section titled “阅读新代码时的十问”以后阅读 Pi 或其他 Agent 框架,可以先回答:
- 这段代码处理的是值、事件、消息、turn、run 还是 session?
- 输入是否来自不可信边界,运行时在哪里验证?
- 唯一状态 owner 是谁,其他副本如何同步?
- 启动、完成、观察和提交顺序分别是什么?
- 失败编码为异常、结果值、事件还是持久化状态?
- 取消能停止什么,不能撤销什么?
- 外部 effect 的幂等键和提交点在哪里?
- interface 是否有第二个 adapter 或独立测试面?
- 当前证据是 implementation、scaffold、测试还是设计说明?
- 这个结论是当前源码事实,还是必须回到历史才能回答的原因判断?
如果下面问题仍有三个以上无法回答,应回到对应章节定向补读,而不是重新通读仓库:
- 一条 assistant delta 如何变成最终消息,再变成产品会话记录?
- 为什么工具参数有 TypeScript 类型仍必须运行 schema validator?
- 为什么
Promise.all的结果顺序不能代表工具完成顺序? agent_end与waitForIdle()的完成点为什么可能不同?- steering 和 follow-up 分别在哪个排水点进入?
- AbortSignal、幂等、事务和补偿分别解决什么问题?
- 至少列出五份状态及其唯一 owner。
- 至少指出三个真实 seam、各自的 adapter 和测试面。
- 为什么
ExtensionAPI能力强,却仍有宽 interface 成本? - 为什么当前
AgentHarness不能按公开类型直接视为完成品? - 当前源码与 Commit History 分别能回答什么?
- Pi runtime 与企业生产底座的责任分界在哪里?