02 · Pi 的设计思想与整体架构¶
2.1 先理解 Harness¶
大模型可以返回文本和工具调用,但不会自行读取你服务器上的文件。Harness 是围绕模型的运行程序:组装上下文、发请求、执行工具、记录结果、安排下一轮、处理取消,并把进展交给界面。
例如你说「找出项目的入口文件」:模型决定调用 find;Pi 校验参数并运行工具;工具结果进入下一次模型请求;模型根据真正存在的文件回答。你自己的 Agent,本质上需要决定这几个边界的行为,而不是另造一个模型。
2.2 设计思想一:把产品策略留给使用者¶
官方称 Pi 是一个精简、可扩展、可以变成自己工具的 Harness。CLI 提供文件操作、会话、模型、终端 UI 等基础能力,不预置统一的 plan mode 和 subagent 工作流。你可以用扩展自己实现,或者安装 Pi package。
这是一种产品取舍:相同的「计划」对代码修改、资料整理、服务器运维有不同含义。把它固定进内核会让业务选择变成框架限制。
应用方法:先把自己的工作流写成简单步骤;只有需要可执行行为或状态约束时才用 Extension。不能因为没有内建模式,就认为 Pi 缺少工具循环或会话能力。
边界:pi-durable 实验运行时确有 child tasks / subagent 支持;「CLI 默认没有 subagents」不等于整个 monorepo 从来没有子任务 API。
2.3 设计思想二:分层组合,按需要取用¶
这张图表达依赖职责,不表示所有包都在默认 CLI 的同一条调用链上。Durable 是另一种持久化 Harness;protocol/client/server 是额外的服务化组件,不应与 stdin/stdout RPC 混为一谈。
| 层 | 它承担什么 | 它不替你决定什么 |
|---|---|---|
| pi-ai | provider、模型发现、认证、消息和流 | 业务任务和工具执行策略 |
| pi-agent-core | 轮次、工具、队列、取消、事件 | CLI 资源发现与文件会话 |
| pi-coding-agent | 编码工具、扩展、会话、压缩、恢复 | 你的产品权限模型 |
| pi-tui | 终端输入和渲染 | 业务判断 |
| pi-mcp / pi-codemode | 外部工具接入与脚本执行 | 工具本身的安全性 |
| pi-durable | 存储先行、恢复、任务状态 | 外部系统恰好执行一次 |
| Chord | 服务组合、复制状态、插件生命周期 | 应用的网络部署和用户认证 |
2.4 设计思想三:历史是数据,当前上下文是投影¶
一个长会话包含很多旧消息、分支、模型切换和压缩记录。直接把所有东西推给模型,会超窗口,也会把废弃分支混入请求。
Pi 用 JSONL 记录树状历史;SessionManager 从当前 leaf 走父节点,重建活跃分支;压缩 entry 决定哪些旧消息用摘要替代。原始历史和当前模型输入因此是两个不同对象。
应用方法:SDK 里恢复会话应使用 SessionManager。不要只给 session.agent.state.messages 赋一个历史数组并期待它会覆盖持久化记录;下次请求会从 canonical projection 重建。
2.5 设计思想四:把协议事实记录进 transcript¶
1.0 中 system messages 携带 prompt sections 和工具声明变化。工具可执行实现存在运行时,而模型能看见的工具声明是 transcript 中的可重放状态。
例如研究助手从只读切到编辑状态,工具集合发生变化。Pi 在后续请求前记录 toolsAdded / toolsRemoved,不只偷偷修改某个内存数组。
这是把「当时模型被允许看到什么」变成可追溯事实。分段 prompt 补丁也让更新某个 skill 或 MCP 摘要时不必重写完整历史前缀,有利于 provider prompt caching。具体缓存命中仍由 provider 决定。
2.6 设计思想五:事件驱动,但有明确屏障¶
流式 token 只是观察值;message_end 才是完整消息。Core Agent 处理事件后按注册顺序等待订阅者,因此 assistant 结束事件可以在工具预检前完成状态处理。
原始 agentLoop() 的 EventStream 是观察流,不会等待消费者异步处理。若你的权限或存储依赖这种先后顺序,应使用 Agent 类或者自己实现显式屏障。
SDK 层还存在自动重试和溢出恢复,所以 agent_end 不是最终产品完成标志;应用应等待 session.prompt() 返回,或观察 agent_settled。
2.7 设计思想六:扩展覆盖多条路径,嵌套工具也走统一管线¶
模型直接调用、MCP 调用、Code Mode 内部调用都需要统一参数校验、拦截和结果处理。否则你在外层禁止 bash,脚本却能从侧门执行它。
ctx.executeTool() → nested runner → runToolCall() 复用预检/执行/后处理。子调用带 parentToolCallId,可以记录归属。
产品建议:你的业务授权要挂在执行边界,而不是只隐藏 UI 按钮。工具曝光控制描述的是可发现性和可调用性,它本身不是操作系统沙箱。
2.8 设计思想七:简单起步,明确承担安全边界¶
Pi 不默认对每次工具调用弹确认框,也不内建文件、网络和进程沙箱。它拥有启动账户的权限。cwd 是路径和资源发现的默认位置,不是限制访问的牢笼。
对个人可信项目,你可以接受这种直接性;对多租户产品,你必须自己提供 OS / 容器边界、身份认证和工具授权。模型提示中的「不要访问目录外」无法替代真实隔离。
这不是抽象提醒:给模型一个任意 bash 工具,就同时给了它当前账户可以执行的程序、网络访问与凭据读取能力。课程实验先用窄业务工具,让你看清权力从哪里产生。
2.9 如何判断是否需要改内核¶
| 想要的能力 | 优先实现位置 |
|---|---|
| 固定回答风格、项目规则 | AGENTS.md / APPEND_SYSTEM.md |
| 一套反复使用的提示 | Prompt template |
| 复杂工作步骤和参考资料 | Skill |
| API、工具、拦截、命令、持久状态 | Extension |
| 自己的网页或后台程序 | Coding Agent SDK |
| 不需要默认编码工具和资源发现 | Agent Core |
| 模型协议不受支持 | Provider extension / pi-ai provider |
| 崩溃后长期任务恢复 | 调研 Durable,或自己实现任务持久化 |
先组合,再改内核。只有现有接口无法表达你的需求,且你愿意维护版本差异时,才考虑 fork。
主要依据:官方 README、Agent、AgentSession、安全文档。