Pi Agent 学习笔记
Pi Agent 是 Mario Zechner(libGDX 创始人)开发的开源 AI 编程助手,核心理念是 radical simplicity:agent loop 仅 ~418 行 TypeScript,system prompt <1000 tokens,4 个核心工具。仓库 star 数以 GitHub 实时页面为准(本笔记补源时为 57k+)。
架构总览
pi-agent-architecture
指向原始笔记的链接
Pi 采用 4 层 monorepo 架构(NPM Workspaces),从底到顶:
| 层 | 包名 | 职责 |
|---|---|---|
| 1 | pi-mono | 构建基础设施 |
| 2 | pi-ai | 统一 LLM API,15+ provider(OpenAI / Anthropic / Gemini / Bedrock 等),300+ 模型 |
| 3 | pi-agent-core | Agent 执行引擎:Agent class、agentLoop、事件流、状态管理 |
| 4 | pi-coding-agent | 领域实现:CLI(pi 命令)、Session、工具集、扩展加载 |
旁路包:
- pi-tui — 终端 UI 库,差分渲染(只刷新变化部分),ANSI-aware
- pi-web-ui — Lit web components,聊天面板 + artifact sandbox
- pi-mom — Slack Bot,Docker sandbox 中安全执行
- pi-pods — vLLM GPU 部署管理(RunPod / DataCrunch)
Agent Loop
核心循环在 packages/agent/src/agent-loop.ts,~418 行。执行流程:
flowchart TD A[用户输入] --> B[transformContext\n裁剪历史 / 注入外部数据] B --> C[convertToLlm\nAgentMessage → 标准 LLM Message] C --> D[stream\n调用 LLM 流式响应] D --> E{响应含\ntool call?} E -- Yes --> F[beforeToolCall\n参数校验 / 可 block] F --> G[执行 tools\n并行 Promise.all 或串行] G --> H[afterToolCall\n可覆盖结果] H --> I{检查 steering\n消息队列} I -- 有 steering --> B I -- 无 steering --> D E -- No --> J{有 follow-up\n消息?} J -- Yes --> B J -- No --> K[输出结果 / 结束] style A fill:#a5d8ff,stroke:#1971c2 style D fill:#a5d8ff,stroke:#1971c2 style E fill:#ffc9c9,stroke:#e03131 style G fill:#b2f2bb,stroke:#2f9e44 style K fill:#dee2e6,stroke:#868e96
关键机制
消息生命周期
AgentMessage 是富类型(支持 user / assistant / toolResult + 自定义类型),发送给 LLM 前经过两步管线:
transformContext— 可选 hook,裁剪历史或注入外部数据(RAG、系统状态)convertToLlm— 过滤 UI-only 消息,转成标准 LLM 角色
Tool 执行模式
- 并行(默认):先串行校验参数确保状态一致,然后
Promise.all并发执行,事件按原始顺序 emit - 串行:逐个执行
Steering & Follow-up
- Steering — agent 执行中途注入消息(例如用户中断补充指令),每个 turn 开头检查
all模式:一次注入全部one-at-a-time:每 turn 注入一条
- Follow-up — agent 完成后追加任务(自动清理、二级任务等)
Tool Hooks
beforeToolCall— 参数校验后、执行前。可返回{ block: true }阻止执行afterToolCall— 执行后。可覆盖content/details/isError
工具系统
两个工具集:
| 集合 | 工具 | 用途 |
|---|---|---|
codingTools | read, bash, edit, write | 可修改文件系统 |
readOnlyTools | read, grep, find, ls | 只读探索 |
核心工具
- read — 读文件,支持图片(JPG/PNG/GIF/WebP → ImageContent),50KB / 2000 行截断,支持 offset + limit 分页
- edit — 精确替换(oldText / newText),exact match 失败时 fuzzy fallback(去尾部空格、Unicode 引号/破折号归一化)
- write — 整文件写入
- bash — shell 命令执行,流式输出 + 截断
文件安全
withFileMutationQueue — 以绝对路径为 key 的 Map<string, Promise>,串行化同一文件的并发写操作,防 race condition。
resolveToCwd / resolveReadPath — 确保所有路径操作在工作目录内。
扩展机制
通过 ExtensionRunner 管理插件生命周期,支持:
- Skills — 预设 prompt 模板
- Themes — UI 主题
- Pi Packages — NPM 包形式的扩展
- 动态工具注册 — 运行时添加自定义 tool(如 image-gen、sub-agent)
- Tool override — 替换内置工具行为
扩展示例见 packages/coding-agent/examples/extensions/(50+ 示例)。
二次开发
NPM 包分层
4 个 npm 包,按抽象层级由低到高:
| 包 | npm | 用途 |
|---|---|---|
packages/agent | @earendil-works/pi-agent-core | Agent loop、工具执行、事件流 |
packages/ai | @earendil-works/pi-ai | 统一多 provider LLM 流式 API |
packages/coding-agent | @earendil-works/pi-coding-agent | CLI + 完整 SDK,session、扩展 |
packages/tui | @earendil-works/pi-tui | 差分终端渲染库 |
按需引用:嵌入自己的应用只需 pi-agent-core + pi-ai;完整功能用 pi-coding-agent。
Extension 开发
扩展是一个导出 default 函数的 .ts 文件,参数为 ExtensionAPI,无需编译(jiti 加载):
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
export default function (pi: ExtensionAPI) {
// 注册工具、hook、命令等
}加载路径(优先级从高到低):
--extension path/to/ext.tsCLI 参数settings.json的"extensions"数组<cwd>/.pi/extensions/— 项目级~/.pi/agent/extensions/— 全局
自定义工具
通过 defineTool() + pi.registerTool() 注册:
import { defineTool, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { Type } from "@earendil-works/pi-ai";
const myTool = defineTool({
name: "hello",
label: "Hello",
description: "A greeting tool",
parameters: Type.Object({
name: Type.String({ description: "Name to greet" }),
}),
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
return {
content: [{ type: "text", text: `Hello, ${params.name}!` }],
details: { greeted: params.name },
};
},
// 可选:自定义 TUI 渲染
renderCall(args, theme, _context) { /* ... */ },
renderResult(result, opts, theme, _context) { /* ... */ },
});
export default function (pi: ExtensionAPI) {
pi.registerTool(myTool);
}核心接口 AgentTool:
execute()返回{ content, details, terminate? }executionMode—"parallel"|"sequential"prepareArguments— 参数预处理
事件 Hook
pi.on(event, handler) 可拦截/修改 agent 行为:
| 事件 | 触发时机 | 可拦截? |
|---|---|---|
tool_call | 工具执行前 | ✅ { block: true, reason } |
tool_result | 工具执行后 | ✅ 可修改结果 |
before_provider_request | LLM API 调用前 | ✅ 可修改消息 |
before_agent_start | session 首次 LLM 调用前 | ✅ 可修改 prompt |
input | 用户输入 | ✅ 可转换文本 |
turn_start / turn_end | 每轮开始/结束 | ❌ |
agent_start / agent_end | agent 开始/结束 | ❌ |
session_* | session 生命周期 | 部分可拦截 |
resources_discover | 资源发现 | ✅ 可注入 skills/prompts |
示例——权限网关:
pi.on("tool_call", async (event, ctx) => {
if (event.toolName === "bash" && isDangerous(event.input.command)) {
const choice = await ctx.ui.select("Allow?", ["Yes", "No"]);
if (choice !== "Yes") return { block: true, reason: "Blocked" };
}
});自定义 LLM Provider
三种方式,灵活度递增:
1. models.json(零代码) — 适合 OpenAI/Anthropic 兼容代理:
- 在
~/.pi/agent/models.json中配置 baseUrl 和模型参数
2. pi.registerProvider()(Extension 级) — 完整 provider:
pi.registerProvider("my-provider", {
baseUrl: "https://my-api.com",
apiKey: "$MY_API_KEY",
api: "my-api",
models: [{ id: "my-model", name: "My Model", ... }],
oauth: { /* PKCE OAuth 流程 */ },
streamSimple: myStreamFn, // 返回 AssistantMessageEventStream
});3. registerApiProvider()(pi-ai 底层) — 最低层注册
已有示例:custom-provider-anthropic/、custom-provider-gitlab-duo/。
SDK 嵌入
三个抽象层级,按需选择:
高层 — createAgentSession():
import { createAgentSession } from "@earendil-works/pi-coding-agent";
const { session } = await createAgentSession({
model: "claude-sonnet-4",
tools: ["read", "bash", "edit", "write"],
customTools: [myTool],
thinkingLevel: "medium",
});
session.subscribe((event) => {
if (event.type === "message_update") {
process.stdout.write(event.assistantMessageEvent.delta);
}
});
await session.prompt("What files are here?");
session.dispose();中层 — Agent class(pi-agent-core):
import { Agent } from "@earendil-works/pi-agent-core";
const agent = new Agent({
initialState: { systemPrompt: "...", model, tools: [...] },
beforeToolCall: async ({ toolCall }) => { /* 拦截 */ },
afterToolCall: async ({ toolCall, result }) => { /* 后处理 */ },
});
await agent.prompt("Hello!");底层 — agentLoop() async generator:
for await (const event of agentLoop([msg], context, config)) {
console.log(event.type);
}RPC 模式
Pi 可作为子进程运行,通过 stdio JSON 通信,任何语言都能驱动。详见 packages/coding-agent/docs/rpc.md。
自定义命令与快捷键
pi.registerCommand("todos", {
description: "Show todos",
handler: async (args, ctx) => { /* ... */ },
});
pi.registerKeybinding({
key: "ctrl+shift+t",
description: "Open todos",
handler: async (ctx) => { /* ... */ },
});配置文件一览
| 路径 | 用途 |
|---|---|
~/.pi/agent/auth.json | API 密钥、OAuth token |
~/.pi/agent/settings.json | compaction、retry、image 等设置 |
~/.pi/agent/models.json | 自定义模型定义 |
~/.pi/agent/extensions/ | 全局扩展 |
~/.pi/agent/prompts/ | prompt 模板 |
~/.pi/agent/themes/ | 自定义主题 |
<cwd>/.pi/extensions/ | 项目级扩展 |
<cwd>/.pi/skills/ | agent skills |
不支持的
- MCP(Model Context Protocol)— 不支持,代码库中零引用。Pi 用自己的原生扩展系统替代。
Session 管理
- JSONL 存储 — 每个 session 一个
.jsonl文件,追加写入 - 树状分支 — session 可以 fork/branch,历史是树结构而非线性
- Compaction — 上下文窗口管理:auto-compaction、不活跃分支摘要化、
CompactionEntrypipeline
执行模式
| 模式 | 说明 |
|---|---|
| Interactive TUI | 默认,差分渲染终端 UI |
| Print / JSON | 非交互输出,适合脚本 |
| RPC | 进程间通信 |
| SDK embedding | 嵌入其他应用 |
与 Claude Code / Codex 对比
| 维度 | Pi Agent | Claude Code | Codex CLI |
|---|---|---|---|
| 开源 | ✅ MIT | ❌ 商业 | ✅ |
| 模型 | 任意(15+ provider) | Claude only | OpenAI only |
| Token 开销 | 低(system prompt <1000 tok) | 高(~3-4x) | 中 |
| 核心工具 | 4 (read/write/edit/bash) | ~15+ | ~10 |
| 扩展 | Skills / Packages / Themes | MCP | 有限 |
| Session | JSONL tree branching | 内置 | 异步 PR 导向 |
| 优势 | 轻量、透明、可 hack | 生态成熟、多工具 | CI/异步工作流 |
值得借鉴的设计
- 极简 agent loop — 418 行包含全部核心逻辑,降低理解和 debug 门槛
- 消息管线 —
transformContext→convertToLlm的两步转换让 AgentMessage 支持自定义类型,UI 消息和 LLM 消息解耦 - File Mutation Queue — 用 promise 链串行化同文件写操作,简洁有效
- Steering — 中途注入消息的能力让 agent 可被人类实时校正
- System prompt <1000 tokens — 证明了”少即是多”,prompt 越短模型越容易遵循
参考依据
补充时间:2026-05-30。以下为本笔记主要依据,后续以官方仓库最新内容为准。
- 仓库主页(stars、许可证、整体定位):https://github.com/earendil-works/pi
- 项目 README(monorepo 分层、核心理念、子包概览):https://raw.githubusercontent.com/earendil-works/pi/main/README.md
- Agent loop 源码(核心循环与 hook 机制):https://raw.githubusercontent.com/earendil-works/pi/main/packages/agent/src/agent-loop.ts
- 扩展机制文档(extensions、注册方式、加载路径):https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/docs/extensions.md
- 自定义 provider 文档(models.json、registerProvider 等):https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/docs/custom-provider.md
- 示例扩展目录(扩展样例集合):https://github.com/earendil-works/pi/tree/main/packages/coding-agent/examples/extensions
- RPC 文档(子进程 + stdio JSON):https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/docs/rpc.md