# 第 2 章:会话与树——把时间掰成分叉
> 历史不是一条线,而是一棵树;你随时可以回到任何一个分岔口重新出发。
## 设计思想
大多数 coding agent 的历史是线性的:压缩有损且不可逆,走错了只能重开。Pi 把会话设计成**单文件内的树结构**——每次交互都是树上的节点,你可以回到任意历史节点原地分叉,探索不同方向而互不覆盖。配合自动压缩(compaction),长会话不炸上下文,且**原始历史永远完整保留**。
## 原理
### 存储格式
- 会话保存为 JSONL 文件,存于 `~/.pi/agent/sessions/`,按工作目录组织,自动保存。
- 每条记录有 `id` 和 `parentId`,整个会话本质是**存在单文件里的一棵树**(格式详见 `docs/session-format.md`)。
### 三种"回到过去"的方式
| 操作 | 行为 | 文件 |
|------|------|------|
| `/tree` | 会话树内**原地导航**:选中任意历史点继续聊、在分支间切换 | 不新建文件,全部历史保留 |
| `/fork` | 从活跃分支上的某条历史 user message **开新会话文件**,复制到该点为止的路径,并把该提示词放回编辑器供修改 | 新文件 |
| `/clone` | 把**当前活跃分支的当前位置**完整复制成新会话文件,编辑器为空 | 新文件 |
CLI 侧:`pi --fork <path|id>` 可直接从既有会话或部分 UUID 分叉出新会话。
### /tree 的操作
- 直接输入文字即搜索;Ctrl+←/→(或 Alt+←/→)在分支间跳转,←/→ 翻页
- Ctrl+O 切换过滤模式:default → no-tools → user-only → labeled-only → all
- Ctrl+X 复制选中消息
- **Shift+L 给节点打标签做书签,Shift+T 切换标签时间戳显示**
### Compaction(上下文压缩)
- **手动**:`/compact` 或 `/compact <自定义摘要指令>`。
- **自动**(默认开启):上下文溢出时触发(恢复并重试);接近上限时主动预防性压缩。可在 `/settings` 或 `settings.json` 配置。
- 压缩**有损**(旧消息被摘要替代),但完整历史仍在 JSONL 文件里——`/tree` 随时可回看;压缩行为可通过 extensions 自定义(内部机制见 `docs/compaction.md`)。
## 用法
### 会话生命周期
```bash
pi -c # 继续最近一次会话
pi -r # 浏览历史会话并选择
pi --no-session # 一次性模式,不保存
pi --name "my task" # 启动时命名
pi --session <path|id> # 使用指定会话(部分 UUID 亦可)
pi --fork <path|id> # 从既有会话分叉新会话
```
交互内用 `/session` 查看当前会话 ID,方便之后 `--session <id>` / `--fork <id>` 复用。
### 典型工作流
- **方案 A/B 对比**:聊到关键决策点,`/tree` 回到分岔口,先走方案 A;不满意再回同一节点走方案 B,两个分支互不干扰。
- **危险操作前存档**:执行大规模重构前,用 `/clone` 复制当前分支;翻车后直接回原会话继续。
- **重写提示词**:`/fork` 选中某条历史提示词,新会话中修改后重发,比较两次结果。
- **长任务防炸上下文**:放着让自动 compaction 处理;发现质量下降时 `/tree` 回到压缩前的节点,换个问法继续。
- **书签导航**:给关键节点 Shift+L 打标签,之后在 labeled-only 过滤模式下快速定位。
- **分享与归档**:`/export` 导出 HTML/JSONL,`/import` 恢复,`/share` 上传为私有 GitHub gist。