Agent X-Ray
RuntimeNotesAbout
Notes/源码拆解/Pi/第13章

第13章:设计精华总结 —— 把 Pi 的设计智慧带走

15 分钟 · 更新于 2026-09-01

第13章:设计精华总结 —— 把 Pi 的设计智慧带走

补全章节说明:原教程(dg-ai-notes)插图版只覆盖到第 10 章。本章综合前 12 章的设计分析与 Pi v0.81.1 官方文档(README.md「Philosophy」、docs/security.md、docs/skills.md、docs/packages.md 等)撰写,作为全系列的收官。引用时区分了官方原话(英文引文)与教程提炼(作者观点),行文风格对齐前 10 章。

前 12 章,我们钻了 12 个子系统。每一章的结尾都有一节"设计精华"或"方法论提炼"——如果你一路读下来,手里已经攒了三十多条零散的设计经验。

这一章不再钻新的子系统。我们把 12 章的决策摆到同一张桌子上,做三件事:找出它们背后共同的思维方式(四条主线);汇总可以搬进你自己项目的方法论(一张总表);以及同样重要的——划清哪些设计是 Pi 语境的特产,照搬会翻车(边界清单)。


一、先看全景:12 章,12 个决策

每章的核心决策压缩成一行:

子系统核心决策一句话本质
1总览三位一体:工具 / 教材 / SDK减法是一种产品立场
2架构三层堆栈 + 正交 UI 库依赖漏斗:底层不知道上层的存在
3Agent LoopstopReason 驱动 + 内核/叠加循环的去留,交给模型输出模式判断
4模型调用事件协议 + 翻译器函数协议 > 实现:不写 BaseProvider 抽象类
5工具系统五步管道 + 错误即消息工具出错不抛异常,编码成消息让模型自愈
6消息系统双层消息 + 边界翻译数据结构同时照顾两个读者(功能层 / 模型)
7事件驱动同步屏障 + 两层事件emit 不是通知,是同步协商
8上下文工程多层防护 + 拉模式加载没有银弹,只有层层兜底
9上下文压缩逆向切点 + 结构化摘要用固定模板对抗 LLM 的自由发挥
10会话管理树形 + append-only不删数据的回退:历史无价,磁盘便宜
11扩展系统事件总线 + 两阶段绑定用"约定"换"灵活",把功能决定权交给用户
12测试模式边界注入 + 事件断言面可测试性是抽象边界设计时"留"出来的

盯着这张表看一会儿,你会发现 12 个决策不是 12 种智慧——它们反复在用同样几招。下面四节,就是这几招。


二、主线一:减法哲学——"不做"是最贵的功能

官方清单:Pi 刻意不做的六件事

Pi 官方 README 的「Philosophy」章节,是整个项目最浓缩的宣言。总纲:

"Pi is aggressively extensible so it doesn't have to dictate your workflow. Features that other tools bake in can be built with extensions, skills, or installed from third-party pi packages. This keeps the core minimal while letting you shape pi to fit how you work." (Pi 激进地可扩展,所以它不必替你决定工作流。别的工具内建的功能,这里用扩展、技能或第三方包实现。核心因此保持最小,而你可以把 Pi 塑造成你的工作方式。)

六条 "No",逐条原文(README.md「Philosophy」):

#官方原文给出的替代方案
1"No MCP. Build CLI tools with READMEs (see Skills), or build an extension that adds MCP support."带 README 的 CLI 工具(走 Skills),或自写扩展
2"No sub-agents. There's many ways to do this. Spawn pi instances via tmux, or build your own with extensions, or install a package that does it your way."tmux 多实例 / 扩展 / 第三方包
3"No permission popups. Run in a container, or build your own confirmation flow with extensions inline with your environment and security requirements."容器隔离,或按自己的安全要求用扩展自建确认流
4"No plan mode. Write plans to files, or build it with extensions, or install a package."把计划写进文件
5"No built-in to-dos. They confuse models. Use a TODO.md file, or build your own with extensions."TODO.md 文件(理由罕见地直接:内置待办"会把模型搞糊涂")
6"No background bash. Use tmux. Full observability, direct interaction."tmux——完全可观测、直接交互

