Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第 30 章:极简核心,能力外置

定位:本章提炼 pi 的核心设计方法论。 前置依赖:全书前 29 章。 适用场景:当你想把 pi 的设计经验应用到自己的系统中。

内核只做三件事

回顾前面的章节,pi 的内核(pi-ai + pi-agent-core)只做三件事:

  1. 调模型(第 4-7 章):统一 20+ 家厂商,流式事件,跨模型消息变换
  2. 跑循环(第 8-9 章):无状态的双层循环,三阶段工具执行管道
  3. 管状态(第 10 章):有状态的 Agent 壳,事件订阅,消息队列

其余所有功能 — 会话持久化、上下文压缩、system prompt 装配、工具实现、UI 渲染、配置管理 — 都在内核之外。

用行数说话

pi-mono 共约 120,000 行 TypeScript(不含测试和生成代码)。内核的占比很小:

行数职责层级
pi-ai~26,875模型调用、provider 注册、消息变换内核
pi-agent-core~1,859agent 循环、事件系统、状态管理内核
内核合计~28,73424%
pi-coding-agent~42,058会话管理、prompt 装配、工具实现、配置产品层
pi-tui~10,764终端 UI 渲染UI 层
mom~4,046Slack bot 适配产品壳
pods~1,773GPU 部署工具工具
web-ui~14,623Web UIUI 层

注意 pi-ai 的行数较多,是因为它包含了 20+ 家 provider 的适配代码(每个 provider 约 200-500 行的流式转换逻辑)。真正的核心抽象StreamFunctionContextModel 类型、agentLoopAgent 类)加起来不到 3000 行。

pi-agent-core 只有 ~1,859 行,是整个 monorepo 中最小的包。但它定义了最关键的协议:agent 循环、事件类型、工具接口、回调钩子。

graph TB
    subgraph Core["内核 ~3000 行核心抽象"]
        AI["pi-ai<br/>调模型"]
        Loop["agent-core<br/>跑循环"]
        Agent["Agent<br/>管状态"]
        AI --> Loop --> Agent
    end
    
    subgraph External["外置能力 ~90000 行"]
        Session["会话树"]
        Compact["Compaction"]
        Prompt["Prompt 装配"]
        Tools["7 个工具"]
        TUI["终端 UI"]
        Config["配置系统"]
        Ext["Extension"]
        Skill["Skill"]
    end
    
    Agent -->|事件流| Session
    Agent -->|事件流| TUI
    Loop -->|回调| Compact
    Loop -->|回调| Prompt
    Loop -->|回调| Tools
    Agent -->|注册| Ext
    Agent -->|注册| Skill
    Agent -->|回调| Config
    
    style Core fill:#e3f2fd
    style External fill:#e8f5e9

这种比例(核心 24%,外围 76%)不是偶然的。它反映了一个判断:agent 系统的核心应该是一个协议(事件流 + 回调),而不是一个框架(内建的功能集合)。

协议 vs 框架:用例子说清楚

"协议式设计"和"框架式设计"是两种根本不同的架构策略。用具体例子对比:

例子 1:添加一个新工具

框架式(假设的 AgentFramework):

// 框架内建了工具注册系统,你在框架的约束内添加
class MyAgent extends AgentFramework {
  @Tool({ name: "search", schema: searchSchema })
  async search(query: string) {
    return await doSearch(query);
  }
}

协议式(pi):

// 工具只是一个满足 AgentTool 接口的对象
const searchTool: AgentTool<typeof searchSchema> = {
  name: "search",
  label: "search",
  description: "Search for matching content",
  parameters: searchSchema,
  async execute(_toolCallId, { query }) {
    return await doSearch(query);
  },
};
// 传入 Agent 的 initialState.tools 即可

区别不在语法糖,而在控制权。框架式设计中,工具的生命周期由框架管理(注册、发现、权限检查都内建)。协议式设计中,工具只是一个数据结构,产品层可以自由地创建、组合、替换。

例子 2:实现上下文压缩

框架式

