pi-atelier:让 AI 编程助手变得专业
这本书是给谁看的?
如果你在做以下任何一件事,这本书适合你:
- 用 AI 编程助手(如 pi、Cursor、Copilot)写代码
- 觉得 AI 助手“差一点“就能做得更好
- 想让 AI 从“问答工具“进化成“项目搭档“
什么是 pi-atelier?
pi-atelier 是一组 pi 扩展,让 AI 编程助手具备项目管理能力。
普通 AI 助手能写代码,但:
- 每次开会话都忘光之前的事
- 做大任务容易跑偏
- 没有规矩,容易犯低级错误
- 会话一长就变笨
pi-atelier 的扩展补上了这些能力缺口:
🧠 记忆
让 AI 记住跨会话的知识
📋 规划
管理 Epic → Story → Task 三层路线图
🛡️ 守卫
给 AI 立规矩,防止犯错
🔍 诊断
控制上下文质量 + token 消耗分析
📊 分析
搜索和回溯历史会话
🗜️ 压缩
长会话中保持 AI 的聪明
详细对比见下表:
| 能力 | 扩展 | 一句话描述 |
|---|---|---|
| 记忆 | pi-memory | 让 AI 记住跨会话的知识 |
| 规划 | pi-roadmap | 让 AI 管理 Epic → Story → Task |
| 守卫 | pi-shepherd | 给 AI 立规矩,防止犯错 |
| 上下文与诊断 | pi-context-manager | 控制 AI 看到的信息质量 + token 消耗诊断 |
| 日志 | pi-journal | 日志报告生成(git 活动 + 会话事件 + 记忆变更) |
| 分析 | pi-session-analyzer | 搜索和回溯历史会话 |
| 压缩 | pi-smart-compact | 长会话中保持 AI 的聪明 |
| 定时 | pi-scheduler | 定时提醒和周期任务 |
| 工作流 | pi-workflow | 子代理编排,并行执行 |
| 工具库 | pi-shared-utils | 扩展开发的公共工具函数 |
阅读路线
快速上手路线(1 小时)
- 第一章:一个 AI 的记忆 → 5 分钟装上 pi-memory
- 第二章:从记忆到规划 → 学会用路线图管理任务
- 第七章:自己动手做扩展 → 了解扩展机制
全面了解路线(3 小时)
按顺序读完所有章节。每章包含:
- 痛点:你一定会遇到的真实问题
- 原理:扩展是怎么工作的
- 案例:真实的使用场景
- 最佳实践:怎么用得更好
按需查阅路线
遇到具体问题时,直接翻到对应章节。每章独立完整。
快速安装
所有扩展包已发布到 npm,在 pi 的 settings.json 中添加你需要的扩展:
推荐组合(日常开发)
{
"packages": [
"npm:pi-shepherd",
"npm:pi-ate-memory",
"npm:pi-roadmap",
"npm:pi-ate-smart-compact"
]
}
全量安装
{
"packages": [
"npm:pi-shepherd",
"npm:pi-ate-memory",
"npm:pi-roadmap",
"npm:pi-context-manager",
"npm:pi-session-analyzer",
"npm:pi-ate-smart-compact",
"npm:pi-ate-scheduler",
"npm:pi-ate-workflow",
"npm:@pi-atelier/shared-utils",
"npm:pi-usage-stats",
"npm:pi-journal"
]
}
大部分扩展都是 开箱即用——安装后无需额外配置(但你可以按需定制)。
💡 提示:pi-ate-workflow 和 @pi-atelier/shared-utils 是供其他扩展调用的开发库,一般用户不需要直接安装。
💡 安装方式:
npm:前缀表示从 npm registry 安装。也可以用git:github.com/catlain/pi-<name>从源码安装(适合开发者)。
包名速查
| 功能 | npm 包名 | 源码仓库 |
|---|---|---|
| 守卫 | pi-shepherd | catlain/pi-shepherd |
| 记忆 | pi-ate-memory | catlain/pi-memory |
| 规划 | pi-roadmap | catlain/pi-roadmap |
| 上下文与诊断 | pi-context-manager | catlain/pi-context-manager |
| 会话分析 | pi-session-analyzer | catlain/pi-session-analyzer |
| 智能压缩 | pi-ate-smart-compact | catlain/pi-smart-compact |
| 定时任务 | pi-ate-scheduler | catlain/pi-scheduler |
| 工作流 | pi-ate-workflow | catlain/pi-workflow |
| 日志报告 | pi-journal | catlain/pi-journal |
| 使用统计 | pi-usage-stats | catlain/pi-usage-stats |
| 工具库 | @pi-atelier/shared-utils | catlain/pi-shared-utils |
重要文件路径
在开始之前,你需要知道 pi 的关键文件在哪里:
| 文件 | 路径 | 说明 |
|---|---|---|
| 全局配置 | ~/.pi/agent/settings.json | 安装扩展、配置 provider |
| 项目配置 | .pi/settings.json(项目根目录) | 项目级自定义配置(覆盖全局) |
| 项目指令 | AGENTS.md(项目根目录或 .pi/agent/) | 注入给 AI 的项目规则 |
| 扩展安装目录 | ~/.pi/agent/npm/node_modules/ | npm 包安装位置 |
| 记忆目录 | .pi/memory/(项目级) | 项目级持久记忆 |
| 全局记忆 | ~/.pi/agent/memory/ | 跨项目通用记忆 |
💡 新手提示:
~指你的用户主目录。在 macOS/Linux 上是/home/你的用户名/,Windows 上是C:\Users\你的用户名\。
约定
本书中的示例使用以下约定:
代码块:命令、文件路径、代码片段- 粗体:重要概念
-
💡 提示:实用技巧和注意事项
- 表格:快速对比和速查
准备好开始了吗?翻开第一章,让我们从“记忆“开始。
一个 AI 的记忆
你可能遇到过这种情况
你正在用 AI 编程助手开发一个项目。第一天的会话里,你花了半小时向 AI 解释:
- 项目用的是 Rust + Axum 技术栈
- 数据库用的 DuckDB 而不是 PostgreSQL
- 认证模块用的 JWT,不是 Session
- 部署目标是 ARM 架构的嵌入式设备
AI 听懂了,帮你写了完美的代码。
第二天,你开了一个新会话,AI 全忘了。它又开始建议你用 Express.js、连 PostgreSQL、用 Session 认证……
💡 这就是 AI 的“金鱼记忆“问题:每次新会话都是一张白纸。
记忆:让 AI 拥有跨会话的知识
pi-memory 就是来解决这个问题的。它给 AI 装上了一个“笔记本“:
- 自动记录:AI 在会话中做出的架构决策、踩过的坑、达成的共识
- 自动加载:每次新会话开始时,AI 自动读取之前的关键知识
- 按项目隔离:不同项目的记忆互不干扰
工作原理
┌─────────────────────────────────────────┐
│ AI 会话 │
│ │
│ ┌──────────┐ ┌──────────────────┐ │
│ │ 用户对话 │ ──→ │ AI 自动提取知识点 │ │
│ └──────────┘ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ memory_update │ │
│ │ 写入记忆文件 │ │
│ └─────────────────┘ │
│ │
│ ┌──────────┐ ┌──────────────────┐ │
│ │ 新会话 │ ──→ │ before_agent_start│ │
│ │ 开始 │ │ 自动加载记忆索引 │ │
│ └──────────┘ └──────────────────┘ │
└─────────────────────────────────────────┘
自动注入:每轮对话自动加载记忆索引
pi-memory 在每个会话开始时(before_agent_start 事件)会自动执行以下操作:
- 读取
memory-prompt.md— 记忆系统的使用说明(告诉 AI 有记忆功能、文件在哪) - 读取
MEMORY.md索引 — 全局~/.pi/agent/memory/MEMORY.md+ 项目.pi/memory/MEMORY.md - 注入到 system prompt — AI 在每轮对话开头就能看到所有记忆的标题和关键词
这意味着 AI 不需要主动去“查“记忆——记忆索引已经在它的上下文里了。AI 看到 JS_replace_$陷阱 这样的标题,就知道有这条记忆存在,需要时用 read 工具读取完整内容。
⚠️ 注入的是索引,不是全部内容。MEMORY.md 里只有标题和关键词,不是完整的记忆内容。AI 需要
read具体文件才能获取细节。
记忆的核心结构:
| 组件 | 作用 |
|---|---|
MEMORY.md | 索引文件,列出所有记忆的标题和关键词(自动注入到每轮对话) |
记忆文件(.md) | 每个文件一个主题,包含具体知识点(按需读取) |
memory_update 工具 | AI 用来写入/更新记忆文件 + 自动更新 MEMORY.md 索引 |
memory_index 工具 | AI 用来查看已有记忆(手动查询) |
before_agent_start 钩子 | 会话开始时自动注入记忆索引到 system prompt |
记忆文件的格式
每个记忆文件遵循统一格式:
# 标题
关键词:`kw1` `kw2` `kw3` ...
## 具体内容
- 知识点 1
- 知识点 2
- 决策记录:为什么选择了方案 A 而不是方案 B
文件命名规则:主题--关键词1,关键词2,关键词3.md
例如:数据库选型--DuckDB,嵌入式,ARM,列存,分析查询.md
实际案例:新项目的第一周
让我们看一个真实的场景。假设你在开发一个数据分析工具:
第 1 天:项目初始化
你和 AI 讨论了技术选型,决定用 Python + FastAPI + DuckDB。会话结束时,AI 自动写入记忆:
# 技术栈决策
关键词:`Python` `FastAPI` `DuckDB` `技术选型`
## 选择理由
- FastAPI:异步支持好,自动生成 API 文档
- DuckDB:嵌入式分析数据库,无需单独部署,适合单机分析场景
- 不选 PostgreSQL:项目不需要并发写入,嵌入式更简单
- Python 3.12+:用了新的类型语法
第 3 天:踩了一个坑
你在 DuckDB 的日期处理上遇到了问题——默认时区是 UTC,但你的用户在中国。AI 帮你解决后,写了一条记忆:
# DuckDB时区问题
关键词:`DuckDB` `时区` `日期` `UTC` `Asia/Shanghai`
## 问题和解决方案
DuckDB 默认用 UTC 时区。查询 `SELECT NOW()` 返回 UTC 时间。
解决:在连接时设置 `SET timezone = 'Asia/Shanghai'`
注意:不要在 SQL 层面做时区转换,在 Python 层用 datetime 处理更可靠。
第 7 天:新会话,无需重复解释
你开了一个新会话说“给分析接口加个导出 CSV 的功能“。AI 已经知道:
- 项目用 FastAPI + DuckDB
- 时区用 Asia/Shanghai
- 数据库查询在 Python 层处理
不需要你再说一遍。
✨ 这就是记忆的价值:省去了每次重复解释的 30 分钟。
最佳实践
✅ 应该记住的内容
- 技术决策:为什么选了 A 而不是 B
- 踩过的坑:遇到的问题和解决方案
- 项目约定:命名规范、目录结构、部署方式
- 架构知识:模块关系、数据流、关键接口
❌ 不应该记住的内容
- 临时调试信息(“这个变量值是 42”)
- 已经过时的结论(记得定期清理)
- 通用编程知识(AI 本来就知道怎么写 for 循环)
记忆文件管理
记忆不是越多越好。系统设有多层保护机制:
| 阈值 | 行为 |
|---|---|
| 20 个 | 提示注意控制数量 |
| 25 个 | 警告接近上限,建议清理合并 |
| 40 个 | 硬拒绝写入,必须先清理 |
清理方法:
- 合并:同主题的多文件合并为一个
- 归档:过时的结论被新结论取代,删掉旧的
- 拆分:单文件超过 200 行时,按子主题拆分
此外,记忆系统还有冲突检测——如果新文件与已有记忆的 topic 相同或关键词重叠 3 个以上,会直接拒绝写入,强制先处理冲突(合并或覆盖)。
配置
pi-memory 通过 pi 的 settings.json 安装:
{
"packages": [
"pi-memory"
]
}
无需额外配置,安装即用。记忆文件存储在项目的 .pi/memory/ 目录下。
记忆的作用域
| 作用域 | 路径 | 用途 |
|---|---|---|
| 项目级 | .pi/memory/ | 项目特有的知识(架构、决策、坑) |
| 全局 | ~/.pi/agent/memory/ | 跨项目通用的知识(工具链、编码纪律) |
进阶场景:记忆清理与知识演化
场景:记忆碎片化了
用了一个月,记忆文件越来越多:
.pi/memory/
├── 数据库选型--DuckDB,嵌入式,ARM.md
├── DB选型--数据库,DuckDB,性能.md ← 跟上一个重复!
├── 部署问题--Docker,ARM,内存.md
├── 部署问题2--Docker,内存,OOM.md ← 跟上一个也重复!
├── auth-bug--JWT,过期,refresh.md
├── fastapi-cors--CORS,FastAPI,跨域.md
├── test-tricks--vitest,mock,测试.md
└── ... (还有 20 个文件)
AI 在每次写入前会自动检查(memory_index),如果发现同主题的已有记忆,会先合并再写入。但如果碎片化已经发生,需要手动清理。
清理步骤:
- 让 AI 执行
memory_index,查看当前所有记忆 - 标记同主题的文件(关键词 3+ 重叠)
- 让 AI 读取标记的文件,合并为一个
- 用
memory_update覆盖写入到已有文件的文件名(而不是新建文件名,否则会触发冲突检测被拒绝) - 删除旧的碎片文件
场景:旧结论被推翻了
上个月写的记忆说“用 Express.js 做后端“,但这个月项目决定迁移到 FastAPI。每次 AI 读到旧记忆都会用 Express 的思路,产生错误的建议。
处理方式:用 memory_update 覆盖旧文件,写入新结论:
# 后端框架选型
关键词:`FastAPI` `Python` `迁移`
## 当前决策
2026-05: 从 Express.js 迁移到 FastAPI。
## 迁移原因
- 需要更好的异步支持
- Python 生态的数据分析库更丰富
- Express.js 版本已归档,不再更新
> ⚠️ 旧结论(已废弃):使用 Express.js + TypeScript
关键:不要只删旧文件——把新结论和旧结论的关系写清楚,这样 AI 不会在别的上下文里又“发明“出旧的方案。
场景:跨项目的知识复用
你在项目 A 里踩了一个坑,想确保项目 B 不会重蹈覆辙。
方案:把通用知识写到全局记忆:
~/.pi/agent/memory/
└── npm-file引用陷阱--npm,file引用,node_modules,缓存.md
全局记忆对所有项目可见。这样不管在哪个项目里,AI 都会知道“npm file: 引用有缓存陷阱“。
原则:
- 项目特有的知识(这个项目的架构、约定)→ 项目级
.pi/memory/ - 通用经验(工具链踩坑、编码纪律)→ 全局
~/.pi/agent/memory/
下一步
现在 AI 有了记忆,能记住过去的知识。但面对一个大项目,它知道第一步该做什么、第二步该做什么吗?
下一章,我们来看如何让 AI 学会规划。
从记忆到规划
你可能遇到过这种情况
你让 AI 做一个“把项目从 JavaScript 迁移到 TypeScript“的大任务。
前 30 分钟一切顺利——AI 按计划迁移了配置文件、类型定义、核心模块。但到第 5 个文件的时候,AI 开始“跑偏“:
- 它忘了之前的约定,开始用不同的命名风格
- 它跳过了测试文件的迁移
- 它开始做一个你没要求的“顺便重构“
- 当你提醒它回到正轨,它已经不记得原计划的前 3 步是什么了
💡 金鱼记忆解决了,但还有“注意力涣散“问题:AI 记得住知识,但管不住任务。
路线图:让 AI 学会管理复杂任务
pi-roadmap 给 AI 装上了“项目管理大脑“:
- 结构化拆解:把大目标拆成 Epic → Story → Task 三层
- 进度追踪:每个任务有明确的状态(todo / doing / done / blocked)
- 持久化:路线图保存在文件中,新会话也能继续推进
- 优先级排序:自动推荐下一步该做什么
为什么是三层结构?
Epic(大方向)
└── Story(可交付的工作块)
└── Task(最小执行单元)
这个结构来自敏捷开发的实践,但做了一点适配:
| 概念 | 传统敏捷 | pi-roadmap | 理由 |
|---|---|---|---|
| Epic | 2-8 周 | 一个完整项目方向 | AI 会话不会持续几周 |
| Story | 1-3 天 | 1-3 个会话可完成 | 适配 AI 的工作节奏 |
| Task | 0.5-1 天 | 30 分钟 - 2 小时 | AI 单次能专注的粒度 |
工作原理
用户描述目标
│
▼
┌──────────────────────────────────┐
│ roadmap_plan │
│ AI 分析目标 → 拆解为三层结构 │
│ 对比已有路线图 → 增量更新 │
└──────────────┬───────────────────┘
│
▼
┌──────────────────────────────────┐
│ ~/.pi/roadmap/<id>.roadmap.json │
│ 全局存储,跨会话跨项目可用 │
│ + 项目级 .pi/roadmap/roadmap.json│
│ (自动同步派生) │
└──────────────┬───────────────────┘
│
┌─────────┼─────────┐
▼ ▼ ▼
roadmap_list roadmap_show roadmap_next
查看所有 查看详情 推荐下一步
实际案例:迁移一个多包项目
让我们看一个真实的场景——把 12 个 npm 包同时做文档升级:
步骤 1:创建路线图
你说:“帮我规划一下给所有包写文档的工作。”
AI 调用 roadmap_plan,自动拆解:
{
"roadmapId": "package-docs",
"title": "包文档升级",
"epics": [
{
"id": "E0",
"title": "制定模板并验证",
"stories": [
{
"id": "E0.S0",
"title": "分析最佳实践,提炼模板",
"tasks": [
{ "id": "E0.S0.T0", "title": "调研 GitHub 文档规范" },
{ "id": "E0.S0.T1", "title": "提炼 README 模板" },
{ "id": "E0.S0.T2", "title": "用第一个包验证模板" }
]
}
]
},
{
"id": "E1",
"title": "批量升级所有包",
"stories": [
{ "id": "E1.S0", "title": "核心扩展(4个包)" },
{ "id": "E1.S1", "title": "工具扩展(4个包)" },
{ "id": "E1.S2", "title": "辅助扩展(4个包)" }
]
}
]
}
步骤 2:按计划推进
每次新会话,你说“继续“。AI 调用 roadmap_next:
📊 推荐下一步任务:
E0.S0.T1 — 提炼 README 模板(high priority)
所属:E0 制定模板并验证 > S0 分析最佳实践
是否开始?
步骤 3:完成标记
AI 完成一个任务后,调用 roadmap_done:
✅ E0.S0.T1 已完成
产出:templates/README-template.md
步骤 4:遇到阻碍
如果某个包的源码缺失,AI 可以标记任务为 blocked:
⚠️ E1.S1.T3 已阻塞
原因:pi-journal 的 API 文档不完整,需要先补充源码注释
路线图 vs 记忆:什么关系?
| 维度 | 记忆(pi-memory) | 路线图(pi-roadmap) |
|---|---|---|
| 存什么 | 知识、决策、踩坑 | 任务、进度、计划 |
| 粒度 | 自由文本 | 结构化 JSON |
| 查询方式 | 关键词 | 状态/优先级 |
| 生命周期 | 长期保留 | 项目结束可归档 |
简单来说:
- 记忆是“我知道什么“
- 路线图是“我要做什么、做到哪了“
两者互补:记忆帮 AI 不忘知识,路线图帮 AI 不忘任务。
最佳实践
✅ 好的 Epic 拆解
Epic: 发布 npm 包
Story: 准备发布环境
Task: 配置 package.json 的 exports 字段
Task: 添加 bundledDependencies 配置
Task: 配置 tsconfig 的 declaration 输出
Story: 编写文档
Task: 完成 README.md
Task: 添加 CHANGELOG.md
❌ 不好的拆解
Epic: 把所有事情做完 ← 太模糊,没有方向
Story: 做第一步 ← 没说做什么
Task: 开始干活 ← 无法执行
拆解的黄金法则
- Epic 的标题应该是一个动词短语:“发布 npm 包“而不是“npm”
- Story 应该有明确的交付物:“完成 README“而不是“写文档”
- Task 应该 30 分钟内可执行:“配置 package.json 的 name 字段“而不是“配置构建”
- 同层级的项应该是同粒度:不要一个 Story 有 2 个 Task,另一个有 20 个
进阶场景:计划调整与进度追踪
场景:需要改变方向
计划赶不上变化。昨天规划好的路线图,今天发现需求变了。你不需要重新建一个——用 roadmap_plan 更新即可:
你:昨天规划的重构方案太大了,我想先只做认证模块。
AI 调用 roadmap_plan(action="update"):
→ 对比当前路线图和你的新需求
→ 保留已完成的任务不动
→ 标记不需要的任务为 dropped
→ 添加新的任务
关键原则:roadmap_plan 是增量更新,不是覆盖。已经 done 的任务永远不会被改回去。
场景:追踪谁做了什么
在多会话协作中,你经常想知道“这个 task 是哪个会话完成的?“Roadmap 自动追踪:
roadmap_show(roadmapId="package-docs")
结果:
E0.S0.T0 调研 GitHub 文档规范 ✅ by: 8740-8fce3e7af232
E0.S0.T1 提炼 README 模板 ✅ by: b8b5-85516ead6253
E0.S0.T2 用第一个包验证模板 ✅ by: b8b5-85516ead6253
E1.S0.T0 核心扩展 - pi-shepherd 🔄 doing by: aa55-a4860e851afb
每个完成的 task 后面的 by: xxxx-xxxxxxxxxxxx 是会话 ID 的短格式(UUID 最后两段)。你可以用这个 ID 搜索到具体的会话:
session_search(action="grep", query="8740-8fce3e7af232")
→ 找到该会话,然后用 session_analyze(action="summary") 查看详情
场景:归档已完成的 Epic
项目做完了,不想让已完成的 Epic 占据视线:
roadmap_archive(roadmapId="package-docs")
→ 自动归档所有已完成的 Epic
→ 默认隐藏,需要时用 show_archived=true 查看
场景:不知道下一步做什么
打开 pi 时不知道该继续什么:
roadmap_next()
结果:
📊 推荐下一步任务(按优先级排序):
1. E1.S0.T3 — 配置 package.json 的 files 白名单 (high, todo)
2. E1.S1.T0 — 工具扩展 - pi-roadmap (medium, todo)
3. E2.S0.T0 — 调研 mdBook 主题定制 (low, todo)
roadmap_next 自动按 doing → todo、high → medium → low 排序,告诉你最该做什么。
进阶功能
搜索路线图:roadmap_search
当路线图越来越多、结构越来越复杂时,你可能想快速找到某个关键词相关的任务。roadmap_search 支持跨路线图全文搜索,匹配 title + description + note:
你:之前有没有规划过 CI 相关的任务?
AI 调用 roadmap_search(query="CI")
→ 在 3 个路线图中找到 7 个匹配项,显示完整层级上下文
任务依赖关系:dependsOn
复杂项目中,任务之间往往有先后顺序——B 必须等 A 做完才能开始。dependsOn 让你在 Story 和 Task 上声明依赖:
{
"id": "E1.S2.T1",
"title": "配置 CI 流水线",
"status": "todo",
"dependsOn": ["E1.S1.T3"]
}
roadmap_show 会自动展示依赖关系:
E1.S2 发布流程
E1.S2.T1 配置 CI 流水线 📋 depends: [E1.S1.T3]
E1.S2.T2 发布到 npm registry 📋 depends: [E1.S2.T1]
📖 依赖的添加和更新方式,见 pi-roadmap README。
保护机制
- 禁止向已归档/已完成的 Epic/Story 添加子项——避免在已完成的工作下追加任务
- 重复 ID 检查——
roadmap_update会检查 dependsOn 中的 ID 是否存在
下一步
AI 有了记忆(记住知识)和路线图(管理任务)。但有时候,AI 还是会“犯错“——改不该改的文件、用不该用的方式。
下一章,我们来看如何给 AI 立规矩。
给 AI 立规矩
你可能遇到过这种情况
你让 AI 帮你“修一下登录页面的样式“。30 秒后你看了一眼代码–
AI 不但改了样式,还:
- “顺手“重构了整个登录组件的目录结构
- 把 CSS 模块改成了 Tailwind(你项目没用 Tailwind)
- 删除了 3 个它认为“没用“的测试文件
- 把 package.json 里的依赖升级到了最新版
等你发现的时候,代码已经提交了。
💡 AI 越能干,越需要规矩。能力强但没有边界,造成的破坏反而更大。
两道防线:Shepherd 和 Context
pi-atelier 提供了两层防护机制:
第一道:pi-shepherd - 行为守卫
Shepherd(牧羊犬)是一个规则驱动的事件钩子引擎,在 AI 的关键动作前后做检查,相当于“门卫“。
AI 准备执行动作(工具调用)
│
▼
┌──────────────────────────────┐
│ Shepherd tool_call 钩子 │
│ 检查:该不该做?该怎么做? │
└──────┬───────────────────────┘
│
┌───┴────┐
│ │
放行 改写/阻止 + 提示原因
... 工具执行完毕 ...
┌──────────────────────────────┐
│ Shepherd tool_result 钩子 │
│ 检查:需要后续动作吗? │
└──────┬───────────────────────┘
│
注入提醒 / 追加动作
支持的钩子时机:
| 钩子 | 触发时机 | 典型用途 |
|---|---|---|
tool_call | AI 调用工具前 | 改写命令、阻止危险操作 |
tool_result | 工具执行后 | 自动提醒跑测试、lint 检查 |
message_end | AI 回复完成时 | 匹配回复文本,拦截错误猜测 |
agent_end | AI 完成对话时 | 提醒提交代码、更新记忆 |
session_shutdown | 会话关闭时 | 清理临时数据 |
Shepherd 的四种动作:
| 动作 | 效果 | 典型用途 |
|---|---|---|
block | 阻止工具执行 | 禁止危险操作 |
notify | 向 AI 上下文注入提醒 | “编辑了 TS 文件,记得跑测试” |
steer | 静默注入引导(不显示给用户) | 引导 AI 查阅规范 |
rewrite | 改写工具调用参数 | 自动给命令加前缀 |
第二道:pi-context-manager - 信息质量与诊断
Context Manager(上下文管家)控制 AI 能看到什么信息,还能帮你诊断 token 消耗问题。
核心能力:
- Distill(蒸馏):自动压缩工具返回的大段内容,保留关键信息
- Tool Result Processor(后处理器):对特定工具输出做格式化精简
- Aging(老化淘汰):自动淘汰长期未被引用的旧工具输出
- Payload 分析:用数据诊断 token 都花在哪了
工具返回大量内容(可能 50KB)
│
▼
┌────────────────────────┐
│ Context Manager │
│ Distill + Processor │
│ 压缩到 ~5KB 关键信息 │
└────────────────────────┘
│
▼
AI 看到精炼后的信息,做出更好的判断
详细原理见 3.3 Context Manager 原理。
实际案例:防止 AI 犯错
场景 1:编辑后自动提醒跑测试
{
"comment": "[TypeScript] 编辑后必须跑测试",
"hook": "tool_result",
"tool": "edit",
"action": "notify",
"conditions": [
{ "field": "path", "pattern": "\\.ts$", "flags": "" }
],
"reason": "编辑了 TypeScript 文件,必须跑覆盖该代码的单元测试(如无测试则先补充),修复所有测试问题确保通过。",
"enabled": true
}
当 AI 编辑了 .ts 文件后,Shepherd 自动提醒 AI 跑测试。
场景 2:会话结束提醒提交代码
{
"comment": "[收尾] 编辑后提醒 commit + 记忆更新 + 总结",
"hook": "agent_end",
"action": "notify",
"conditions": [{ "builtin": "has_edits" }],
"reason": "检测到文件编辑,执行收尾工作:\n1️⃣ Git commit...\n2️⃣ 更新记忆...\n3️⃣ 会话总结",
"stopReason": ["stop"],
"enabled": true
}
conditions: [{ builtin: "has_edits" }] 表示只有本轮会话确实编辑了文件才触发。stopReason: ["stop"] 表示只在 AI 正常结束(而非被中断)时触发。
场景 3:自动改写命令
{
"comment": "[rtk] 自动代理高频 bash 命令",
"tool": "bash",
"action": "rewrite",
"pattern": "^(git\\s+(status|log|diff)|cargo\\s+(test|build|clippy)|pytest)\\b",
"flags": "",
"reason": "rtk command rewrite:自动加 rtk 前缀压缩输出",
"enabled": true
}
当 AI 尝试执行 git status 等命令时,Shepherd 自动改写为 rtk git status(rtk 是一个输出压缩工具)。
场景 4:代码风格检查
{
"comment": "[TS] 禁止空格缩进 - TS 文件必须用 Tab",
"hook": "tool_call",
"tool": "edit",
"action": "notify",
"conditions": [
{ "field": "path", "pattern": "\\.ts$", "flags": "" },
{ "field": "text", "pattern": "\\n [\\S ]", "flags": "" }
],
"reason": "❌ TS 文件要求 Tab 缩进,不是空格。请用 Tab 缩进重写代码。",
"enabled": true
}
两个条件同时满足才触发:文件是 .ts 且代码内容包含空格缩进。
场景 5:连续出错时提醒翻记忆
{
"comment": "[debug] 工具反复出错时提醒翻记忆",
"hook": "tool_result",
"action": "steer",
"state": { "countKind": "errors", "gte": 5 },
"reason": "🔍 **工具反复出错**:连续失败多次,翻看 .pi/memory/ 目录下的记忆文件,看是否已有踩坑记录。",
"enabled": true,
"subagent": false
}
state 实现了状态追踪–Shepherd 会记住工具出错的次数,累计到阈值才触发。subagent: false 表示子代理中不触发此规则。
Shepherd 规则管理
规则可以通过两种方式管理:
shepherd_rules工具:让 AI 帮你安全地增删改规则,内置写入校验和回滚机制- 直接编辑 JSON 文件:全局规则在
~/.pi/agent/extensions/shepherd/rules.json,项目规则在.pi/extensions/shepherd-rules.json
规则文件修改后即时生效,无需重启。
📖 完整的字段说明和参数文档,见 pi-shepherd README。
典型用法:
# 让 AI 帮你添加一条规则
你:帮我加一条规则,编辑 .rs 文件后提醒跑 cargo clippy
AI 调用 shepherd_rules(action="add", rule={...})
# 查看项目级规则
AI 调用 shepherd_rules(action="list", scope="project")
Context 的配置方式
pi-context-manager 提供以下命令:
| 命令 | 用途 |
|---|---|
/record [on|off] | 开关 payload 录制 |
/context | TUI 面板:可视化上下文使用情况 |
/distill-config [N] | 查看/设置 distill token 阈值 |
/distill-config --cap [N] | 查看/设置首次全文上限(firstSeenCap,0 = 不设上限) |
/processor-config [N|off] | 查看/设置 tool-result-processor 阈值 |
/aging-config [N|off] | 查看/设置 aging 淘汰轮数 |
/context-clean [sessionId] | 清理持久化数据 |
💡 所有命令无参调用时显示当前配置和用法说明,例如直接输入
/distill-config即可查看当前阈值和用法。
详细原理见 3.3 Context Manager 原理。
最佳实践
✅ 好的规则设计
- 精确的条件:用
conditions限定触发范围,不要一刀切 - 清晰的提示:告诉 AI “为什么不行“和“应该怎么做”
- 分层防护:重要的事情用
block(强制阻止),次要的用notify(提醒),内部引导用steer(静默) - 善用状态追踪:连续出错 3 次再提醒,比每次都提醒更有效
❌ 不好的规则设计
- 过于频繁:每个工具调用都
notify,AI 会被提醒淹没 - 过于严厉:
deny所有bash命令,AI 连ls都不能执行 - 模糊的提示:
"reason": "注意"– 注意什么? - 忽略子代理:某些规则用
"subagent": false排除子代理场景,避免干扰独立任务
规则的优先级
当多条规则同时匹配时:
block>notify>steer(阻止 > 提醒 > 静默引导)- 同优先级下,按规则文件中的定义顺序依次执行
agent_end钩子中,check条件不满足的规则直接跳过
下一步
有了记忆、规划和规矩,AI 已经是一个靠谱的助手了。但一个会话做了很多事之后–你怎么知道它具体做了什么?哪些文件改了?哪些决策做了?
下一章,我们来看如何让 AI 学会复盘。
3.2 pi-shepherd 原理:规则驱动的钩子系统
Shepherd 是 pi-atelier 的“神经系统“–它不提供工具或命令,而是通过事件钩子连接所有其他扩展。
架构概览
pi 事件总线
│
├─ before_provider_request ← Shepherd 在此注入临时提示
│
├─ tool_call ← Shepherd 拦截/改写工具调用
│ │
│ ▼
│ 工具执行
│ │
│ ▼
├─ tool_result ← Shepherd 检查结果、触发后续动作
│
├─ agent_end ← Shepherd 触发收尾工作
│
└─ session_shutdown ← Shepherd 清理临时状态
核心概念
规则(Rule)
每条规则是一个 JSON 对象,定义了“什么时机、什么条件、做什么动作“:
规则 = 钩子时机(hook) + 匹配条件(conditions/pattern) + 动作(action) + 提示(reason)
动作类型详解
| 动作 | 注入方式 | 用户可见 | 典型用途 |
|---|---|---|---|
notify | 注入到 AI 上下文 | ✅ 是 | 提醒 AI 跑测试、lint |
steer | 静默注入 | ❌ 否 | 引导 AI 查阅规范 |
rewrite | 修改工具参数 | ✅ 是 | 自动给命令加前缀 |
block | 阻止执行 | ✅ 是 | 禁止危险操作 |
状态追踪(State)
Shepherd 内部维护了工具调用的状态计数器:
"state": { "countKind": "errors", "gte": 5 }
这表示“累计错误 ≥ 5 次时触发”。countKind 支持:
"errors":工具返回错误时计数"calls":工具被调用时计数
message_end 钩子
message_end 是一个特殊的钩子时机:它在 AI 回复完成之后触发,匹配的是 AI 输出的文本内容,而不是工具参数。这让 Shepherd 可以“监听” AI 说了什么,并在发现问题时注入纠正。
AI 回复完成
│
▼
┌──────────────────────────────┐
│ Shepherd message_end 钩子 │
│ 正则匹配 AI 回复文本 │
└──────┬───────────────────────┘
│
匹配到了?
├── 是 → steer 静默注入纠正(下一轮生效)
└── 否 → 无操作
与 tool_call/tool_result 的区别:
| 维度 | tool_call / tool_result | message_end |
|---|---|---|
| 匹配对象 | 工具参数(命令、文件路径等) | AI 的回复文本 |
conditions[].field | path 或 text(工具参数) | text(AI 回复) |
| 适用动作 | block/notify/rewrite/steer | 通常只用 steer |
| 典型用途 | 拦截危险命令、编辑后提醒 | 拦截错误猜测、引导修正 |
典型用法见 场景 7:拦截 AI 的错误归因猜测。
跨扩展通信
Shepherd 通过 pi.events 事件总线接收其他扩展的“提示“(hint):
其他扩展发出 hint → pi.events.emit("ephemeral:hint") → Shepherd 收集
│
before_provider_request 时 → Shepherd 把收集的 hints 注入 AI 上下文
这种机制让扩展之间可以协作而不需要直接依赖。
规则加载流程
1. 加载扩展包内的 rules.json(全局默认规则)
│
▼
2. 加载全局自定义规则 ~/?pi/agent/shepherd-rules.json(通过 shepherd_rules 工具管理)
│
▼
3. 扫描项目目录下的 .pi/shepherd-rules-*.json(项目规则)
│
▼
4. 规则叠加生效(项目规则覆盖同名全局规则)
规则文件变更后即时生效,无需任何操作。
shepherd_rules 工具的 scope 参数
shepherd_rules 工具的 scope 参数控制操作目标:
global(默认):操作~/.pi/agent/shepherd-rules.json,所有项目生效project:操作.pi/shepherd-rules.json,仅当前项目生效
list 操作不传 scope 时,返回全局 + 项目合并列表(标注每条规则的来源)。
配置
Shepherd 的配置支持三层合并(默认值 → 全局 settings → 项目 settings),可以在 .pi/settings.json 中按需覆盖。
📖 完整配置参数,见 pi-shepherd README。
下一步
了解 Shepherd 如何守卫 AI 行为后,下一节我们看 Context Manager 如何控制信息质量。
3.3 pi-context-manager 原理:信息质量控制与 Token 诊断
pi-context-manager 由原 pi-context 和 pi-payload-analyzer 合并而来,统一管理上下文质量和 token 诊断。
三个核心能力
1. Distill:压缩工具返回的海量信息
当工具返回大量内容(比如读取一个 1000 行的文件),pi-context-manager 会自动压缩,只保留关键信息:
原始工具输出(50KB)
│
▼
┌────────────────────────┐
│ Distill 处理器 │
│ 提取关键行 + 摘要 │
└────────────────────────┘
│
▼
压缩后输出(~5KB)
│
▼
AI 看到精炼后的信息
Distill 默认自动开启。有两个关键参数:
| 配置项 | 命令 | 说明 |
|---|---|---|
distillThreshold | /distill-config | 超过此 token 数的工具输出会被压缩 |
firstSeenCap | /distill-config --cap | 首次遇到的工具输出上限(0 = 不设上限) |
💡 firstSeenCap 的作用:有些工具第一次返回结果非常大(比如
ls列出一个大目录),但又不需要保留全部内容。firstSeenCap限制首次输出的最大 token 数,后续请求中如果该结果被 distill 处理会进一步压缩。
2. Tool Result Processor:智能格式化精简
Tool Result Processor 对特定工具的输出做结构化精简,比 distill 更精准:
- Code Graph 输出精简:自动压缩 AST 搜索结果,保留关键签名和位置
- MCP JSON 输出精简:压缩 MCP 工具返回的冗长 JSON
- 错误输出精简:截断过长的错误堆栈
- Web 搜索输出精简:只保留搜索结果的关键信息
通过 /processor-config 命令查看或设置处理阈值。
3. Aging:老化淘汰旧内容
在长会话中,早期的工具输出可能已经不再相关。Aging 机制自动淘汰“过时“的内容:
轮次 1: 工具输出 A(新鲜 🟢)
轮次 5: 工具输出 A(有点旧 🟡)
轮次 10: 工具输出 A(太旧了 🔴 → 自动删除)
Aging 的智能豁免:某些内容类型不会被老化淘汰,包括:
- 技能文件(SKILL.md)的内容
- 用户手动标记保留的内容
- 最近一次被 AI 引用的内容
通过 /aging-config 命令设置淘汰轮数,/aging-config off 禁用。
4. Payload 分析:用数据诊断上下文问题
会话变长后 AI 变笨?用 payload_analyze 找出原因。
⚠️ 重要:
payload_analyze是一个 AI 工具,不是终端命令。你需要在 pi 的聊天对话中让 AI 来执行它。例如:帮我用 payload_analyze 看一下当前的 token 使用情况或者更精确地指定:
运行 payload_analyze action="budget"
| 分析模式 | 怎么跟 AI 说 | 看什么 |
|---|---|---|
budget | “分析一下 token 预算分布” | system/tools/history 各部分的 token 占比 |
growth | “看看 token 增长趋势” | 随会话进行,token 是怎么膨胀的 |
expensive | “找最耗 token 的工具调用” | 最贵的工具调用 Top N |
overview | “详细分析 payload” | 逐消息的详细 token 分析 |
messages | “查看第 5 条消息” | 按索引/范围/关键词精确定位消息 |
chain | “追踪这个工具调用的命运” | 跨 payload 追踪同一个工具调用 |
diff | “对比两个 payload 的差异” | 找出两次请求之间的变化 |
stats | “看 distill/processor 的命中率” | 聚合统计压缩效率 |
💡 先用 budget,再深入:遇到上下文问题时,先用
budget看整体分布,然后用expensive定位大户,最后用messages精确查看某条消息。
/context TUI 面板
pi-context-manager 还提供了一个 TUI(终端界面)面板,让你可视化浏览上下文内容:
/context 命令
│
▼
┌─────────────────────────────────────┐
│ 📊 Context Panel │
│ │
│ [分类] [工具详情] [标记删除] │
│ │
│ ├─ System Prompt 4.2K tokens │
│ ├─ Tool Definitions 8.1K tokens │
│ ├─ Memory 2.3K tokens │
│ ├─ History 52K tokens │
│ │ ├─ 轮次 1-10 (已标记删除) │
│ │ ├─ 轮次 11-20 │
│ │ └─ 轮次 21-30 │
│ └─ Tool Results 64K tokens │
│ ├─ read(schema.ts) 8.2K 🔴 │
│ └─ grep("TODO") 4.1K 🟡 │
└─────────────────────────────────────┘
在面板中可以:
- 分类浏览:按类型查看上下文内容
- 工具详情:查看每个工具返回的完整内容
- 标记删除:手动标记不需要的内容,下次请求时自动排除
💡 无参调用即查看:所有
/distill-config、/processor-config、/aging-config命令不带参数时会显示当前配置和用法说明。📖 完整命令参考和参数说明,见 pi-context-manager README。
最佳实践
| 你遇到的问题 | 先做什么 | 再做什么 | 解决方案 |
|---|---|---|---|
| 会话 30 轮后 AI 变笨 | payload_analyze(action="growth") | 看 token 在哪个阶段暴涨 | 调低 distill 阈值 / 装智能压缩 |
| AI 忽略某些文件内容 | 检查 distill 配置 | 可能是 distill 过度压缩 | 调整 distillThreshold |
| 每次工具调用特别慢 | payload_analyze(action="expensive") | 找出最贵的 Top N 调用 | 限制大文件读取或拆分文件 |
| 旧工具输出占空间 | /aging-config 查看 | 设置合适的淘汰轮数 | Aging 自动淘汰 + 手动 /context 面板清理 |
| 首次工具输出太大 | /distill-config --cap | 设置首次全文上限 | firstSeenCap 限制首次输出大小 |
下一步
下一章,我们来看如何让 AI 学会复盘——自动记录会话事件,随时回溯历史。
3.5 Shepherd 实战场景
这一节通过真实场景展示如何用 Shepherd 规则解决 AI 编码中的常见问题。
场景 1:编辑代码后自动提醒跑测试
问题
AI 改了 TypeScript 代码但忘了跑测试。你每次都得手动说“跑一下测试“。
规则
{
"comment": "[TypeScript] 编辑后必须跑测试",
"hook": "tool_result",
"tool": "edit",
"action": "notify",
"conditions": [
{ "field": "path", "pattern": "\\.ts$", "flags": "" }
],
"reason": "编辑了 TypeScript 文件,必须跑覆盖该代码的单元测试(如无测试则先补充),修复所有测试问题确保通过。",
"enabled": true
}
效果
AI:我修改了 src/auth/login.ts 的空值检查逻辑。
🛡️ Shepherd 提醒:编辑了 TypeScript 文件,必须跑覆盖该代码的单元测试。
AI:好的,我来跑测试... ✅ 3 个测试全部通过。
场景 2:防止 AI 乱改别人的代码
问题
你在 team 项目里工作,工作区有同事的未提交改动。AI 一看“这里不对“,顺手就 git checkout 恢复了别人的文件。
规则
{
"comment": "[安全] 禁止 git checkout 恢复文件",
"hook": "tool_call",
"tool": "bash",
"action": "block",
"conditions": [
{ "field": "text", "pattern": "git\\s+checkout\\s+--", "flags": "" }
],
"reason": "❌ 禁止 git checkout 恢复文件!工作区里有别人的未提交改动,你没有权力决定哪些改动是"无关"的。",
"enabled": true
}
效果
AI 准备执行:git checkout -- src/config.ts
🛡️ Shepherd 阻止:禁止 git checkout 恢复文件!
AI:抱歉,我不会恢复别人的文件。让我看看其他方案...
场景 3:会话结束自动提交代码
问题
AI 改了一堆文件,会话结束了,但代码没提交。第二天发现工作区一团糟。
规则
{
"comment": "[收尾] 编辑后提醒 commit + 记忆更新 + 总结",
"hook": "agent_end",
"action": "notify",
"conditions": [{ "builtin": "has_edits" }],
"reason": "检测到文件编辑,执行收尾工作:\n1️⃣ Git commit...\n2️⃣ 更新记忆...\n3️⃣ 会话总结",
"stopReason": ["stop"],
"enabled": true
}
check: "has_edits" 确保只有真的改了文件才提醒,不会在纯聊天会话中干扰。stopReason: ["stop"] 确保只在 AI 正常结束时触发,被中断时不触发。
场景 4:编辑 .gd 文件后自动提醒跑架构检查
问题
你在做 Godot 游戏项目,AI 编辑了 .gd 文件后应该跑架构检查 + 格式化检查,但每次都得手动提醒。
规则(同一个文件可以有多条规则,按顺序执行)
{
"comment": "[arch] 编辑 .gd 文件后提醒跑编译验证",
"hook": "tool_result",
"tool": "edit",
"action": "notify",
"conditions": [
{ "field": "path", "pattern": "\\.gd$", "flags": "" }
],
"reason": "编辑了 .gd 文件,请跑 check_arch 验证架构合规。",
"enabled": true
},
{
"comment": "[format] 编辑 .gd 文件后提醒跑格式化检查",
"hook": "tool_result",
"tool": "edit",
"action": "notify",
"conditions": [
{ "field": "path", "pattern": "\\.gd$", "flags": "" }
],
"reason": "编辑了 .gd 文件,请跑 gdformat 格式化检查。",
"enabled": true
}
两条规则都会触发,AI 会依次跑架构检查和格式化检查。
场景 5:工具反复出错时自动提醒翻记忆
问题
AI 在连续出错——edit 匹配失败、bash 命令找不到、测试反复不过。它在同一个死胡同里绕圈。
规则
{
"comment": "[debug] 工具反复出错时提醒翻记忆",
"hook": "tool_result",
"action": "steer",
"state": { "countKind": "errors", "gte": 5 },
"reason": "🔍 **工具反复出错**:连续失败多次,翻看 .pi/memory/ 目录下的记忆文件,看是否已有踩坑记录。",
"enabled": true,
"subagent": false
}
关键点:
state: { "countKind": "errors", "gte": 5 }— 累计出错 5 次才触发,不会每次都提醒action: "steer"— 静默注入引导,不在用户界面显示subagent: false— 子代理中不触发,避免干扰独立任务
效果
AI 尝试 edit,失败...
AI 尝试 edit,失败...
AI 尝试 bash sed,失败...
AI 尝试 edit,失败...
AI 尝试 edit,失败...
🛡️ Shepherd 静默引导:翻看记忆文件。
AI:让我看看记忆... 找到了!记忆文件说"edit 匹配失败时先检查 CRLF"。
AI:运行 audit_format.py 检查格式... 果然是 CRLF 问题。
场景 6:自动改写高频命令
问题
AI 经常执行 git status、git log、npm test 等命令,这些命令的输出可能很长,浪费 token。
规则
{
"comment": "[rtk] 自动代理高频 bash 命令",
"tool": "bash",
"action": "rewrite",
"pattern": "^(git\\s+(status|log|diff)|cargo\\s+(test|build|clippy)|pytest)\\b",
"flags": "",
"reason": "rtk command rewrite:自动加 rtk 前缀压缩输出",
"enabled": true
}
当 AI 执行 git status 时,Shepherd 自动改写为 rtk git status(rtk 是一个输出压缩工具),减少 token 消耗。AI 不需要知道这个改写——对它来说,执行结果就是更简洁了。
场景 7:拦截 AI 的错误归因猜测
问题
测试失败时,AI 经常说“可能是 jiti 缓存问题““应该是 vitest proxy 没生效”——它不去查自己的代码逻辑,而是假设工具链有 bug。这种猜测不仅浪费时间,还可能误导方向。
规则
{
"comment": "归因猜测拦截:AI 回复含'可能'时提醒验证",
"hook": "message_end",
"action": "steer",
"conditions": [
{
"field": "text",
"pattern": "(可能|应该|估计).{0,30}(缓存|jiti|vitest|proxy|模块解析|工具链)",
"flags": ""
}
],
"reason": "⚠️ 不要假设工具链有问题。先查自己的代码——逻辑错误、条件顺序、类型不匹配、路径拼接。绝大多数时候都是代码有 bug。",
"enabled": true,
"subagent": false
}
关键点
hook: "message_end"— 在 AI 回复完成后触发,匹配的是 AI 的回复文本action: "steer"— 静默注入引导,不干扰用户界面,只在下一轮对话中影响 AIfield: "text"— 匹配 AI 回复的文本内容(tool_call/tool_result匹配的是工具参数,message_end匹配的是 AI 输出)subagent: false— 只在主代理生效,子代理中的探索性猜测不应被拦截
效果
AI:测试失败了,可能是 jiti 缓存问题...
🛡️ Shepherd 静默注入:不要假设工具链问题,先查代码。
AI:让我重新看看代码... 发现是条件判断顺序写反了。
为什么不用 block 或 notify?
block只能阻止工具调用,不能阻止 AI 说一段文字notify会弹通知打断用户steer静默注入,AI 下一轮会自动调整,用户几乎无感
规则设计模式总结
| 模式 | 动作 | 适用场景 |
|---|---|---|
| 编辑后提醒 | notify + conditions | 改代码后跑测试、lint、格式化 |
| 危险操作阻止 | block + conditions | 禁止 git checkout、禁止删文件 |
| 收尾自动化 | agent_end + conditions | 会话结束自动 commit + 记忆更新 |
| 连续出错引导 | steer + state | 反复失败时引导翻记忆 |
| 命令改写 | rewrite + pattern | 自动给命令加前缀压缩输出 |
| 回复内容拦截 | message_end + steer | 拦截 AI 的错误归因猜测 |
📖 回到 3.1 给 AI 立规矩 看完整的规则字段说明。
让 AI 学会复盘
你可能遇到过这种情况
你开了一个 3 小时的会话,AI 帮你做了很多事:
- 修了两个 bug
- 重构了一个模块
- 配置了 CI 流水线
- 写了一堆测试
第二天你想回顾一下:“昨天那个登录 bug 具体是怎么修的?“但你只有模糊的记忆——是改了 auth.ts 还是 middleware.ts?是加了空值检查还是改了类型断言?
你翻遍了 git log,commit message 写的是“fix: update auth“——等于没写。
💡 AI 做了很多事,但没有人记录“为什么这样做“。git 只记了改了什么,没记思考过程。
核心工具:pi-session-analyzer
pi-atelier 提供了 Session Analyzer(会话分析器)来搜索和分析历史会话:
| 功能 | 说明 |
|---|---|
| 跨会话搜索 | 关键词搜索所有历史会话的内容 |
| 按文件搜索 | 找到所有修改过某个文件的会话 |
| 时间线视图 | 按时间顺序查看一个会话的完整过程 |
| 摘要生成 | 自动总结一个会话做了什么 |
| 分支分析 | 分析 /tree 产生的平行分支 |
| 接手报告 | 5 维度上下文,帮 AI 快速恢复工作 |
| 审计检查 | 检查会话中是否有违规操作 |
💡 关于 pi-journal:pi-journal 可以通过
/journal命令或journal工具生成日报/周报,采集 git 活动、记忆变更、会话活动三路数据。详见 4.2 pi-journal 原理。
实际案例:找到上周的 bug 修复
场景:跨会话搜索
你说:“帮我找一下上次修 DuckDB 时区问题的会话。”
AI 调用 session_search:
🔍 搜索 "DuckDB 时区" — 找到 2 个匹配会话:
1. 05-22 19:36 — DuckDB 时区配置修复
改了 src/db/connection.ts
2. 05-20 14:30 — 数据库初始化讨论
讨论了时区策略但没改代码
场景:查看会话详情
你说:“看看第一个会话的详细过程。”
AI 调用 session_analyze,生成时间线:
📅 会话时间线(05-22 19:36)
[19:36] 用户:DuckDB 查询返回 UTC 时间,需要改成中国时区
[19:37] AI:读取 db/connection.ts
[19:38] AI:发现连接时没有设置时区参数
[19:39] AI:修改 connection.ts,添加 SET timezone = 'Asia/Shanghai'
[19:40] AI:运行测试,2 个测试失败
[19:41] AI:发现测试用的 mock 没有时区设置
[19:42] AI:修改测试 mock
[19:43] AI:所有测试通过 ✅
[19:44] AI:提交 git commit "fix: set DuckDB timezone to Asia/Shanghai"
场景:按文件追踪
你说:“connection.ts 最近被谁改过?”
AI 调用 session_search 的 file 模式:
📄 src/db/connection.ts 的修改历史:
1. 05-22 19:36 — DuckDB 时区修复(你)
2. 05-20 14:30 — 数据库连接池配置(同事)
3. 05-18 09:15 — 初始创建(你)
Session Analyzer 的 compact 模式
entries 动作支持 compact 参数,精简输出适合快速浏览大会话:
# 标准模式
session_analyze(action="entries")
→ 完整的每条记录(含时间戳、类型、完整内容)
# Compact 模式
session_analyze(action="entries", compact=true)
→ 去掉 type 列、时间只保留 HH:MM、预览 60 字符
→ 适合 100+ 条记录的大会话
Session Analyzer 的分析维度
| 模式 | 用途 | 命令示例 |
|---|---|---|
summary | 会话概览 | “这个会话做了什么” |
entries | 逐条事件 | “列出所有文件修改” |
timeline | 时间线 | “AI 的操作顺序是什么” |
chain | 子代理追踪 | “子代理做了什么” |
audit | 审计检查 | “有没有违规操作” |
digest | 对话序列 | “我和 AI 聊了什么” |
takeover | 接手报告 | “帮我接手上次的工作” |
最有用的模式:takeover
takeover 生成一份“接手报告“,包含 5 个维度:
📋 会话接手报告
1. 用户意图:修复 DuckDB 时区问题
2. 修改文件:connection.ts, connection.test.ts
3. 最近步骤:修改了测试 mock,测试通过
4. 下一步:考虑是否需要在文档中说明时区行为
5. 关键决策:选择在连接层而非 SQL 层处理时区
当你想“接着上次的工作继续“时,这个报告能帮你(或者另一个 AI)快速恢复上下文。
最佳实践
✅ Session Analyzer 的高效用法
grep模式:跨所有会话搜索关键词(比翻 git log 快得多)file模式:找到所有动过某个文件的会话(代码审查利器)takeover模式:接手别人的工作时,先生成接手报告compact模式:大会话快速浏览,精简输出audit模式:定期检查 AI 是否有违规操作
❌ 常见误区
- 不要用
session_search代替记忆——搜索是回溯(过去做了什么),记忆是知识(学到了什么) - 不要期望找到代码全文——会话记录是摘要,不是完整备份
进阶场景
场景:审查 AI 是否遵守了规范
你给 AI 定了很多规矩(不要直接写 settings.json、不要覆盖大文件),但不确定它是否真的遵守了。用 audit 模式检查:
你:审查一下上次会话有没有违规操作
AI:
🛠 session_analyze(action="audit", sessionId="...")
⚠️ 发现 2 个问题:
1. [违规] 直接 writeFileSync(settings.json) — 应该用 patchSettingsSectionWithBackup
2. [警告] 大文件 write 覆盖(312 行)— 应拆分或用 edit
audit 模式会检查:是否用了被禁止的操作、是否遵循了文件修改规范、是否有不安全的操作。
场景:了解 AI 的探索过程
AI 用 /tree 开了探索分支——试了方案 A 不行,又试了方案 B 成功了。你想知道方案 A 为什么不行:
你:上次 AI 试了两种方案,帮我看看每个分支的结果
AI:
🛠 session_analyze(action="branches", sessionId="...")
🌿 分支分析
[主分支] 讨论重构方案
[B1] 方案 A — 修改函数签名 + 兼容层
结果:测试失败,兼容层引入了循环依赖
[B2] 方案 B — 新建接口 + 渐进迁移
结果:所有测试通过 ✅,已合回主分支
这比翻 git log 更有用——git 只记录最终结果,branches 能看到 AI 的试错过程。
场景:追踪子代理做了什么
会话中用了 subagent 启动了多个子代理。你想知道每个子代理的具体输出:
你:上次会话的子代理都干了什么?
AI:
🛠 session_analyze(action="chain", sessionId="...")
🔗 子代理链
主代理
├──→ pv-explorer
│ 任务:分析 src/auth/ 目录的架构
│ 结果:5 个模块,2 个设计模式,依赖方向正确
│
└──→ pv-reviewer
任务:审查 JWT → session 迁移方案
结果:发现 1 个数据模型违反,2 个测试缺口
chain 模式追踪主代理和子代理之间的调用关系,清晰展示每个子代理收到了什么任务、返回了什么结果。
下一步
有了复盘能力,AI 和你都能回顾过去的工作。但还有一个问题:会话越长,AI 越容易“变笨“——开始重复自己、忘记前面的约定。
下一章,我们来看如何让 AI 在长会话中保持聪明。
4.2 pi-journal:自动日志报告
为什么需要日志
你开了一天 pi,做了很多事:
- 上午修了两个 bug,顺带重构了一个模块
- 下午配置了 CI 流水线,写了一堆测试
- 晚上研究了一个新方案,更新了记忆文件
到了晚上你想回顾:“今天到底干了什么?“你翻 git log,全是碎片化的 commit;翻记忆文件,只记了关键结论;翻会话记录,十几个会话不知道从哪看起。
💡 你需要一个“自动日报“——把散落在 git、记忆、会话中的活动汇总成一份可读的报告。
pi-journal 做什么
pi-journal 采集三种数据源,自动生成 Markdown 格式的日报/周报:
| 数据源 | 采集内容 |
|---|---|
| Git 活动 | 扫描 ~/.pi/agent/git/ 下所有仓库,统计提交数、文件变更、增加/删除行数 |
| 记忆变更 | 扫描全局和项目记忆目录,找出时间范围内的文件变更 |
| 会话活动 | 扫描 pi 会话记录,统计会话数、工具调用、编辑操作、活跃时长 |
使用方式
命令:/journal
# 今天的日报(默认)
/journal
# 指定时间范围
/journal yesterday
/journal this_week
/journal 3d # 最近 3 天
/journal 2025-05-27 # 指定日期
工具:journal
AI 也可以主动调用 journal 工具来生成报告。当你说“写个日记“、“今天做了什么”、“写周报“时,AI 会自动触发。
生成的报告格式
# 📓 日报 — 2025-05-27
## Git 活动
- **pi-shepherd** (2 commits, +45/-12)
- 新增 tool_result 规则支持
- 修复优先级排序 bug
- **pi-context-manager** (1 commit, +30/-5)
- 添加 aging 自动淘汰功能
## 记忆变更
- 新增: debug_anti_pattern.md
- 更新: coding_standards.md
## 会话活动
- 019e6494 (45m) — Shepherd 规则引擎重构
- 工具调用: 23, 编辑: 8
- 019e6203 (30m) — Context aging 功能实现
- 工具调用: 15, 编辑: 5
## 摘要
- 总提交: 3
- 总会话: 12
- 总编辑: 43
最佳实践
✅ 推荐用法
- 每日收工前:
/journal生成日报,回顾今天做了什么 - 周五总结:
/journal this_week生成周报 - 让 AI 写:直接说“写个今天的日记“,AI 会调用工具生成
⚠️ 注意事项
- 报告中的“AI 总结“部分需要 AI 在生成后补充
- Git 活动只扫描
~/.pi/agent/git/下的仓库,不包含系统其他位置的 git 仓库 - 会话活动依赖 pi 的会话记录存储
工作原理
用户输入时间范围
↓
parseTimeRange() → 解析为 since/until 时间戳
↓
┌─────────────┬──────────────┬──────────────────┐
│ Git 活动 │ 记忆变更 │ 会话活动 │
│ 自动发现仓库 │ 扫描 memory/ │ 从 session-analyzer│
│ git log 统计 │ 文件时间戳 │ 获取会话列表 │
└──────┬──────┴──────┬───────┴────────┬─────────┘
↓ ↓ ↓
renderReport() → 汇总渲染为 Markdown
↓
输出报告
下一步
pi-journal 解决了“回顾过去“的需求。但有时候你需要的不只是回顾——你需要找到某个具体的会话,看 AI 当时是怎么想的。下一节我们来看 pi-session-analyzer 的详细用法。
长会话生存指南
你可能遇到过这种情况
你开了一个长会话,AI 帮你做了很多事。到第 50 轮对话的时候,你发现:
- AI 开始问你之前已经回答过的问题
- 它重新提了一个已经被否决的方案
- 它的代码质量明显下降——少了错误处理、类型定义
- 有时候它甚至开始“幻觉“——编造不存在的函数和文件
最极端的情况:AI 直接报错了——“上下文窗口超出限制”(context window exceeded),整个会话崩溃。
💡 这就是“上下文膨胀“问题:AI 的“工作记忆“有容量上限,装太多东西就会溢出。
问题的根源
AI 的上下文窗口是一个固定大小的“工作台“:
上下文窗口(例如 128K tokens)
┌──────────────────────────────────────┐
│ 系统提示(System Prompt) ≈ 5K │
│ 工具定义(Tools) ≈ 8K │
│ 记忆注入(Memory) ≈ 2K │
│ ───────────────────────────── │
│ 对话历史(前 50 轮) ≈ 80K │ ← 膨胀的主要来源
│ 工具返回结果 ≈ 30K │ ← 工具返回的内容可能很大
│ ───────────────────────────── │
│ 剩余可用空间 ≈ 3K │ ← 快满了!
└──────────────────────────────────────┘
问题在于:
- 对话历史只增不减:每轮对话都往上下文里加内容,从不删除
- 工具返回结果可能巨大:
read一个 1000 行的文件可能就占 5K tokens - 重复信息累积:AI 多次读取同一个文件,每次都占空间
两件工具:Smart Compact 和 Context Manager
你可能会问:这两个包都要装吗?答案是:推荐都装,它们解决不同层面的问题:
| 维度 | pi-smart-compact | pi-context-manager |
|---|---|---|
| 做什么 | 压缩历史对话 | 诊断 token 消耗 + 压缩工具返回 |
| 主动/被动 | 全自动触发 | 诊断需你叫 AI 跑,distill 自动 |
| 解决什么 | “历史对话太长” | “工具返回太多” + “为什么这么慢” |
| 能互相替代吗 | ❌ 不能 | ❌ 不能 |
💡 一句话总结:context-manager 帮你发现问题(token 都花哪了),smart-compact 帮你自动解决问题(压缩历史)。两个配合使用效果最佳。
pi-smart-compact — 智能压缩
Smart Compact 在上下文快满的时候自动“压缩“历史对话:
压缩前(80K tokens 的对话历史):
┌─────────────────────────────────┐
│ 用户:帮我看看 auth.ts │
│ AI:我读取了 auth.ts...(500字) │
│ 用户:加个空值检查 │
│ AI:好的,我修改了...(300字) │
│ 用户:测试一下 │
│ AI:测试结果...(200字) │
│ ... 重复 50 轮 ... │
└─────────────────────────────────┘
压缩后(15K tokens 的摘要):
┌─────────────────────────────────┐
│ 摘要: │
│ - 在 auth.ts 中添加了空值检查 │
│ - 修改了对应的测试文件 │
│ - 所有测试通过 │
│ - 使用了 JWT 认证方案 │
│ ... 关键信息保留 ... │
└─────────────────────────────────┘
两阶段压缩策略:
| 阶段 | 方法 | 说明 |
|---|---|---|
| Phase 1 | 提取关键信息(如决策、文件修改、结论) | 遍历对话,生成结构化意图摘要 |
| Phase 2 | 丢弃低价值信息(如重复的文件读取、中间调试输出) | 让 LLM 逐批判断工具结果去留 |
pi-context-manager — 诊断工具
pi-context-manager 提供了 payload_analyze 工具,帮你看清 token 到底花在哪了:
📊 Token 预算分析
System Prompt: 4,200 tokens ( 3.2%)
Tool Definitions: 8,100 tokens ( 6.2%)
Memory Injection: 2,300 tokens ( 1.8%)
Conversation: 52,400 tokens (40.0%)
Tool Results: 64,800 tokens (49.5%) ← 大头在这!
──────────────────────────────────────
Total: 131,800 / 128,000 ← 超了!
Top 3 最贵的工具调用:
1. read(src/database/schema.ts) — 8,200 tokens
2. code_graph_module_overview — 6,400 tokens
3. grep("TODO|FIXME") — 4,100 tokens
实际案例:诊断上下文崩溃
真实场景回顾
有一次,一个会话在上下文使用率只有 34.8% 的时候就崩溃了。看起来不应该——才用了三分之一啊?
用 pi-context-manager 的 budget 模式分析后发现:
问题根因:
工具返回结果中有 34.8% 是 error 输出
→ 大量重复的 "Command not found" 错误信息
→ 每次错误都消耗了 token 但没有有价值的信息
→ 累积导致上下文提前耗尽
解决方案:在 shepherd 规则中加入 after_bash 钩子,失败命令的错误输出自动截断,避免无意义的 token 消耗。
用 growth 模式看趋势
📈 上下文增长趋势
请求 #1: 15K ████
请求 #5: 28K ███████
请求 #10: 45K ████████████
请求 #15: 72K ████████████████████
请求 #20: 98K ██████████████████████████ ← 接近上限
请求 #23: 💥 崩溃!
关键发现:第 10-15 轮之间增长最快,因为那个阶段做了大量的文件搜索。
优化:用 compact 模式的 code_graph_semantic_code_search(只返回签名和位置)替代 grep(返回完整匹配行),token 消耗减少 70%。
配置 Smart Compact
通过 settings.json 安装:
{
"packages": ["pi-smart-compact"]
}
安装后默认为 手动模式(enabled: false),只在用户执行 /smart-compact 时触发。
用 /smart-compact-config auto 可开启自动接管模式。
可选的高级配置
在 .pi/smart-compact.json 中:
{
"enabled": true
}
enabled: true:自动触发(pi 触发 compact 时接管)enabled: false:手动模式(默认,只响应/smart-compact命令)
手动触发:在对话中输入 /smart-compact。
配置查看:输入 /smart-compact-config。
Payload Analyzer 的常用命令
| 命令 | 用途 | 何时用 |
|---|---|---|
budget | Token 预算分析(system/tools/history 构成) | “token 都花哪了” |
growth | 上下文增长趋势(token 随请求变化曲线) | “为什么越来越慢” |
expensive | 最贵的工具调用(Top N 排序) | “哪个工具最吃 token” |
overview | 逐消息详细分析(含 distill 事件) | “精确诊断某个时间点” |
messages | 按索引/范围/关键词定位消息 | “看看第 10 条消息说了什么” |
chain | 跨 payload 追踪同一个工具调用 | “这个调用后来怎么样了” |
chain-tcid | 跨 payload 追踪同一个 toolCallId | “验证 distill 是否压缩了这条调用” |
chain-tcid | 跨 payload 追踪同一个 toolCallId | “验证 distill 行为” |
diff | 对比两个 payload 的差异 | “这两次请求有什么不同” |
stats | 聚合统计 distill/processor 命中率 | “压缩效率如何” |
single | 分析单个 payload 文件 | “深入看一个录制文件” |
list | 列出所有录制文件 | “有哪些可以分析的” |
💡 诊断流程:先用
list看有哪些录制文件 →budget看整体分布 →expensive找大户 →messages精确定位。
Context Manager 的 Aging 和 Processor
除了 Distill,pi-context-manager 还提供了两个辅助机制:
Aging(老化淘汰)
自动淘汰长期未被引用的旧工具输出。通过 /aging-config 设置淘汰轮数。
特殊豁免:技能文件(SKILL.md)内容不会被 aging 淘汰,确保 AI 始终能看到当前加载的技能。
Tool Result Processor(后处理器)
对特定类型的工具输出做格式化精简(如 code-graph 的 AST 搜索结果、MCP JSON 输出)。通过 /processor-config 设置阈值。
/context TUI 面板
输入 /context 可以打开可视化面板,分类浏览上下文内容,手动标记不需要的内容进行删除。
最佳实践
✅ 保持长会话健康的习惯
- 用 compact 模式搜索:
code_graph_semantic_code_search(compact: true)比grep省 70% token - 及时压缩:不要等到崩溃才处理,上下文接近上限时就应该触发压缩
- 避免重复读取:用记忆记住文件内容,不要反复
read同一个文件 - 大文件分块读:用
offset/limit只读需要的部分,而不是整个文件 - 合理配置 aging:设置 8-12 轮淘汰,自动清理过时内容
- 定期用 payload_analyze 体检:长会话中途跑一次
budget,提前发现问题
✅ 诊断的优先级
上下文出问题时:
1. payload_analyze budget → 看总量分布
2. payload_analyze expensive → 找最贵的调用
3. payload_analyze growth → 看增长趋势
4. 针对性优化(换工具、加过滤、调整策略)
❌ 常见误区
- “上下文还有 50% 空间,不用担心” → 错,工具返回结果可能突然膨胀
- “压缩会丢失重要信息” → Smart Compact 优先保留决策和结论
- “重启会话就好了” → 治标不治本,下次还会遇到同样的问题
下一步
现在 AI 有了记忆、规划、规矩、复盘和压缩——它已经是一个相当能干的助手了。但它还是“被动“的——你问它才做。
能不能让 AI 主动工作?比如每天自动检查代码质量,或者自动跑一轮研究分析?
下一章,我们来看如何让 AI 自动化工作。
5.2 pi-smart-compact 原理:两阶段增强压缩
Smart Compact 是 pi 内置 Compaction 机制的增强版——它不是简单地截断历史,而是“聪明地“决定保留什么、丢弃什么。
为什么需要增强压缩?
pi 内置的 Compaction 会在上下文接近上限时自动压缩旧对话,但它不够“聪明“:
内置 Compaction:
压缩前 100 轮对话 → 压缩后生成一段通用摘要
问题:摘要太粗糙,关键细节丢失,工具调用结果被无差别截断
Smart Compact 的改进——接管 pi 的压缩事件,执行两阶段增强压缩:
| 阶段 | 做什么 | 怎么做 |
|---|---|---|
| Phase 1:意图总结 | 提取用户意图、关键决策、当前状态 | 遍历对话,提取 AI 回复中的非工具文本,生成结构化意图摘要 |
| Phase 2:工具去留 | 判断哪些工具调用结果可以安全丢弃 | 将所有工具调用配对(调用+结果),让 LLM 逐批判断保留/丢弃 |
pi 触发 compact 事件
→ Smart Compact 接管(如果 auto 模式开启)
→ Phase 1:提取意图摘要(保留决策、约定、结论)
→ Phase 2:逐批判断工具结果去留
→ 输出精简后的对话历史,替代 pi 默认的粗糙摘要
两个阶段是一次性顺序执行的——Smart Compact 接管压缩事件后,先做意图总结,再做工具筛选,最后输出精简结果。不是按上下文使用率分阶段触发。
配置
安装
{
"packages": ["pi-smart-compact"]
}
命令
| 命令 | 用途 |
|---|---|
/smart-compact | 手动触发两阶段压缩 |
/smart-compact-config [auto|manual] | 查看或切换自动/手动模式 |
自动/手动模式
auto(/smart-compact-config auto开启):pi 触发 compact 事件时自动接管,执行增强压缩manual(默认):只在用户执行/smart-compact时触发
高级配置
在项目级 .pi/smart-compact.json 中可配置:
{
"enabled": false,
"intentModel": "",
"filterModel": "",
"thinkingTruncateChars": 500,
"toolCallTruncateChars": 1000,
"toolResultTruncateChars": 2000,
"filterBatchSize": 20
}
| 参数 | 默认值 | 说明 |
|---|---|---|
enabled | false | 是否自动接管 pi 的 compaction |
intentModel | 空(用会话默认模型) | Phase 1 意图总结使用的模型 |
filterModel | 空(用会话默认模型) | Phase 2 工具去留判断使用的模型 |
thinkingTruncateChars | 500 | thinking 块截断字符数 |
toolCallTruncateChars | 1000 | toolCall arguments 截断字符数 |
toolResultTruncateChars | 2000 | toolResult 内容截断字符数 |
filterBatchSize | 20 | Phase 2 每批判断的工具数量 |
压缩保留什么?
Smart Compact 的 Phase 2 会根据以下优先级判断工具结果:
| 优先级 | 内容类型 | 为什么保留 |
|---|---|---|
| 🔴 最高 | 用户明确的要求和约束 | 这是任务目标 |
| 🟠 高 | 关键决策和选择理由 | 避免 AI 重复讨论已否决的方案 |
| 🟡 中 | 文件修改记录(edit/write) | 让 AI 知道哪些文件已改过 |
| 🟢 低 | 文件读取和搜索结果 | 可以重新执行 |
| ⚪ 最低 | 失败的尝试和调试过程 | 已经学到了教训 |
最佳实践
- 长会话时开启 auto 模式:Smart Compact 会在 pi 准备压缩时自动接管,比默认压缩保留更多关键信息
- 手动触发适合关键操作前:在开始一个重要的重构之前,手动
/smart-compact清理上下文 - 配合 context-manager:Smart Compact 压缩对话历史,Context Manager 的 distill 压缩工具输出,两者互补
- 可用便宜模型做压缩:如果不想浪费主模型的 token,可以在配置中指定
filterModel为更便宜的模型
📖 回到 5.1 长会话生存指南 看完整的诊断和优化案例。
5.3 用 pi-context-manager 诊断 Token 消耗
pi-payload-analyzer 的功能已合并到 pi-context-manager 中。本节介绍如何使用统一的
payload_analyze工具诊断上下文问题。
Token 都花在哪了?
长会话中 AI 变笨,往往是因为上下文被“垃圾“填满了。但具体是哪些内容占了 token?靠猜是不行的。
pi-context-manager 提供了 payload_analyze 工具,用数据告诉你 token 的去向。
需要先开启录制
payload_analyze 需要先录制 payload 才能分析。在对话中输入:
/record on
录制会保存在 ~/.pi/agent/distill/recordings/ 目录下。录制期间会有轻微的性能开销,用完记得 /record off 关闭。
分析模式速查
全局概览类
| 模式 | 用途 | 输出 |
|---|---|---|
list | 列出所有录制文件 | 文件列表 + 大小 |
budget | Token 预算分析 | system/tools/history 各部分占比 |
growth | 增长趋势 | token 随请求变化的曲线 |
stats | 聚合统计 | distill/processor 命中率、压缩效率 |
深入诊断类
| 模式 | 用途 | 输出 |
|---|---|---|
expensive | 最贵的工具调用 | 按 token 排序的 Top N |
overview | 逐消息详细分析 | 每条消息的 token 构成 + distill 事件 |
messages | 精确定位消息 | 按索引/范围/关键词过滤 |
追踪对比类
| 模式 | 用途 | 输出 |
|---|---|---|
chain | 追踪工具调用命运 | 同一个 argsSig 跨 payload 的变化 |
chain-tcid | 追踪 toolCallId | 同一个 toolCallId 跨 payload 的变化(验证 distill 行为) |
chain-tcid | 追踪 toolCallId | 验证 distill 行为 |
diff | 对比两个 payload | 找出两次请求的差异 |
single | 分析单个文件 | 单个录制文件的完整分析 |
messages 模式的精确定位
messages 是最灵活的诊断工具,支持多种过滤方式:
# 看第 5 条消息(0-based)
payload_analyze(action="messages", msgIndex=5)
# 看第 5-10 条消息
payload_analyze(action="messages", msgRange="5-10")
# 看最后 5 条消息
payload_analyze(action="messages", msgRange="last:5")
# 按关键词过滤
payload_analyze(action="messages", grep="error|fail")
# 按工具名过滤
payload_analyze(action="messages", toolName="read")
# 按文件路径过滤
payload_analyze(action="messages", file="*.ts")
实战案例
案例 1:找出上下文膨胀的根因
第一步:budget 模式看总量
你:"帮我用 payload_analyze 分析 token 预算"
结果:Tool Results 占 49.5%
第二步:expensive 模式找大户
你:"找出最耗 token 的工具调用 Top 10"
结果:read(schema.ts) 占 8.2K tokens
第三步:优化
→ 用 offset/limit 分块读取大文件
→ 或者启用 distill 自动压缩
案例 2:诊断压缩效率
第一步:stats 模式看命中率
你:"看 distill 和 processor 的压缩效率"
结果:distill 命中率 75%,processor 命中率 60%
第二步:chain 模式追踪
你:"追踪 read(schema.ts) 的 distill 行为"
结果:第 3 次请求被 distill,从 8.2K 压缩到 1.5K
案例 3:对比两次请求的差异
你:"对比这两个 payload 有什么不同"
AI 调用 payload_analyze(action="diff", payloadPath="...", payloadPath2="...")
结果:第二次请求多了 3 条工具调用,但 distill 压缩了 2 条
📖 完整长会话诊断案例见 5.1 长会话生存指南
5.4 长会话实战场景
这一节通过真实使用场景,展示如何组合 pi-atelier 的工具解决长会话中的常见问题。
场景 1:AI 变笨了——用 Smart Compact 压缩
症状
你跟 AI 已经聊了 2 个小时,做了很多事。突然发现 AI 开始:
- 问你之前已经回答过的问题
- 重新提出已经被否决的方案
- 代码质量明显下降——少了错误处理
传统压缩 vs Smart Compact
pi 内置的 Compaction 会在上下文接近上限时自动触发,但它只是简单地压缩旧对话为一段通用摘要。Smart Compact 更聪明:
传统 Compaction:
100 轮对话 → 一段 500 字的通用摘要
问题:关键细节丢失,AI 不知道之前做了什么决定
Smart Compact(两阶段):
Phase 1(意图总结):
→ 提取:决策、约定、文件修改、结论
→ 保留所有关键信息,丢弃冗余过程
Phase 2(工具去留):
→ 逐批判断每个工具结果是否需要保留
→ 丢弃:重复读取、失败尝试、调试过程
操作步骤
1. AI 已经帮你做了很多事,你感觉上下文快满了
2. 输入 /smart-compact
3. Smart Compact 分析对话历史,生成增强摘要
4. AI 继续工作,但"记得"所有关键决策
或者什么都不做——如果配置了 auto 模式(默认),Smart Compact 会在合适的时机自动触发。
配合 Context Manager 效果更好
Smart Compact 压缩对话历史,Context Manager 的 Distill 压缩工具返回结果。两个配合使用:
上下文窗口
├── 对话历史 ←── Smart Compact 压缩(保留决策和结论)
├── 工具结果 ←── Distill 压缩(保留关键信息,丢弃冗余)
└── 记忆注入 ←── 固定大小,不变
场景 2:上下文已经炸了——Handoff 接手
症状
更极端的情况:AI 报错了——“上下文窗口超出限制”(context window exceeded)。整个会话已经无法继续。
这时候光压缩已经来不及了——会话直接崩溃。
解决方案:开新会话 + takeover 接手
1. 开一个新会话
2. 告诉 AI:"帮我接手上次的工作"
3. AI 调用 session_analyze(action="takeover")
4. 生成 5 维度接手报告:
📋 会话接手报告
1. 用户意图:重构认证模块,从 JWT 切换到 session-based
2. 修改文件:
- src/auth/middleware.ts(已改完)
- src/auth/login.ts(进行中,80% 完成)
- src/auth/__tests__/login.test.ts(待写)
3. 最近步骤:
- 修改了 middleware.ts 的类型签名
- 开始改 login.ts 但还没改完
- 测试还没写
4. 下一步:
- 完成 login.ts 的修改
- 写 login.test.ts 的测试
- 跑一遍全量测试
5. 关键决策:
- 选择 session-based 而非 refresh token 方案
- 原因:项目不需要跨域 SSO
takeover 的 5 个维度
| 维度 | 包含什么 | 为什么重要 |
|---|---|---|
| 用户意图 | 原始需求和目标 | 新 AI 知道“要做什么“ |
| 修改文件 | 已改和待改的文件列表 | 知道“改了什么“ |
| 最近步骤 | 最近 3-5 步操作 | 知道“进行到哪了“ |
| 下一步 | 还需要做什么 | 知道“接下来该做什么“ |
| 关键决策 | 重要的技术选择和理由 | 知道“为什么这样做“ |
有了这 5 个维度,新会话的 AI 可以在 1-2 轮对话内完全恢复工作上下文。
操作示例
你:帮我接手上次修 Godot MCP Bridge 的工作
AI:
🛠 session_analyze(action="takeover", sessionId="019e620f-...")
📋 接手报告已生成。上次你在调试 MCP Bridge 的 WebSocket 连接问题,
已经定位到 nohup 导致 listen() 返回值被吞。下一步是不用 nohup
直接启动 TCP 端口。要我继续吗?
场景 3:想了解过去发生了什么——会话分析
症状
你昨天让 AI 做了一大堆事。今天你想回顾:“昨天那个 DuckDB 时区 bug 具体是怎么修的?”
操作步骤
第一步:跨会话搜索
你:搜一下之前修 DuckDB 时区的会话
AI:
🛠 session_search(action="grep", query="DuckDB 时区")
找到 2 个匹配会话:
1. 05-22 19:36 — DuckDB 时区配置修复
2. 05-20 14:30 — 数据库初始化讨论
第二步:看时间线
你:看看第一个的详细过程
AI:
🛠 session_analyze(action="timeline", sessionId="...")
📅 时间线:
[19:36] 用户:DuckDB 查询返回 UTC 时间
[19:37] AI:读取 db/connection.ts
[19:38] AI:发现没有设置时区参数
[19:39] AI:添加 SET timezone = 'Asia/Shanghai'
[19:43] AI:所有测试通过 ✅
第三步:看对话原文(如果需要更多细节)
你:看看 19:39 前后的对话原文
AI:
🛠 session_analyze(action="entries", range="5-10", sessionId="...")
或者按文件过滤——找到所有动过 connection.ts 的操作:
你:这个会话里哪些操作动过 connection.ts?
AI:
🛠 session_analyze(action="entries", file="*connection*", sessionId="...")
📋 匹配 3 条操作:
[12] read(db/connection.ts) — 读取文件
[15] edit(db/connection.ts) — 添加 SET timezone
[18] read(db/connection.ts) — 验证修改结果
常用的分析模式组合
快速了解一个会话:summary → 知道做了什么
追踪操作顺序: timeline → 知道步骤
查看对话原文: entries → 知道细节
接手别人的工作: takeover → 知道上下文
检查是否有违规: audit → 知道有没有问题
场景 4:工具输出占满了上下文——Aging 自动遗忘
症状
AI 在一个会话中用了大量工具:读了 20 个文件、跑了 10 次搜索、执行了 5 次 bash 命令。每个工具返回的结果都留在上下文里。
问题是:你只需要最近的几个结果。20 分钟前 read 的那个文件,现在早就不需要了。
解决方案:Aging 自动淘汰
Aging(老化淘汰)会在指定轮数后自动淘汰未被再次引用的工具输出:
时间线:
第 1 轮:read(auth.ts) → 5K tokens
第 2 轮:read(middleware.ts) → 4K tokens
第 3 轮:grep("TODO") → 3K tokens
...
第 10 轮:edit(auth.ts) ← auth.ts 又被引用,"续命"成功
第 1+8=9 轮:grep("TODO") → 8 轮没被引用,自动淘汰 ✅
第 2+8=10 轮:middleware.ts 没被再次引用,自动淘汰 ✅
第 1+8=9 轮:auth.ts → 被 edit 引用,保留!
配置 Aging
/aging-config 8 # 8 轮后淘汰(推荐 8-12)
/aging-config off # 关闭自动淘汰
💡 技能文件豁免:SKILL.md 的内容不会被 aging 淘汰——AI 始终能看到当前加载的技能。
手动干预:/context TUI 面板
如果不想等自动淘汰,可以手动标记删除:
1. 输入 /context 打开 TUI 面板
2. 分类浏览:按工具类型、时间顺序查看所有上下文内容
3. 选中不需要的内容,标记为删除
4. 下次 AI 发送请求时,标记的内容不会带上
这在以下场景特别有用:
- AI 读了一个巨大的配置文件但你只需要其中一行
- 搜索返回了 50 条结果但你只用了 3 条
- 之前调试时的错误信息已经不需要了
场景 5:为什么 AI 变慢了——Token 预算诊断
症状
AI 的响应越来越慢,而且每轮对话的等待时间明显变长。你怀疑是上下文太大了,但不知道具体是什么在占空间。
操作步骤
第一步:开启录制
你:/record on
... 多做几轮对话 ...
第二步:看 token 预算
你:帮我分析 token 预算
AI:
🛠 payload_analyze(action="budget")
📊 Token 预算分析
System Prompt: 4,200 (3.2%)
Tool Definitions: 8,100 (6.2%)
Memory Injection: 2,300 (1.8%)
Conversation: 52,400 (40.0%)
Tool Results: 64,800 (49.5%) ← 大头在这!
第三步:找最贵的调用
你:找出最耗 token 的工具调用
AI:
🛠 payload_analyze(action="expensive", topN=5)
Top 5 最贵的工具调用:
1. read(src/database/schema.ts) — 8,200 tokens
2. code_graph_module_overview — 6,400 tokens
3. grep("TODO|FIXME") — 4,100 tokens
4. read(src/config/settings.ts) — 3,800 tokens
5. bash("npm test") — 3,200 tokens
第四步:针对性优化
→ schema.ts 太大,用 offset/limit 只读需要的部分
→ code_graph 用 compact=true 模式
→ grep 加 --include 限定文件类型
诊断流程速查
1. budget → 看总量分布(哪部分占比最高?)
2. expensive → 找大户(具体哪个调用最耗 token?)
3. growth → 看趋势(哪段时间增长最快?)
4. messages → 精确定位(看看那条具体的消息内容)
5. 针对性优化(换工具、加过滤、启用 distill)
场景组合:超长会话的完整生存策略
会话开始
│
├── 第 1-20 轮:正常工作,不用操心
│
├── 第 20-40 轮:上下文使用率 ~40%
│ → 开启 /record on(可选,为后续诊断做准备)
│ → 注意避免重复读取大文件
│
├── 第 40-60 轮:上下文开始紧张
│ → Smart Compact 接管 pi 的压缩事件(如果 auto 模式开启)
│ → 或手动 /smart-compact
│ → Aging 开始淘汰旧工具输出
│
├── 第 60-80 轮:接近上限
│ → Smart Compact 已完成两阶段压缩
│ → 考虑是否该开新会话了
│ → 如果要继续:用 payload_analyze budget 检查
│
├── 💥 崩溃了!
│ → 开新会话
│ → session_analyze(action="takeover") 接手
│ → 继续工作
│
└── 收工前:
→ /record off
→ 让 AI 用 /journal 生成日报
→ agent_end 自动提醒 commit + 记忆更新
📖 回到 5.1 长会话生存指南 看完整的工具介绍。
自动化与工作流
你可能遇到过这种情况
每周五下午,你都要做同样的事:
- 检查本周所有会话,看看改了哪些文件
- 跑一遍测试,确保没有回归
- 检查是否有未提交的代码
- 写周报——总结本周进展
每次都要手动提醒 AI 做这些事。有时候忘了,周一回来发现上周五的改动没提交。
💡 AI 能做事,但它不会“主动“做事。它需要你告诉它“现在该做什么“。
两件工具:Scheduler 和 Workflow
pi-scheduler — 定时任务
Scheduler(调度器)让 AI 在指定的时间做指定的事:
定时触发
│
▼
┌──────────────────┐
│ 注入预设消息 │
│ "现在是周五下午, │
│ 请执行周度检查" │
└──────────────────┘
│
▼
AI 自动执行
支持的定时类型:
| 类型 | 说明 | 示例 |
|---|---|---|
| 一次性 | 指定时间后触发一次 | “30 分钟后提醒我开会” |
| 重复 | 按固定间隔重复触发 | “每 2 小时检查一次测试” |
pi-workflow — 子代理编排
Workflow(工作流)让 AI 把复杂任务拆成多个子代理并行执行:
主代理:"研究一下 XXX 的最佳实践"
│
├──→ 子代理 1:搜索网页资料
├──→ 子代理 2:搜索 GitHub 源码
└──→ 子代理 3:搜索历史会话
│
▼
主代理:综合三个子代理的结果,给出建议
子代理是独立的执行环境:
- 有自己的上下文窗口(不会污染主会话)
- 有自己的工具集(可以限制权限)
- 执行完返回结果给主代理
实际案例:自动周报
配置每周五的自动检查
{
"action": "create",
"interval_ms": 604800000,
"recurring": true,
"prompt": "现在是周五下午。请执行以下检查:\n1. 用 session_search 查看本周所有会话\n2. 用 session_analyze summary 总结每个会话\n3. 检查 git status 是否有未提交的改动\n4. 汇总生成周报并写入 .pi/journal/weekly-summary.md\n5. 提醒用户提交未提交的代码"
}
每周五,AI 会自动:
- 搜索本周所有会话
- 逐个生成摘要
- 检查 git 状态
- 生成周报
- 提醒你提交
定时提醒的日常使用
用户:"30 分钟后提醒我看看 CI 的结果"
AI:✅ 已创建定时任务,30 分钟后提醒
... 30 分钟后 ...
AI:⏰ 提醒:该检查 CI 结果了。当前时间 15:30。
实际案例:子代理研究
场景:调研一个新技术的最佳实践
你说:“调研一下 Bun 和 Node.js 的性能差异,给我一个选型建议。”
AI 启动研究工作流:
🔬 研究工作流启动
子代理 1(搜索专家):
→ 搜索 "Bun vs Node.js performance benchmark 2026"
→ 找到 5 篇技术文章
→ 提取关键数据点
子代理 2(源码专家):
→ 搜索 Bun 的 GitHub 仓库
→ 查看 benchmark 目录
→ 查看 issue 中的性能讨论
子代理 3(历史专家):
→ 搜索本项目历史会话
→ 看看之前有没有讨论过类似选型
─────────────────────────
主代理综合报告:
📊 Bun vs Node.js 选型建议
1. 性能对比:
- HTTP 吞吐:Bun 快 3-4x
- 启动时间:Bun 快 5x
- npm 兼容性:Node.js 更好(Bun 95% 兼容)
2. 本项目建议:继续用 Node.js
- 理由:项目依赖了多个 Node.js 原生模块
- Bun 的兼容性问题可能导致额外维护成本
- 性能差异对本项目影响不大(IO 密集型)
子代理的优势
| 特性 | 单代理(普通对话) | 多代理(Workflow) |
|---|---|---|
| 上下文隔离 | 所有信息混在一起 | 每个子代理独立 |
| 并行执行 | 串行,一个一个来 | 可并行搜索 |
| 错误隔离 | 一个环节出错影响全局 | 子代理出错不影响其他 |
| Token 效率 | 所有信息都在主上下文 | 只有最终结果回到主上下文 |
Scheduler 的配置
通过 settings.json 安装:
{
"packages": ["pi-scheduler"]
}
工具提供三个操作:
| 操作 | 说明 | 参数 |
|---|---|---|
create | 创建定时任务 | interval_ms, prompt, recurring |
list | 查看所有任务 | 无 |
cancel | 取消任务 | id |
常用的时间间隔
| 间隔 | interval_ms | 用途 |
|---|---|---|
| 30 分钟 | 1,800,000 | 短期提醒 |
| 2 小时 | 7,200,000 | 定期检查 |
| 每天 | 86,400,000 | 日报/日检查 |
| 每周 | 604,800,000 | 周报/周检查 |
Workflow 的配置
通过 settings.json 安装:
{
"packages": ["pi-workflow"]
}
Workflow 提供两个核心概念:
- 因子研究(Factor Research):多轮搜索 + 评估 + 综合
- 因子优化(Factor Optimization):初筛 + 解剖 + 组合 + 迭代 + 验证
通常你不需要直接操作 Workflow——AI 会根据任务复杂度自动选择是否启用子代理。
最佳实践
✅ 好的定时任务设计
- 明确的指令:告诉 AI 具体要做什么,不要模糊的“检查一下“
- 合理的间隔:不要每 5 分钟检查一次(浪费资源)
- 有意义的触发:提醒应该是“该做 X 了“,而不是“你在吗“
✅ 好的子代理设计
- 单一职责:每个子代理只做一件事
- 明确的输出:子代理应该返回结构化的结果,不是自由文本
- 适度的并行:3-5 个子代理是甜蜜点,太多会增加综合难度
❌ 常见误区
- “定时任务可以替代所有手动操作” → 不行,复杂决策还是需要人参与
- “子代理越多越好” → 太多子代理的综合成本可能超过收益
- “Workflow 可以做任何事” → 它擅长研究和分析,不适合需要人工判断的决策
下一步
到目前为止,我们介绍了 pi-atelier 提供的所有核心工具。但如果这些工具还不够——你想做一个当前没有的功能呢?
下一章,我们来看如何自己动手开发扩展。
自己动手做扩展
为什么要自己写扩展?
pi-atelier 提供了 10 个扩展,覆盖了记忆、规划、规矩、复盘、压缩、自动化等核心场景。但每个项目都有自己的特殊需求:
- 你的团队用飞书而不是 Slack,需要一个飞书通知扩展
- 你做游戏开发,需要一个自动管理 assets 目录的扩展
- 你写学术论文,需要一个 LaTeX 编译 + 引用检查的扩展
💡 扩展的本质就是给 AI 加新工具和新知识。
扩展的架构
一个扩展由什么组成?
pi-xxx/
├── package.json # 包元数据 + pi 扩展配置
├── index.ts # 入口,注册工具和钩子
├── lib/ # 工具实现
│ └── tools-xxx.ts
├── prompts/ # 提示模板(AI 看到的描述)
│ └── xxx-description.md
└── README.md # 文档
核心概念
| 概念 | 说明 | 类比 |
|---|---|---|
| Tool | AI 可以调用的函数 | 给 AI 一把新锤子 |
| Hook | 在特定时机执行的逻辑 | 给 AI 一个闹钟 |
| Prompt | 工具的描述(AI 看到的说明) | 告诉 AI 这把锤子怎么用 |
| Config | 用户可配置的参数 | 锤子的力度调节 |
扩展的生命周期
1. pi 启动
│
▼
2. 加载 settings.json 中的 packages
│
▼
3. 安装/更新扩展(npm 或 git)
│
▼
4. 执行扩展的入口函数 `export default function(pi)`
│
├── 注册工具(pi.registerTool)
├── 注册命令(pi.registerCommand)
└── 监听事件(pi.on)
│
▼
5. AI 会话中可以调用新工具
实战:从零写一个“代码统计“扩展
让我们一步步写一个简单的扩展——统计项目代码行数。
第 1 步:创建项目
mkdir pi-code-stats
cd pi-code-stats
npm init -y
修改 package.json:
{
"name": "pi-code-stats",
"version": "0.1.0",
"main": "index.ts",
"piExtension": true
}
💡
"piExtension": true告诉 pi 这是一个扩展包。"main"指向入口文件(TypeScript 或 JavaScript 均可,pi 用 jiti 加载)。
第 2 步:写工具实现
lib/tools-stats.ts:
import { execSync } from 'child_process';
export function countLines(directory: string, extension: string): {
total: number;
files: { path: string; lines: number }[];
} {
const cmd = `find ${directory} -name "*.${extension}" -not -path "*/node_modules/*" -not -path "*/.git/*"`;
const files = execSync(cmd).toString().trim().split('\n');
const results = files.map(file => ({
path: file,
lines: Number(execSync(`wc -l < ${file}`).toString().trim())
}));
return {
total: results.reduce((sum, r) => sum + r.lines, 0),
files: results.sort((a, b) => b.lines - a.lines)
};
}
第 3 步:写入口文件
index.ts:
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
import { countLines } from './lib/tools-stats';
export default function (pi: ExtensionAPI) {
pi.registerTool({
name: 'code_stats',
label: 'Code Stats',
description: '统计项目代码行数。用户说"统计代码"、"多少行代码"时使用。',
promptSnippet: '统计项目代码行数',
parameters: {
type: 'object',
properties: {
directory: {
type: 'string',
description: '要统计的目录路径'
},
extension: {
type: 'string',
description: '文件扩展名,如 ts, py, rs'
}
},
required: ['directory']
},
async execute(_toolCallId: string, params: any): Promise<any> {
const result = countLines(params.directory, params.extension || 'ts');
return {
totalLines: result.total,
fileCount: result.files.length,
topFiles: result.files.slice(0, 10)
};
}
});
}
第 4 步:写工具描述
prompts/stats-description.md:
统计项目代码行数。
参数:
- directory(必填):要统计的目录路径
- extension(可选):文件扩展名,默认 ts
返回:
- totalLines:总行数
- fileCount:文件数
- topFiles:最大的 10 个文件
示例:
code_stats(directory="src", extension="ts")
→ { totalLines: 12340, fileCount: 45, topFiles: [...] }
第 5 步:安装测试
// settings.json
{
"packages": [
"./path/to/pi-code-stats"
]
}
重启 pi,AI 就能用 code_stats 工具了。
pi-shared-utils:你的工具箱
写扩展时,不用什么都从零开始。pi-shared-utils 提供了一组常用工具函数:
| 模块 | 功能 | 什么时候用 |
|---|---|---|
logger | 统一日志格式 | 需要打印调试信息 |
storage | 跨会话持久存储 | 需要保存配置或状态 |
paths | 统一路径处理 | 需要找文件位置 |
json | 安全的 JSON 读写 | 需要操作 JSON 文件 |
validator | 参数校验 | 需要验证工具参数 |
settings-backup | settings.json 备份与回滚 | 需要安全写入配置 |
file-lock | 文件锁(proper-lockfile 封装) | 需要防竞态写入 |
config | 三层配置合并(defaults → 全局 → 项目) | 扩展需要可配置参数 |
使用示例
import { logger, storage, paths } from '@pi-atelier/shared-utils';
// 日志
logger.info('扩展已激活');
logger.warn('配置文件缺失,使用默认值');
// 路径
const projectRoot = paths.getProjectRoot();
const memoryDir = paths.getMemoryDir();
配置 API 示例
如果你的扩展需要用户可配置的参数:
import { getEffectiveConfig } from '@pi-atelier/shared-utils';
const defaults = { threshold: 1000, enabled: true };
const config = getEffectiveConfig('my-extension', defaults, cwd);
// config = 三层合并后的最终配置
调试你的扩展
扩展开发中最常遇到的问题:工具注册了但 AI 不调用、handler 报错了看不到日志、返回结果不是预期的。
查看日志输出
扩展中 logger.info() 和 console.log() 的输出会出现在 pi 的终端窗口中(不是聊天窗口)。调试步骤:
# 在终端中启动 pi(而不是后台运行),这样能看到所有日志输出
pi
# 然后在聊天窗口中让 AI 调用你的工具
# 终端会显示日志输出
确认工具是否注册成功
在 pi 聊天中直接问 AI:
你现在有哪些工具可以用?能看到 code_stats 吗?
如果 AI 看不到你的工具,检查:
package.json中是否有"piExtension": truesettings.json中包路径是否正确- 入口函数是否正确导出(
export default function(pi))
常见问题排查
| 问题 | 原因 | 解决方案 |
|---|---|---|
| AI 看不到工具 | piExtension 字段缺失 | 在 package.json 加 "piExtension": true |
| 工具调用报错 | handler 内部异常 | 查看终端日志中的错误栈 |
| AI 不调用工具 | description 太模糊 | 让工具描述更具体,包含参数说明和示例 |
| 返回值为空 | 异步操作未 await | handler 加 async,调用加 await |
| 路径找不到 | 相对路径问题 | 用 paths.getProjectRoot() 获取绝对路径 |
💡 技巧:开发扩展时,可以在 handler 里先
console.log(JSON.stringify(args, null, 2))打印参数,确认 AI 传了什么进来。
发布你的扩展
发布到 npm
# 1. 确认 package.json 信息完整
npm version patch # 0.1.0 → 0.1.1
# 2. 发布
npm publish --access public
发布后的安装方式
其他用户在 settings.json 中添加你的包名即可:
{
"packages": [
"pi-code-stats"
]
}
发布前的检查清单
-
package.json有完整的description和keywords -
README.md按模板写完(安装、使用、配置、示例) - 有
LICENSE文件 - 有
CHANGELOG.md - 代码有基本的错误处理
- 工具描述(prompts/*.md)清晰完整
扩展开发的最佳实践
✅ 好的扩展设计
- 单一职责:一个扩展做一件事,不要把所有功能塞进一个包
- 描述即文档:工具的 description 写得足够清楚,AI 不需要猜
- 参数校验:在 handler 里验证参数,给出有意义的错误信息
- 幂等操作:同样的输入应该返回同样的结果,避免副作用
✅ 工具描述的写法
# 好的描述
统计指定目录下特定类型文件的代码行数。
参数:
- directory(必填):目录路径
- extension(可选):文件后缀,默认 "ts"
返回:{ totalLines, fileCount, topFiles }
# 不好的描述
统计代码
❌ 常见错误
- 工具名太泛:
analyze→ 应该是code_stats - 描述太简短:AI 不知道怎么用,会传错参数
- 忘记处理错误:文件不存在时直接崩溃
- 返回值太大:整个文件内容作为返回值 → 应该返回摘要
附录:pi 扩展 API 速查
registerTool
pi.registerTool({
name: string, // 工具名(唯一标识,AI 用这个调用)
label: string, // 显示名(TUI 中展示用)
description: string, // 工具描述(AI 看到的,决定 AI 什么时候调用)
promptSnippet?: string, // 短描述(注入 AI system prompt,不填则不出现在 Available tools)
promptGuidelines?: string[], // AI 使用指南
parameters: TypeBox.Object({...}), // 参数定义(TypeBox schema)
renderShell?: "default" | "self", // 渲染模式(默认 "default")
executionMode?: "sequential" | "parallel", // 执行模式
async execute(
toolCallId: string, // 工具调用 ID
params: any, // AI 传入的参数
signal: AbortSignal | undefined, // 取消信号
onUpdate: Function | undefined, // 流式更新回调
ctx: ExtensionContext // 执行上下文
): Promise<{ content: [{ type: "text", text: string }], details: any }>
});
registerCommand
pi.registerCommand(name: string, {
description: string, // 命令描述
getArgumentCompletions?: (prefix: string) => AutocompleteItem[], // 参数自动补全
handler: async (args: string, ctx: ExtensionCommandContext) => {
// args: 用户输入的参数(/command 后面的文本)
// ctx.ui.notify(message, level): 显示通知
// ctx.compact(): 触发压缩
// ctx.switchModel(model): 切换模型
}
});
registerShortcut
pi.registerShortcut(shortcut: string, {
description: string,
handler: async (ctx: ExtensionContext) => { ... }
});
事件监听
// 会话生命周期
pi.on('session_start', (event, ctx) => { ... });
pi.on('session_shutdown', (event, ctx) => { ... });
pi.on('session_before_switch', (event, ctx) => { ... });
pi.on('session_before_fork', (event, ctx) => { ... });
pi.on('session_before_tree', (event, ctx) => { ... });
pi.on('session_tree', (event, ctx) => { ... });
// 压缩
pi.on('session_before_compact', (event, ctx) => { ... });
pi.on('session_compact', (event, ctx) => { ... });
// AI 交互
pi.on('before_provider_request', (event, ctx) => { ... });
pi.on('after_provider_response', (event, ctx) => { ... });
pi.on('context', (event, ctx) => { ... });
// Agent 生命周期
pi.on('before_agent_start', (event, ctx) => { ... });
pi.on('agent_start', (event, ctx) => { ... });
pi.on('agent_end', (event, ctx) => { ... });
pi.on('turn_start', (event, ctx) => { ... });
pi.on('turn_end', (event, ctx) => { ... });
// 消息
pi.on('message_start', (event, ctx) => { ... });
pi.on('message_update', (event, ctx) => { ... });
pi.on('message_end', (event, ctx) => { ... });
// 工具执行
pi.on('tool_call', (event, ctx) => { ... });
pi.on('tool_result', (event, ctx) => { ... });
pi.on('tool_execution_start', (event, ctx) => { ... });
pi.on('tool_execution_update', (event, ctx) => { ... });
pi.on('tool_execution_end', (event, ctx) => { ... });
// 其他
pi.on('model_select', (event, ctx) => { ... });
pi.on('thinking_level_select', (event, ctx) => { ... });
pi.on('input', (event, ctx) => { ... });
pi.on('user_bash', (event, ctx) => { ... });
pi.on('resources_discover', (event, ctx) => { ... });
辅助方法
pi.sendMessage(message, options?); // 发送自定义消息到会话
pi.appendEntry(role, content); // 向会话追加消息
pi.registerFlag(name, options); // 注册 CLI 标志
pi.getFlag(name); // 获取 CLI 标志值
pi.registerMessageRenderer(type, renderer); // 注册自定义消息渲染器
恭喜你读完了!
现在你已经了解了 pi-atelier 的全部核心概念:
- 记忆(pi-memory)— 让 AI 记住知识
- 规划(pi-roadmap)— 让 AI 管理任务
- 规矩(pi-shepherd + pi-context-manager)— 让 AI 遵守规则、控制信息质量
- 复盘(pi-session-analyzer + pi-journal)— 让 AI 记录和回溯工作
- 压缩与诊断(pi-smart-compact + pi-context-manager)— 让 AI 在长会话中保持聪明
- 自动化(pi-scheduler + pi-workflow)— 让 AI 主动工作
- 扩展(pi-shared-utils + 你自己的扩展)— 让 AI 无所不能
欢迎在 GitHub 上提交 Issue 和 PR,一起让 AI 编程助手变得更好!
附录
A. 扩展速查表
| 扩展 | npm 安装 | 源码仓库 | 核心工具/命令 | 一句话用途 |
|---|---|---|---|---|
| pi-shepherd | npm:pi-shepherd | GitHub | shepherd_rules, 规则驱动的钩子引擎 | AI 行为守卫 |
| pi-ate-memory | npm:pi-ate-memory | GitHub | memory_update, memory_index | 跨会话知识持久化 |
| pi-roadmap | npm:pi-roadmap | GitHub | roadmap_plan, roadmap_next, roadmap_done, roadmap_search, roadmap_update 等 | 任务拆解、进度追踪、依赖管理 |
| pi-context-manager | npm:pi-context-manager | GitHub | payload_analyze, /record, /context, /distill-config, /aging-config 等 | 上下文质量控制 + Token 诊断 |
| pi-session-analyzer | npm:pi-session-analyzer | GitHub | session_search, session_analyze | 历史会话搜索、分析、接手报告 |
| pi-ate-smart-compact | npm:pi-ate-smart-compact | GitHub | /smart-compact, /smart-compact-config | 长会话智能压缩 |
| pi-ate-scheduler | npm:pi-ate-scheduler | GitHub | schedule, /loop, /remind, /tasks | 定时任务和提醒 |
| pi-ate-workflow | npm:pi-ate-workflow | GitHub | registerWorkflowTool(供其他扩展调用) | 工作流框架库 |
| @pi-atelier/shared-utils | npm:@pi-atelier/shared-utils | GitHub | logger, storage, paths, json, validator, settings-backup, file-lock | 扩展开发工具库 |
| pi-journal | npm:pi-journal | GitHub | /journal, journal | 日志报告生成(git 活动 + 会话事件 + 记忆变更) |
| pi-usage-stats | npm:pi-usage-stats | GitHub | usage_stats | 工具使用次数统计 |
💡 安装方式:在
settings.json的packages数组中添加"<npm包名>"或"npm:<npm包名>"。开发者也可以用"git:github.com/catlain/pi-<name>"从源码安装。
B. 推荐扩展组合
个人项目(轻量组合)
{
"packages": [
"npm:pi-ate-memory",
"npm:pi-roadmap",
"npm:pi-ate-smart-compact"
]
}
核心三件套:记住知识 + 管理任务 + 长会话不笨。
团队项目(标准组合)
{
"packages": [
"npm:pi-shepherd",
"npm:pi-ate-memory",
"npm:pi-roadmap",
"npm:pi-session-analyzer",
"npm:pi-ate-smart-compact"
]
}
加上规矩和复盘能力。
大型重构(全量组合)
{
"packages": [
"npm:pi-shepherd",
"npm:pi-ate-memory",
"npm:pi-roadmap",
"npm:pi-context-manager",
"npm:pi-session-analyzer",
"npm:pi-ate-smart-compact",
"npm:pi-ate-scheduler"
]
}
全量安装,充分利用诊断和自动化能力。
C. pi 内部机制速览
Compaction(压缩)
pi 内置了上下文压缩机制。当对话历史接近上下文窗口上限时,pi 会自动压缩旧对话。Smart Compact 扩展增强了这个机制——它会识别关键信息(决策、约定、结论)优先保留。
Distill(蒸馏)
工具返回的结果可能很大(比如读取一个 1000 行的文件)。pi 内置了 distill 机制来压缩工具输出。pi-context-manager 扩展提供了:
- 自动 distill:超过阈值(
/distill-config)的工具输出自动压缩 - 首次全文上限:
firstSeenCap(/distill-config --cap)限制首次输出大小 - Tool Result Processor:特定工具类型的格式化精简(
/processor-config) - Aging 淘汰:自动淘汰旧工具输出(
/aging-config)
Tool Call 生命周期
1. AI 决定调用工具
│
▼
2. Shepherd tool_call 钩子(rewrite / block / notify / steer)
│
▼
3. 执行工具
│
▼
4. Context Manager distill + processor 处理返回值
│
▼
5. Shepherd tool_result 钩子(notify / steer)
│
▼
6. AI 生成回复
│
▼
7. Shepherd message_end 钩子(steer / notify)
│
▼
8. 结果返回给用户
...
▼
9. Shepherd agent_end 钩子(notify:提醒 commit / 记忆更新)
会话存储
所有会话数据存储在 ~/.pi/ 目录下:
~/.pi/
├── roadmap/ # 全局路线图
└── agent/
├── settings.json # 全局配置(安装扩展、provider)
├── mcp.json # MCP server 配置
├── memory/ # 全局记忆文件(L1)
├── skills/ # 全局技能
├── extensions/ # 内联扩展
├── agents/ # 子代理定义
├── npm/node_modules/ # npm 安装的扩展包
├── git/ # git 包安装位置
├── sessions/ # 会话历史记录(JSONL)
├── distill/ # context-manager 的数据
│ └── recordings/ # payload 录制文件
{project}/.pi/
├── settings.json # 项目级配置(覆盖全局)
├── memory/ # 项目级记忆(L2)
└── roadmap/ # 项目级路线图
D. 常见问题
Q: 安装扩展后不生效?
检查:
settings.json格式是否正确(JSON 语法)- 包名是否拼写正确
- 重启 pi(扩展需要重启才能加载)
Q: 记忆文件太多怎么办?
pi-memory 会自动检查文件数量。超过 25 个时建议清理,超过 40 个会拒绝写入。清理方法:
- 合并同主题的多文件
- 删除过时的记忆
- 大文件拆分为小文件
Q: Shepherd 规则不生效?
检查:
- 全局规则通过
shepherd_rules工具管理(或编辑~/.pi/agent/extensions/shepherd/rules.json) - 项目规则放在
.pi/extensions/shepherd-rules.json - 确认规则中
"enabled": true(或省略enabled字段,默认生效)。如需禁用规则,设"enabled": false - 规则文件变更即时生效,无需额外操作
message_end钩子匹配的是 AI 回复文本,不是工具参数——不要写匹配工具参数的 pattern
Q: Token 用得太快?
- 用
payload_analyze的budget和expensive模式找出 token 消耗大户 - 用 compact 模式搜索(
semantic_code_search(compact: true)) - 调低 distill 阈值(
/distill-config) - 配置 aging 自动淘汰旧内容(
/aging-config)
Q: payload_analyze 报“无录制文件“?
需要先开启录制:/record on。录制期间正常使用,用完后 /record off。