减法能成立的三个前提

看完 12 章再回头读这六条 "No",你会发现每一条都不是"偷懒不做",而是有配套机制兜底的战略放弃

前提 1:扩展系统足够强(第 11 章)。 六条 "No" 里五条的替代方案包含 "build with extensions"。官方 README 列出的扩展能力清单里,"Sub-agents and plan mode"、"Permission gates and path protection"、"MCP server integration" 赫然在列。敢做减法,是因为加法的接口已经留好了。 没有事件总线和 34 个介入点,"No permission popups" 就只是裸奔。

前提 2:现有工具已经解决(Unix 哲学)。 "No background bash. Use tmux."——后台任务管理是 tmux 花了十几年磨好的能力,重造一遍只会更差。第 8 章的 Skills 拉模式同理:"带 README 的 CLI 工具"本身就是比 MCP 更轻的集成协议。

前提 3:文件比功能更持久。 plan 写进 plan.md、待办写进 TODO.md、项目规范写进 AGENTS.md——文件能进 git、能 diff、能被任何编辑器打开、能被下一个工具读取。内置功能只活在这个工具里,文件活在整个生态里。

教程提炼:减法哲学的完整表述不是"少做功能",而是——每砍掉一个功能,都要说清楚"谁来补、用什么补、为什么补得更好"。 六条 "No" 每条都带着替代方案,这才是它和"没做完"的区别。

减法的另一面:五种可组合的加法杠杆

"核心不做"之所以不等于"用户没有",靠的是五种定制杠杆。把它们放在一张表里看,会发现每种杠杆的能力上限上下文成本是精心错开的:

杠杆本质能力上限上下文成本加载位置
Prompt TemplatesMarkdown 文件,/名字 展开复用一段提示词(支持 $1/$@ 参数)用时才展开~/.pi/agent/prompts/.pi/prompts/
Skills带 frontmatter 的说明书教模型一套流程/工具用法仅 name+description 常驻,全文按需 read~/.pi/agent/skills/.agents/skills/
ThemesJSON 颜色定义(51 个 token)改 TUI 外观~/.pi/agent/themes/
ExtensionsTypeScript 模块(第 11 章)任意代码:工具/命令/事件拦截/UI按注册内容~/.pi/agent/extensions/.pi/extensions/
Pi Packagesnpm/git 分发容器打包以上四种pi install npm:... / git:...

Skills 那一行是官方对"渐进式披露"最标准的实现,skills.md 的原话:

"This is progressive disclosure: only descriptions are always in context, full instructions load on-demand." (这就是渐进式披露:只有描述常驻上下文,完整指令按需加载。)

另一个细节体现了生态观:Pi 遵循 Agent Skills 标准但刻意放宽了"技能名必须等于目录名"的约束,理由是共享技能目录会被多个 Agent 工具混用(skills.md)——你甚至可以直接把 ~/.claude/skills 配给 Pi 用。兼容别人的生态,而不是再造一个自己的,这也是减法。

安全观:拒绝安全表演

减法哲学最有争议的落点是安全。Pi 默认 YOLO——没有权限弹窗。官方 security.md 的论证:

"A partial in-process sandbox would be easy to misunderstand as a security boundary while still depending on the host shell, filesystem, package managers, credentials, and extension code. Real isolation needs to come from the operating system or a virtualization/container boundary." (进程内的半吊子沙箱容易被误当成安全边界,实际上它仍然依赖宿主 shell、文件系统、包管理器、凭据和扩展代码。真正的隔离必须来自操作系统或虚拟化/容器边界。)

以及对提示注入的罕见坦诚:

"Prompt injection from repository files, comments, documentation, context files, or build output is expected local-agent risk and cannot be reliably prevented by pi." (来自仓库文件、注释、文档、上下文文件或构建产物的提示注入,是本地 Agent 的预期风险,Pi 无法可靠地防住它。)

这个立场可以概括为:与其提供一个让人产生安全错觉的机制,不如诚实地说"我不安全,请把我关进容器"。(前 10 章提到的"弹窗疲劳 / security theater"等说法出自作者 Mario 的博客阐述,官方文档采用的是上面这种更工程化的表述——两者指向同一判断:形同虚设的防护比没有防护更危险。)

第 12 章其实已经见过这个思维的另一个化身:skipIf 让没有 API key 的测试显示为"跳过"而不是"通过"——不假装验证过没验证的东西。诚实一以贯之。


三、主线二:协议优先——稳定性不在实现里,在约定里

把 12 章里所有"两个组件怎么打交道"的答案排成一列,会看到惊人的一致性——Pi 几乎从不用继承和直接调用来连接组件,永远用"协议"

连接点协议是什么章节
Agent Loop ↔ 30+ 家 LLMStreamFunction 函数签名 + 12 种流事件第 4 章
Agent ↔ UI / 持久化 / 日志10 种 AgentEvent + subscribe第 7 章
Agent ↔ 工具Tool 三层类型 + ToolResultMessage(错误也是消息)第 5 章
消息系统 ↔ LLM API三种标准消息 + convertToLlm 边界翻译第 6 章
核心 ↔ 扩展34 种事件 + 返回值协议(block / cancel / 链式补丁)第 11 章
会话 ↔ 存储SessionStorage 接口(JSONL / 内存 / 你的数据库)第 10 章
工具 ↔ 操作系统Operations 最小接口(本地 / SSH / 沙箱 / Mock)第 5 章
系统 ↔ 测试以上全部协议的免费复用第 12 章

第 4 章解释过为什么不用 BaseProvider 抽象类:"翻译器之间几乎没有共同点——继承需要找共同代码,但协议只约定输入输出。"这个判断推广到全系统就是:

协议的三重红利

  1. 可替换——任何一方可以换实现,另一方无感(30+ 供应商、可插拔存储)
  2. 可扩展——第三方按协议接入,核心不用改一行(扩展系统的全部立足点)
  3. 可测试——替身只需实现协议,不需要理解实现(第 12 章的 faux provider、MockStream、虚拟终端)

第三条最容易被忽视,也最值钱。第 12 章的结论值得再引一次:可测试性不是测试阶段"加"上去的,而是每个抽象边界设计时"留"出来的。 协议就是那个"留"的动作。

教程提炼:设计两个组件的连接时,先写协议(事件清单 + 返回值语义 + 错误语义),再写实现。协议要窄(StreamFunction 只有一个函数)、要完备(错误也在协议内——第 5 章"错误即消息")、要有版本意识(第 6 章声明合并留扩展槽)。


四、主线三:分层与最小知识——每一层只知道刚好够用的事

三种分层,一个原则

Pi 里"分层"出现了三次,形态不同,原则相同:

包级分层(第 2 章)pi-ai → pi-agent-core → pi-coding-agent 依赖漏斗,pi-tui 完全正交。验证标准朴素到残酷——"去掉上层,这一层还能跑吗?"

逻辑分层(第 3 章):Agent Loop 的"内核 + 叠加"。最简循环十几行是所有 Agent 的通用法则;steering、followUp、钩子是产品功能的按需叠加。"剥掉任何一层,里层仍能跑。"

能力分层(第 11 章):扩展 API 的三层门面——ExtensionAPI(注册)、ExtensionContext(观测 + 温和动作)、ExtensionCommandContext(危险操作)。分层依据不是信任等级,而是调用时机的安全性

三者共享同一个原则——最小知识:每一层只知道、只暴露、只依赖它职责内刚好够用的东西。pi-ai 不知道 Agent 的存在,Agent 内核不知道 TUI 的存在,事件处理器拿不到 switchSession