// 框架内建了 compaction 策略
agent.setCompactionStrategy("summarize", {
  threshold: 100000,
  model: "claude-haiku",
});

协议式(pi):

// transformContext 回调可以做任何事
const config: AgentLoopConfig = {
  transformContext: (messages) => {
    // 你自己决定压缩策略
    if (estimateTokens(messages) > threshold) {
      return compactMessages(messages);
    }
    return messages;
  },
};

框架式更方便(一行配置),协议式更灵活(可以实现框架没预见到的策略)。

例子 3:多产品复用

这是协议式设计的杀手级优势。pi 的同一个内核被三个完全不同的产品使用:

pi CLI(终端 coding agent)
├── 使用:TUI 渲染、本地文件系统工具、自定义权限钩子
├── 不使用:Slack API、Docker sandbox

mom(Slack bot)
├── 使用:Docker sandbox、Slack 消息输出、事件调度
├── 不使用:TUI、同步确认流、本地工具权限

web-ui(浏览器 IDE)
├── 使用:浏览器内 Agent 视图、proxy-aware streamFn、IndexedDB 存储
├── 不使用:TUI、Slack API、Docker sandbox

如果内核是框架式的(内建了 TUI、本地文件工具、权限 popup),mom 和 web-ui 要么 fork 框架,要么在框架上面做大量适配。pi 的协议式内核没有这些假设 — 它只定义"模型怎么调"和"循环怎么跑",产品层自行决定其余一切。

与竞品的架构对比

以下对比是结构性的(架构选择),不是评价性的(孰优孰劣)。每种选择都有其适用场景。

Claude Code

Claude Code 是 Anthropic 官方的 CLI agent。架构对比:

维度piClaude Code
模型层多 provider 抽象单一 Anthropic API
循环引擎可组合纯函数(agentLoop)内建循环 + 工具管理
工具系统外置,通过接口注入内建标准工具集
扩展机制Extension API + SkillSlash command + CLAUDE.md
会话管理外置 SessionManager内建会话持久化
UI 层独立的 pi-tui 包内建终端 UI
多产品CLI / Slack / WebCLI 为主,headless 模式

关键差异:Claude Code 是单一产品优化的设计 — 它只需要支持一个 provider(Anthropic)、一个 UI(终端)、一组工具。这让它可以把更多功能内建,降低上手成本。pi 是多产品基座的设计 — 它需要支持多个 provider、多个 UI、多个产品形态,所以必须把更多东西外置。

Cursor / Windsurf(IDE Agent)

维度piIDE Agent(Cursor 类)
宿主独立进程IDE 插件(VS Code extension)
编辑操作工具调用 → Edit/Write直接操作 IDE API
上下文手动管理(transformContext)IDE 提供语义索引
文件导航Glob/Grep 工具LSP + 语义搜索
多模型用户可切换内建路由(不同任务用不同模型)

关键差异:IDE agent 有一个巨大优势 — IDE 本身就是上下文源(打开的文件、编辑历史、语言服务器)。pi 作为独立进程,需要通过工具(Glob、Grep、Read)来获取这些信息。但 pi 的独立性也意味着它不受 IDE 限制 — 可以在 Slack、Web、CI/CD 中运行。

Aider

维度piAider
语言TypeScriptPython
编辑策略LLM 生成 edit/write 工具调用LLM 生成 unified diff
循环模型通用 agent 循环专注于 code edit 循环
Git 集成通过 bash 工具内建 git 操作
上下文管理手动 + transformContextRepo map + 文件标签

关键差异:Aider 是垂直整合的 coding assistant — git、diff、编辑、测试一体化。pi 是水平分层的 agent 基座 — coding 只是其中一个用例。Aider 在纯代码编辑场景中更高效(diff 策略比 tool call 更节省 tokens),但 pi 的通用循环可以做 Aider 做不到的事(Slack bot、自动化管道等)。

"不内建"的判断标准

pi 如何决定一个功能是内建还是外置?有三个判断标准:

1. 这个功能是否产品相关?

