LLM 的上下文窗口是有限的——Claude Sonnet 4.5 是 200K,MiniMax M3 是 1M(API 保证 512K)。
听起来很大,但实际使用很快就会塞满:
- 一次 read 工具调用可能返回几千 token
- Bash 命令输出经常上万 token
- 多轮对话 + 工具调用的累积
塞满之后会发生:
- AI 开始「忘记」之前的指令
- 工具调用参数报错(context too long)
- 响应速度变慢(要处理的内容太多)
- 某些 Provider 直接报错拒绝
Pi 的解决思路:旧消息自动总结,保留关键信息,新对话继续往前走。
Pi 有两套上下文压缩机制,针对不同场景:
| 机制 | 触发时机 | 目的 |
|---|---|---|
| Compaction(压缩) | 上下文超阈值,或 /compact |
总结旧消息腾空间 |
| Branch Summary(分支摘要) | /tree 切换分支 |
切分支时保留离开分支的上下文 |
两套机制用同一个结构化摘要格式,累积追踪文件操作——多次压缩不会丢失文件操作历史。
Pi 自动触发压缩的判断逻辑:
contextTokens > contextWindow - reserveTokens
reserveTokens 默认 16384(16K),给 LLM 的响应留空间。
举例:用 200K 上下文的 Claude 模型,剩 184K 空间时自动触发压缩。
/compact
/compact 重点保留文件路径和决策,不要细节对话
第二个用法是 自定义摘要重点——告诉 AI「我希望你重点保留什么」。
压缩前:
entry: 0 1 2 3 4 5 6 7 8 9
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│hdr │usr │ass │tool│usr │ass │tool│tool│ass │tool│
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
压缩后:
entry: 0 1 2 3 4 5 6 7 8 9 10
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│hdr │usr │ass │tool│usr │ass │tool│tool│ass │tool│cmp │ ← 新增压缩条目
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
AI 实际看到的:
┌────────┬─────────┬────┬────┬────┬────┬────┬────┐
│system │summary │usr │ass │tool│tool│ass │tool│
└────────┴─────────┴────┴────┴────┴────┴────┴────┘
↑ ↑ └─── kept messages ───┘
prompt 压缩摘要
关键是:原始消息没删,只是 AI 看不到。 Session 文件保留完整历史,压缩是「视图」层面的操作。
反复压缩时,Pi 不是从压缩点重新开始,而是从上一次压缩的「保留边界」开始:
这样多次压缩累积有效,不会把上一轮已经保留的内容又「忘了」。
在 ~/.pi/agent/settings.json 或项目级 <project>/.pi/settings.json:
{
"compaction": {
"enabled": true,
"reserveTokens": 16384,
"keepRecentTokens": 20000
}
}
| 配置项 | 默认值 | 作用 |
|---|---|---|
enabled |
true |
是否启用自动压缩 |
reserveTokens |
16384 |
给 LLM 响应预留多少 token |
keepRecentTokens |
20000 |
保留最近多少 token 不压缩 |
实战调参建议:
{
"compaction": {
"enabled": true,
"reserveTokens": 32768, // 长输出场景(生成长文、写代码)调大
"keepRecentTokens": 40000 // 不想让 AI 太快「失忆」调大
}
}
注意:keepRecentTokens 调太大,会让自动压缩迟迟不触发,最后反而单次压缩成本更高。
Compaction 是「时间线」上的压缩,/tree 是在「分支」上的导航。
Pi 的每个对话不是线性结构,是树形结构。每条消息有 id 和 parentId,当前所在位置是「活跃叶子」。
├─ user: "Hello..."
│ └─ assistant: "Of course!"
│ ├─ user: "Try approach A..." ← 当前活跃
│ │ └─ assistant: "Approach A works..."
│ └─ user: "Try approach B..." ← 探索过
│ └─ assistant: "Approach B works..."
/tree 命令打开这个树视图,让你 在分支之间跳来跳去。
打开 /tree 后:
| 按键 | 作用 |
|---|---|
| ↑/↓ | 上下移动光标 |
| ←/→ | 翻页 |
| Ctrl+← / Ctrl+→ | 折叠/展开 |
| Shift+L | 给条目打标签 |
| Enter | 选中当前条目 |
| Escape | 取消 |
| Ctrl+O | 切换过滤模式 |
选中 user/custom 消息:
1. 把当前叶子移到选中消息的父节点
2. 选中的消息内容放进编辑器
3. 你可以编辑后重新提交——创建新分支
选中 assistant/tool/压缩条目:
1. 把当前叶子移到该条目
2. 编辑器清空
3. 从那个点继续对话
关键能力:探索多条方案,不用担心「走错了回不去」。 AI 给了 A 方案不满意,跳回决策点试 B 方案。
/tree 切换到另一个分支时,Pi 会问你要不要给「离开的分支」生成摘要。
┌─ B ─ C ─ D (old leaf, 准备离开)
A ───┤
└─ E ─ F (目标分支)
选中目标后,Pi 提示三个选项:
摘要会作为 BranchSummaryEntry 追加到新分支,新分支的 AI 能看到「原来那条分支干了什么」,避免切分支后「失忆」。
| 命令 | 输出 | 用途 |
|---|---|---|
/tree |
同一文件 | 在当前会话探索多个方案 |
/fork |
新文件 | 从历史某个 user 消息开始新会话 |
/clone |
新文件 | 复制当前活跃分支到新会话 |
经验法则:
- 还在探索 → /tree(不用新文件,简化管理)
- 想开新工作线 → /fork 或 /clone
# 命令行启动
pi -c # 续上次会话
pi -r # 浏览历史会话
pi --no-session # 临时模式(不保存)
pi --name "我的任务" # 启动时给会话命名
pi --session <id> # 用特定会话
pi --fork <id> # Fork 特定会话
# 交互模式
/resume # 浏览历史
/new # 新会话
/name <name> # 给当前会话命名
/session # 查看当前会话信息
/tree # 分支导航
/fork # 从历史消息 fork
/clone # 复制当前分支
/compact [prompt] # 手动压缩
/export [file] # 导出 HTML
/share # 上传 GitHub Gist
跑跨多个文件的重构任务时:
/name "重构 v2:xxx 方案" 命名/fork 出来新会话对比/compact 手动触发,配上「保留文件决策和当前进度」AI 给了两种实现方案?
/tree 回到方案选择点/tree 回到原分支继续pi --no-session 跑一次性任务(不存盘)keepRecentTokens,让压缩更激进,省 token原始数据不丢,Session 文件保留完整历史。丢的是「AI 视图」——AI 看到的旧消息被摘要替代。
如果你后面想看原始内容,/export 导出 HTML,里面有完整记录。
默认靠自动。手动压缩适合以下场景:
- 任务切换前(让旧任务摘要掉)
- 想让 AI 「专注」(主动扔掉旧上下文)
- 测试新 prompt(用 /compact 重点关注 xxx)
Escape 取消,不会真的切分支。Tree 视图是只读的,选中 Enter 才生效。
pi -r # 启动时浏览
或交互模式 /resume。
支持:
- 搜索(输入关键词)
- Ctrl+P 切换显示路径
- Ctrl+S 切换排序
- Ctrl+N 只看命名会话
- Ctrl+R 重命名
- Ctrl+D 删除(用 trash CLI,不是 rm)
JSONL 文件,存在 ~/.pi/agent/sessions/,按工作目录组织。每条消息是文件一行,附 id 和 parentId 字段。
上下文管理是 Pi 区别于「一次性 AI 对话」的关键能力。
很多 AI 工具用着用着就「失忆」了,Pi 的解法是:自动压缩保活跃、tree 分支保探索、Session 文件保完整。
三个机制配合,让 Pi 能跑长任务、多方案、需要回溯的真实工作流。
下次跑 Pi 遇到「AI 忘了之前说过什么」,试试 /compact 配上具体指令。
💬 评论区