双层数据结构:同时照顾两个读者

分层思想在数据结构上的投影,是第 6 章那条主线:"数据结构要同时照顾两个读者。"内层 AgentMessage 字段丰富伺候功能层,外层三种标准消息伺候 LLM 协议,边界处一次有损翻译。第 10 章的 Session Tree(树形存储 vs 压扁成线性 messages)、第 9 章的结构化摘要(六段模板 vs 自由文本)都是同一思路的变体。

教程提炼:发现一个数据结构在"两拨人抢着用"时,不要折中成一个谁都不满意的形状——分成两层,各自伺候好自己的读者,边界处写翻译器。


五、主线四:把决定权交给合适的人

这是四条主线里最抽象、也最"Pi"的一条。整个系统里反复出现同一个动作:核心代码拒绝做决定,把决定权移交给更合适的决策者。

决定交给谁机制章节
循环要不要继续模型输出里有没有 toolCall——代码只做信号判断第 3 章
工具出错后怎么办模型错误编码成消息,模型自己决定重试/换路/解释第 5 章
加载哪个技能模型拉模式:清单进 prompt,模型按需 read 全文第 8 章
要不要拦这个工具调用用户(扩展)tool_call 事件 + block 协议第 11 章
要不要权限弹窗、要不要子 Agent用户(扩展/包)六条 "No" 的全部替代方案第 1、11 章
安全边界画在哪操作系统/容器拒绝进程内假沙箱第 1 章、security.md
会话存在哪集成方SessionStorage 接口第 10 章

注意这不是"什么都不管"的甩锅——每一次移交都配着一份精确的协议(这就是主线二的接口费):模型拿到的是结构化错误消息,扩展拿到的是带返回值语义的事件,容器拿到的是一个诚实声明"我不做隔离"的进程。

反面教材就是各家"全包"工具的路径:工具替用户决定要不要弹窗(结果弹窗疲劳)、替模型决定任务分几步(结果计划模式僵化)、替集成方决定存储格式(结果没法上云)。每替别人做一个决定,就欠下一笔"适配所有人"的债。

教程提炼:遇到"系统该不该做 X"的争论时,先问一句——这个决定的最佳决策者是谁? 如果是模型,就把信息编码进它能看到的消息;如果是用户,就留协议化的介入点;如果是环境,就诚实声明边界。核心代码只保留"没有更合适决策者"的决定。


六、方法论总表:三十条经验,按场景取用

把 12 章所有"可迁移方法论"汇总归类(括号内为出处章节),当作你自己项目的查阅清单:

架构与分层

  • 依赖漏斗分层法:底层不知道外面的世界,用"去掉上层还能跑吗"验证(2)
  • 类型递进扩展:底层原子类型,上层联合/继承扩展,不改底层(2、5、6)
  • 内核 + 叠加:先写十几行能跑的内核,产品功能做成可剥离的叠加层(3)
  • 正交能力独立成库:与主线无关的能力(如 TUI)彻底解耦,反向依赖为零(1、2)

协议与接口

  • 协议 > 实现:定义事件协议 + 函数签名,不做基类继承(4)
  • 统一枚举 + 映射表:上层说语义("high"),底层查表翻译成各家方言(4)
  • 语义接口与机制接口分离:上层说"做什么",底层决定"怎么做"(4)
  • 错误即消息:错误编码成协议内的一等公民,不让异常穿透边界(5)
  • 声明合并留扩展槽:核心包封闭协议 + 空插槽,应用包类型安全地注入(6)