如果功能的实现取决于产品形态,就应该外置。

  • 权限确认:CLI 弹终端 popup,Slack 发消息让用户回复,Web 弹 modal → 外置
  • 工具执行:CLI 在本地执行,mom 在 Docker 中执行 → 外置
  • 输出渲染:CLI 用 TUI,Slack 用 mrkdwn,Web 用 HTML → 外置
  • 流式事件格式:所有产品都需要相同的事件流 → 内建

2. 这个功能是否有多种合理实现?

如果功能有多种同样合理的实现方式,就应该外置为回调或接口。

  • compaction 策略:可以用 LLM 总结、可以按时间截断、可以按 token 预算裁剪 → 外置on("session_before_compact", ...) 钩子
  • 上下文变换:可以注入 plan 指令、可以过滤历史、可以动态切换模型 → 外置transformContext 回调
  • 消息序列化:每个 LLM 的消息格式不同 → 内建在 provider 层统一处理

3. 这个功能是否需要系统级别的一致性?

如果功能的不一致会导致系统行为不可预测,就应该内建。

  • 事件类型:所有组件必须用相同的事件定义 → 内建
  • 工具执行管道:prepare/execute/finalize 三阶段必须统一 → 内建
  • 工具的具体 schema:不同工具有不同参数 → 外置

与竞品的设计对比(决策矩阵)

设计维度pi典型框架式 Agent
Sub-agents不内建,用 tool call 组合内建 multi-agent orchestration
权限控制beforeToolCall 钩子内建 permission popup
Plan modetransformContext 组合内建 plan/execute 模式
会话存储SessionManager(产品层)内建 memory store
Prompt 管理AGENTS.md + skills内建 prompt registry
模型路由产品层选择 model内建 model router
错误恢复产品层实现 retry内建 retry + fallback
可观测性事件流 + 订阅内建 tracing/logging

pi 的每个"不内建"都对应一个"用更底层的机制组合出来"。不是没想到,是故意没做。

极简核心的代价

诚实地说,极简核心不是没有代价:

上手成本高。新用户面对的是"洋葱架构" — pi-ai、pi-agent-core、pi-coding-agent、pi-tui 四层,每层有自己的类型和回调。理解一个"消息从用户输入到模型回复"的完整路径,需要穿越所有四层。

重复劳动。不同的产品壳(CLI、mom、web-ui)各自实现了类似的功能(session 加载、system prompt 构建、工具权限)。虽然它们各有差异,但重复的部分不小。

文档负担。内建功能可以在框架文档中一次性说明。外置功能需要每个产品各自文档化其组合方式。

调试困难。bug 可能出现在内核、产品层、或两者的交互中。分层越多,追踪问题的路径越长。

这些代价是 pi 为多产品适应性付出的"税"。对于只需要单一产品的团队,框架式设计可能是更好的选择。

取舍分析

得到了什么

极致的适应性。同一个内核跑在终端(pi CLI)、Slack(mom)、浏览器(pi-web-ui)、GPU 集群(pods)。每个产品只需要实现自己需要的"壳"。内核的 24% 代码驱动了 100% 的产品形态。

长期可维护性。内核的变化不影响产品层(只要协议不变)。产品层的变化不影响内核。这种解耦在 monorepo 中已经得到验证 — mom 和 web-ui 可以独立演进,不需要协调内核修改。

放弃了什么

上手成本。用户需要理解"洋葱架构"的每一层才能有效使用 pi。没有"开箱即用"的体验 — 你要么接受默认配置,要么深入理解系统才能定制。

开发速度。在框架式系统中,添加一个新功能可能只需要调一个 API。在 pi 中,你可能需要理解三层的交互才能找到正确的注入点。


版本演化说明

本章核心分析基于 pi-mono v0.66.0 的架构快照。 极简核心的设计哲学从 pi 的第一个版本就确立了,后续版本只是在不扩大内核的前提下 通过 extension、skill、回调等机制添加新能力。行数统计可能随版本变化, 但内核与外围的比例预计保持稳定。