M02. 分层:包之间如何保持边界#
当你第一次打开 Pi 的 monorepo,目录数量会让人误以为它是一个“大 CLI”。更有效的读法不是从文件名背 API,而是先问:这一层能不能在没有上一层的情况下工作?它依赖的是数据类型、控制流,还是用户界面?
1. 从依赖方向开始#
可以先用这张简化图定位职责:
pi-coding-agent
/ | \\
pi-agent-core 会话/扩展 pi-tui
|
pi-aipi-ai 不需要知道什么是 Agent;pi-agent-core 可以用在非编码场景;pi-coding-agent 把编码工具、会话、扩展和模型选择组装起来;pi-tui 只负责终端 UI。分层的关键不是包的数量,而是依赖方向不能反过来:模型层不应该导入业务层的会话对象。
2. pi-ai:统一的是协议,不是模型能力#
不同供应商在消息格式、流式事件、thinking 参数、缓存字段和错误形态上都可能不同。pi-ai 负责把它们翻译到统一接口,例如模型对象、上下文对象和事件流。
import { getModel, stream } from "@earendil-works/pi-ai/compat";
import type { Context } from "@earendil-works/pi-ai";
const model = getModel("anthropic", "claude-sonnet-4-5");
const context: Context = {
systemPrompt: "You are a careful code reviewer.",
messages: [{ role: "user", content: "Explain the entry point." }],
};
const events = stream(model, context);
for await (const event of events) {
if (event.type === "text_delta") process.stdout.write(event.delta);
}这层只解决“如何向模型发送上下文并读取响应”。它不负责决定什么时候再调用一次,也不保存 session file。模型供应商或模型 ID 会变化,代码阅读应关注接口和适配器,而不是把某个模型名当架构的一部分。
3. pi-agent-core:把模型回复变成控制流#
核心 Agent 持有消息、工具、stream function 和循环钩子。它知道 assistant 是否要求工具、工具结果何时回来、下一轮是否继续,但不知道用户界面应该怎么画,也不应假设一定使用 JSONL 持久化。
这使得同一个核心可以被聊天 Agent、测试 Agent 或数据处理 Agent 复用。编码只是工具集合和系统提示词的一个应用场景。
4. pi-coding-agent:产品装配层#
这一层把核心 Agent 与实际编码环境接起来:模型注册、默认工具、系统提示词、扩展加载、会话管理、压缩、bash 执行和 SDK 工厂都在这里。AgentSession 是理解这一层的关键对象,因为它同时连接了 Agent、SessionManager、设置和扩展运行时。
5. pi-tui 为什么单独存在#
终端 UI 很容易把业务逻辑吸进去:组件直接读取 Agent 状态,Agent 又反过来调用组件,最后任何改动都牵一发而动全身。Pi 将 TUI 作为独立库,使得 headless SDK 不必携带终端渲染;同样,TUI 也可以服务于不是 Agent 的终端程序。
这是一种正交性设计:UI 关心“如何显示变化”,Agent 关心“变化为什么发生”。二者通过事件或状态投影连接,而不是通过互相调用内部方法连接。
6. 类型如何逐层增厚#
可以把层间转换看成三次增厚:
pi-ai用标准消息和模型对象表达一次请求。pi-agent-core在消息周围增加工具、循环和事件。pi-coding-agent再增加会话、扩展、文件操作和产品控制。
上层可以拥有更多信息,但不能要求下层理解上层的业务名词。一个很实用的检查是:暂时删除 pi-coding-agent 的导入,pi-agent-core 的类型和测试是否仍然有意义?如果没有,说明边界可能已经泄漏。
7. 读源码时的三个问题#
- 这个函数返回的是“数据”、“事件”,还是“控制流决定”?
- 这个状态只在当前运行有效,还是应该写入 session?
- 这一层是在翻译协议,还是在添加产品策略?
它们能帮助你避开最常见的误读:把 SDK 的便利方法当成核心 Loop,把会话持久化当成 Agent 必然具备的能力,或者把 TUI 的交互状态当成模型上下文。
8. 分层真正保护的是什么#
分层保护的不是目录整齐,而是变化的局部性:供应商协议变化时,主要影响 pi-ai;Loop 策略变化时,主要影响 pi-agent-core;会话格式变化时,主要影响 coding-agent;终端交互变化时,主要影响 pi-tui。如果一次小改动必须横穿四层,通常意味着某个抽象边界没有承载好状态或转换责任。
资料
本文依据 Pi 仓库、Agent 核心源码和 SDK 文档重新创作。