事件与插件

  • 事件总线暴露关键点:识别工作流关键节点 emit 事件,胜过为每个点写专门 hook(7、11)
  • 四种事件模式:通知 / 一票否决 / 链式修改 / 短路接管,覆盖插件介入全力度(11)
  • 同步屏障:需要状态一致时 emit 后 await 全部监听器;高频低价值事件开"先收集后批量等"的口子(7)
  • 按后果选错误语义:观测类 fail-open 吞错继续,安全类 fail-closed 出错即拦(7、11)
  • throwing stubs + 延迟绑定:插件加载早于核心就绪时,用抛错桩把时机错误变成即时可读的失败(11)
  • 按时机分配门面:危险操作只进"用户显式触发"的上下文(11)

上下文与数据

  • 多层防护不找银弹:截断、组装、压缩、摘要各管一段,层层兜底(8)
  • 拉模式上下文加载:清单常驻 + 内容按需拉取,工具调用即上下文加载器(8)
  • 结构化摘要对抗自由发挥:固定模板 + 增量更新,把"易遗漏"变成"必须填"(9)
  • 逆向遍历保护最重要的:从最新往回收集,而不是从最旧往前砍(9)
  • 双读者双层数据结构:内层结构化为源,边界有损翻译为流(6)
  • append-only + 指针回退:撤销/分支场景不删数据,树形 + leafId 定位(10)
  • 节点化状态变量:状态变更存成节点而非全局变量,回退天然正确(10)

测试与质量

  • 不确定性收敛到单一注入点:LLM 调用抽成可替换函数(12)
  • 事件序列做主断言面:可观测性即可测试性(12)
  • 替身真到能测边界:拟真替身测流式/中断/并发,空壳 mock 只能测 happy path(12)
  • 内存替身 + 临时目录双层隔离:每个测试一个无菌沙盒(12)
  • 真依赖测试作可选层:缺凭据优雅跳过,确定性小题控住精确断言(12)

七、边界清单:哪些设计不能照搬

Pi 的一切设计都建立在一个具体语境上:单用户、本地终端、开发者工具、作者有极强的品味和克制力。换了语境,照搬就是翻车。四个典型:

1. YOLO 默认 ≠ 你的产品可以没有权限控制。 Pi 敢默认全放行,是因为用户 = 机器主人 = 后果承担者,三位一体;跑在容器里则连后果都可控。如果你做的是多租户 SaaS、面向非技术用户的产品、或者 Agent 操作的是共享资源——权限控制就不是"安全表演",而是必需品。Pi 的启示不是"删掉弹窗",而是"弹窗要么真的保护什么,要么就别弹"。

2. 同步屏障 ≠ 所有事件系统都该 await。 第 7 章讲过代价:Agent 必须等最慢的消费者。终端场景消费者就三五个(TUI、存储、扩展),等得起。如果你的事件要广播给成百上千个消费者、或者消费者里有慢 IO——照搬同步屏障会把吞吐拖死。Pi 自己都给 tool_execution_update 开了口子,你更该按事件价值分级。

3. JSONL 文件存储 ≠ 服务端也这么存。 本地单用户,文件就是最好的数据库(可 grep、可 git、零运维)。上了服务端多用户,并发写、检索、配额全来了——好在第 10 章讲过,Pi 把"存哪里"和"长什么样"拆成了两个独立维度,SessionStorage 接口就是给你换数据库留的门。照搬的应该是"树形 + append-only"的数据结构,而不是 JSONL 这个介质。

4. 90 词系统提示词 ≠ 提示词越短越好。 Pi 的系统提示词能极简,是因为它把知识挪进了三个地方:AGENTS.md(项目规范)、Skills(按需拉取)、工具描述(行为约定)。总量没有消失,只是从"每次全款预付"变成了"按需支付"。如果你没有这套渐进式披露的机制就硬砍提示词,砍掉的是能力不是浪费。

边界清单的通用判别法:把 Pi 的每个设计还原成"决策 + 语境前提",前提在你的场景里成立,才搬决策。前提不成立时,往往上一层的思维方式(主线一到四)仍然成立——那才是真正可以无条件带走的东西。


八、收官:如果你只带走三样东西

三十条方法论记不住没关系。压缩到三样:

1. 一个标准:每个抽象边界,用"另一方可以被替换成假的吗"来检验。能——说明协议干净;不能——说明知识泄漏了。这一个标准同时买到可扩展、可维护、可测试。

2. 一个动作:做功能取舍时,把"不做"当成一等选项认真论证——它的替代方案是什么?谁来补?为什么补得更好?写不出这三个答案的"不做"是偷懒,写得出来的是设计。

3. 一个问题:每当要写"如果 X 就 Y"的逻辑时,先问"这个决定的最佳决策者是谁?"——是模型、是用户、是环境,还是真的只能是你的代码?

最后,关于怎么继续。这个教程读完只是"看懂了",离"学到手"还差一次实践。三条路线按投入排序:

  • 一小时:写一个自己的扩展(第 11 章的模板改起),/reload 看它生效——体验"约定换灵活"
  • 一个周末:用 pi-ai + pi-agent-core 搭一个非编码场景的 Agent(客服、数据分析都行)——体验"每层可独立使用"不是宣传语
  • 一个月:给自己的项目做一次"Pi 式重构"——挑一个组件间的直接调用,改成事件协议;挑一个写死的依赖,改成注入接口;然后给它补上第 12 章式的测试

Pi 证明了一件事:在一个所有人都在做加法的赛道里,把每一次"不做"都设计得有理有据,本身就是最锋利的竞争力。 愿你的下一个系统,也敢这样做减法。

(全系列完)


附:Pi 关键数字速查(v0.81.1 官方口径,附出处)

数字事实出处
4 / 7默认给模型 4 个工具(read/write/edit/bash);内置共 7 个(+grep/find/ls)README.md
3 + 30+订阅制 provider 3 家(Claude Pro/Max、ChatGPT Codex、Copilot);API-key provider 约 30 家README.md
7 档思考等级:off / minimal / low / medium / high / xhigh / maxREADME.md
16384 / 20k压缩参数默认值:reserveTokens 16384(给回复留量)、keepRecentTokens 20k(近期不压缩)——第 9 章的两个阈值docs/compaction.md
50KB / 2000 行read 工具内建截断上限(先到者胜)——第 8 章双重限制的官方数字docs/extensions.md
51 个主题必须定义的颜色 token 数docs/themes.md
64 / 1024Skill frontmatter 的 name / description 长度上限docs/skills.md
34 种扩展事件类型总数——第 11 章extensions/types.ts
337 / ~3600测试文件数 / 用例量级——第 12 章仓库统计
4 种运行模式:interactive / print(-p) / json / rpc,全部构建在 createAgentSession() 之上docs/sdk.md

本章素材来源(Pi v0.81.1):

  • README.md「Philosophy」— 六条 "No" 原文与总纲;「What's possible」— 扩展能力清单
  • docs/security.md — 信任边界、进程内沙箱批判、提示注入坦诚声明
  • docs/skills.md — 渐进式披露:"only descriptions are always in context, full instructions load on-demand"
  • docs/packages.md — 五种定制杠杆的分发机制;docs/sdk.md — 四包分层与 SDK/RPC 选型
  • 第 1-12 章各章"总结 / 设计精华 / 方法论提炼"小节

版本说明:本章引用的官方文档与源码为 Pi v0.81.1(前 10 章基于 v0.80.2)。第 11-13 章为补全章节,官方原教程(dg-ai-notes)发布时未包含。

本章目录
一、先看全景:12 章,12 个决策二、主线一:减法哲学——"不做"是最贵的功能三、主线二:协议优先——稳定性不在实现里,在约定里四、主线三:分层与最小知识——每一层只知道刚好够用的事五、主线四:把决定权交给合适的人六、方法论总表:三十条经验,按场景取用七、边界清单:哪些设计不能照搬八、收官:如果你只带走三样东西附:Pi 关键数字速查(v0.81.1 官方口径,附出处)
苏ICP备2025204887号-2