Part I · The Map

02 三层边界 每一层守住的东西,正好是上一层不想知道的东西

已发布 provider 39 个 内建工具 7 个 扩展事件 25 类
§1 Mental Model 一句话心智模型

先记住这一句

三层的分工可以用「谁知道什么」来概括:ai 知道 provider 但不知道循环;agent 知道循环但不知道文件系统长什么样;coding-agent 知道用户,并且承担全部产品决策。
层它知道它刻意不知道
pi-ai39 家 provider 的请求格式、认证、流式差异、token 计费什么是「一轮对话」;什么是工具执行
pi-agent-core循环、工具调用协议、会话存储、压缩工具具体做什么;UI 怎么渲染;用户是谁
pi-coding-agent7 个内建工具、扩展、TUI、设置、权限(没有)、产品行为—(它是终点)

这个分层最实用的用途是定位问题:模型返回格式不对 → ai;工具跑了但结果没进上下文 → agent;工具行为不符合预期 → coding-agent。

§2 Evidence 代码实证

第一层:pi-ai 只做「统一 provider」这一件事

它的自我定义写在 README 第一句:统一的 LLM API,带 provider 集合、自动认证解析、token 与成本跟踪,以及简单的上下文持久化与中途换模型的交接。ai/README.md:3

它还有一条显式的收录标准:

packages/ai/README.md原文 L5
**Note**: This library only includes models that support tool calling (function calling),
as this is essential for agentic workflows.
一个通用 LLM 库不会这样限定。pi-ai 从一开始就是为 agent 服务的库,不是一个中立的多 provider SDK。ai/README.md:5

第二层:pi-agent-core 的工具是「抽象工具」

packages/agent 里确实有 bash/read/write/edit 四个工具实现,但它们接受一个注入的执行环境(ExecutionEnv、FileSystem、Shell),而不是直接调 node:fs。harness/types.ts:315

这就是 第 15 章里 Gondolin 扩展能把工具路由进 micro-VM 的原因:工具逻辑与「在哪执行」是分开的。而 coding-agent 有自己那套 7 个工具(多了 grep/find/ls),面向真实文件系统。

顺带一提,这是全书第二处「同名双份实现」——第一处是压缩(第 06 章,漂移 d3)。两处的成因相同:harness-v2 迁移期,新旧两套并存。

锚点之后两处双份实现都已经没有了,但不是因为 coding-agent 迁到了新 harness:7fd478a(2026-10-01) 随 harness 一起删掉了 agent 包里的工具与压缩副本,随 v1.0.0 发布。见 漂移记录 · 上游后续。

第三层:coding-agent 承担全部产品决策

决策在哪一层本书章节
「不做权限系统」coding-agent15
压缩阈值 = window − 16384coding-agent06
!!cmd 不进上下文coding-agent05
会话格式 v3coding-agent07
扩展事件有哪 25 类coding-agent12

packages/agent 对这些一无所知——它只提供机制。

核心最小化是写进贡献指南的

CONTRIBUTING.md原文 L9 与 L11(并置)
If your feature does not belong in the core, it should be an extension.
PRs that bloat the core will likely be rejected.

Pi's core exists to be minimal and to be extensible so that it can be influenced and
manipulated by extensions.  Even hook points for extensions however should be well
considered and discussed to avoid adding unmaintainable bloat and complex interactions.
第二段那句「即便是给扩展用的 hook point 也要充分考虑和讨论」,解释了 第 03 章里循环钩子为什么只有五个、且都不改控制流。CONTRIBUTING.md:11

锚点之后锚点时,「做成扩展」最有代表性的一条表态写在 coding-agent 的 README 里:coding-agent/README.md:498。这句在 25cc5c7(2026-09-22)的文档重写中删去,8562bcf(2026-09-29)随后加入了内建的 MCP 与 codemode,coding-agent 直接依赖这两个包:package.json:45、package.json:46。按本章对「核心」的定义(packages/agent)这不算破例,MCP 也仍以扩展形式加载(cli.md:197);变的是产品层的默认:从「需要就自己写扩展」变成「默认自带」。贡献指南这两段在 v1.2.0 上未变。

§3 Design 设计动机与权衡

边界画在哪里,决定了什么能被替换

把「一轮对话」的概念留在 agent 而不是 ai,意味着换 provider 不影响循环;把「文件系统」留在注入接口而不是硬编码,意味着换执行环境不影响工具逻辑;把「UI」留在 coding-agent 而 tui 零依赖,意味着无头运行不需要 mock UI。

三条边界都遵循同一个判据:一个东西如果可能有第二种实现,就把它做成注入点。

代价:同一个概念在三层各有一份类型

一条消息在 ai 里是 Message,在 agent 里是 AgentMessage,在 coding-agent 里还有扩展出来的自定义 role。第 05 章那两道闸就是这个代价的具体形态——每次 LLM 调用都要做一次类型转换。

换来的是每层可以独立演化。coding-agent 加一种消息类型不需要动 ai;ai 加一个 provider 不需要动 agent。

「核心最小」与「产品层 12 万行」并不矛盾

容易误读的一点:CONTRIBUTING.md 说核心要最小,但 coding-agent 有 12 万行。这不矛盾——「核心」指的是 packages/agent(2 万行)。产品层大是正常的,因为产品决策就是多。

真正的约束落在别处:coding-agent 里那些「本可以做成扩展」的功能,才是这条规则要拦的。而它给出的出路是扩展系统(第 12 章)——所以扩展系统的能力边界,直接决定了这条规则可执行到什么程度。

§4 Limits 边界与失效条件
§5 Takeaway 拿走什么
§ Self-check 自测

给场景,判断 pi 会怎么做

场景模型返回的流式 chunk 解析出错、字段对不上。按三层分工,最先查哪个包?

看答案

B。「模型返回格式不对 → ai;工具结果没进上下文 → agent;工具行为不符预期 → coding-agent」——分层的定位价值就在这里。

场景你想给 pi 加「让模型悄悄重试上一个 turn」的功能。写个扩展能做到吗?

看答案

C。循环的五个钩子没有一个能跳过或重排 turn。这是刻意的:CONTRIBUTING.md:11

基于 earendil-works/pi @ ac4ac9e(2026-08-07 快照)。
仓库 commit 速度约 428/月,行号引用仅对该 SHA 有效。