整体架构
pi 不是一个单体程序,而是四个可以独立使用的 npm 包,自底向上层层组装。理解这个分层,就理解了 pi 的一半——因为每一层都可以单独拿出来,拼进你自己的项目。
四层结构
分层的意义在于:每一层对上层只有一个小接口,上层可以随时被替换。不想用终端 UI?拿 pi-agent-core 做个 Web 界面。不想写 loop?直接用 pi-coding-agent 的 SDK,几行代码得到一个完整 agent。实战篇最后一章会完整走一遍这条"用积木拼 agent"的路线。
一次 prompt 的完整旅程
架构图是静态的,更有用的是跟着一次用户输入走完整个系统。下面这条链路基于 packages/agent/src/agent-loop.ts 的真实实现:
几个要点:
- 一个 turn = 一次 LLM 调用 + 由它触发的所有工具执行。loop 转到模型不再发起 toolCall 为止——pi 没有 max steps 之类的旋钮,作者的理由是"我从没遇到过需要它的场景"。
- 一切都是事件。loop 不直接碰 UI,它只发事件(
agent_start、turn_start、message_update、tool_execution_end……)。TUI、JSON 模式、RPC 模式都只是事件的不同消费者。这就是为什么 pi 能一个核心支持四种运行模式。 - 上下文在进入 LLM 前才转换。loop 内部始终使用
AgentMessage[](可以包含 UI 专属消息类型),只有在调 LLM 的边界上才通过convertToLlm转成 provider 需要的格式。上下文工程的全部控制权因此都在你手里。
coding-agent 内部:产品层如何组装
packages/coding-agent/src 大致分三块:
组装顺序(简化):main.ts 解析 CLI → 创建 ModelRuntime(选定 provider/模型)→ createAgentSession(...) 把系统提示词、内置工具、会话管理器、扩展、技能全部装进一个 AgentSession → 交给四种模式之一去消费事件流。
为什么这样设计
作者在构建手记里给出的理由非常一致:控制上下文,观测一切。
- 事件驱动 + 会话 JSONL 落盘,意味着 agent 的每一次呼吸都可以被检查、被后处理;
- 分层 + 小接口,意味着任何一层不合你意都可以换掉,而不需要 fork 整个项目;
- 核心只保留"loop + 四个工具",其余全部交给扩展——这正是下一篇设计思想要展开的主题。
接下来:极简主义:pi 的核心哲学 →