Pi实战 14:树状会话历史篇——会话的“版本控制“
Pi实战 14树状会话历史篇——会话的版本控制来源Pi 官方《Sessions》文档、社区关于/tree上下文管理的实践StackToHeap、developersdigest、以及 Pi 会话的内部数据结构分析。第 04 篇把会话树当作多代理经验之一提了一句。这篇专门深挖它——因为树状会话历史很可能是 Pi 最被低估的能力它让你像用 git 一样对待对话可分支、可回退、可分享是管理上下文窗口的核心手段。本文按你要的三块展开使用场景 → 实现原理 → 实战例子中间穿插命令速查。一、使用场景什么时候该用它普通聊天是一条直线说错一句只能/undo一步步回或者/new全丢。Pi 的会话是一棵树这带来几个真实痛点的全部解法场景 1Agent 跑偏进死胡同。它改着一改着把你带沟里了越改越乱。你不需要/undo连按十几次直接/tree跳回最后一个还算正常的点从那儿重走。场景 2同一套上下文试多个方案。重构一个模块想分别试保守重构 / 激进重写 / 只抽函数三种。每种都开新会话要重新喂一遍上下文用树的话在同一个点分三个叉互不污染最后挑一个。场景 3一系列相似任务共享同一上下文。先加载一个技能 需求跑一次/grill-me把意图对齐之后每个子任务都从这点对齐好的状态分支出去执行“Do A”、“Do B”、“Do C”不用重复前置对话。场景 4噪声探索不污染主线。你想让 Agent 放手去试翻日志、跑实验、走死胡同但又不想让这些噪声塞满主线的上下文。分支出去探索把结论落成文件然后跳回主线继续——主线始终干净。场景 5一个会话内模拟角色分离。开一个分析分支做调研、一个实现分支写代码类似子代理的分工但全在一个会话文件里切换零成本。场景 6分享 / 另起。把整条会话回退到某点、分支、分享给别人或从旧会话的某个状态fork出一个全新任务。一句话心智模型StackToHeap 总结得很准Pi 的会话是一棵树不是一条时间线。想清楚这点用法就通了。二、实现原理它凭什么能版本控制1. 会话存成一个 JSONL 文件每条记录是一棵树的节点。路径在~/.pi/agent/sessions/org/repo/_.jsonl按工作目录组织一个会话一个文件。文件里每条 entry 有id和parentId——这就是树的来源追加式append-only树不是线性列表。2. 一个叶子指针leafId跟踪你当前在树的哪个位置。追加一条消息 给当前 leaf 生一个子节点。分支 把 leaf 指针移到更早的某个节点上但不改动任何历史。你接着发的下一条消息就成了那个旧节点的一个新子节点——于是旧的延续和新的延续变成兄弟分支。3. 组装模型上下文时从 leaf 一路走回 root。buildSessionContext()从当前 leaf 回溯到根把路径上的消息拼成数组喂给模型。结果只有你当前所在的激活分支在上下文里但所有分支都完整保留在文件中。这就是切换分支 切换上下文的机制。4. 分支摘要branch_summary——带走要点不带 token。用/tree从一条分支切到另一条时Pi 可以把离开的那条分支总结成一段摘要附到新位置上。你带走的是我刚才试过啥、结论是啥而不是整条分支的 token 细节。5. Compaction压缩也是非破坏的。压缩时 Pi 不是删旧消息而是append 一个 summary entry只在组装未来上下文时才用它替换掉那段老历史。原历史仍在文件里。6. 节点类型不止对话。一条 entry 可能是user/assistant/tool工具调用/model_change/thinking_level_change/compaction/branch_summary/label你打的书签/custom扩展状态不进模型上下文/custom_message扩展注入、会进模型上下文/session_info会话名。7. 版本演进。v1 是线性的v2 引入树v3 把hookMessage改名custom旧会话加载时自动迁移。一张图看懂分支在某个点/tree后试了 approach Bsession root └── user: implement feature └── assistant: approach A ├── user: continue with A ← 原始分支 │ └── assistant: ... └── user: try approach B ← /tree 回退后新建的分支 └── assistant: ...你在try approach B这条线上时continue with A那条完全不受影响随时/tree跳回approach A继续。三、命令速查命令作用/tree导航当前会话树跳到任意节点继续/fork从某个历史点建新会话文件/clone把当前激活分支复制成一个新会话文件/resume或启动pi -r浏览并选择历史会话/new开新会话/name 名字给会话起名好找pi --name ...启动时设/session看当前会话文件路径、ID、消息数、token、花费/compact [prompt]压缩旧上下文/export [file]导出会话/share分享会话CLI 侧命令作用pi -c继续最近一次会话pi -r启动即浏览历史会话pi --session path|id指定会话文件/部分 IDpi --fork path|id从某会话 fork 出新会话pi --no-session临时模式不保存/treevs/forkvs/clone怎么选输出视图典型用途/tree同一会话文件整棵树在一个工作流内快速跳分支/fork新会话文件历史消息选择器从更早的某句话另起一个新任务/clone新会话文件当前激活分支继续前先复制一份当前工作原则想就地试 alternatif 用/tree某分支变成独立任务/交付物时用/fork或/clone拆成单独文件。四、/tree里怎么操作敲/tree或连按两次Esc打开会话树导航器方向键上下移动选择节点。ShiftL给当前节点打 label书签ShiftT切换 label 上是否显示时间戳。CtrlO循环过滤模式default / no-tools隐藏工具调用/ user-only / labeled-only只看打了书签的/ all。选中一个user 消息leaf 移到它的父节点并把这句话丢进输入框让你改——你一改重发就新建了一个分支。选中assistant / tool 条目leaf 移到那从该点继续。关键细节在/tree里选 user 消息时你是回到它发出之前所以重发会生成分支选 assistant/tool 则是接着它的结果继续。两者都是新建分支原历史一字不改。五、实战例子实战 1最小场景——跑偏了跳回去你: 把这个函数的错误处理重写下 Agent: 改了一堆引入了新问题来回扯皮三轮 你: /tree → 方向键选到 把这个函数的错误处理重写下 的上一句正常状态 → 回车输入框已载入那句话改成重写错误处理但保留原有返回值结构只加日志 → 重发重发的瞬间一条新分支诞生原来那三轮混乱留在旁边随时能回看。实战 2同一上下文试 3 个重构方案你: 重构 auth 模块先别动代码给三个方案 Agent: 方案A 保守、方案B 抽层、方案C 重写 你: /tree → 选到 Agent 给出三方案那条 assistant 消息 → 继续发按方案A实现 ← 分支1 看完方案A结果/tree 回退到同一点 → 按方案B实现 ← 分支2与分支1兄弟互不影响 再 /tree 回退 → 按方案C实现 ← 分支3 你: /tree → 三个方案结果并排都在挑一个继续深入比起开三个新会话各自重新喂auth 模块背景 三方案这里前置对话只发生一次。实战 3噪声探索不污染主线推荐工作流想大胆试错但保持主线干净照 StackToHeap 这套 5 步1. /tree 到或就在主线某点分支出去 2. 噪声探索翻日志、跑实验、走死胡同 3. 把有用结论落盘让 Agent 把结论写进 notes.md 4. /tree 跳回主线 5. 继续主线读 notes.md 消费探索结果主线上下文里永远只有干净的决策 结论文件路径没有大段试错过程。实战 4分支摘要 分享会话长会话切分支时Pi 提示要不要给离开的分支做摘要选是你: /tree → 切到另一分支 → 提示 Summarize the branch youre leaving? 选 Yes → 生成 branch_summary 附在新位置之后把整条会话/share给别人对方拿到的是带分支结构、且关键岔路有摘要的完整历史不是流水账。实战 5命令行 fork——从旧会话另起任务不必进交互模式直接从一个历史会话 fork# 先看有哪些会话带名字/消息数/目录pi-r# 从某个会话文件或部分 IDfork 出一个新会话接着干pi--fork~/.pi/agent/sessions/acme/shop/2026-09-20_14-03.jsonl适合上次那个会话的状态我还想要但要朝新方向走、且不想动原会话。六、避坑分支 ≠ 改历史。跳回旧节点再发消息建的是新分支原分支一字不改。别指望它覆盖过去。当前上下文只含激活分支。/tree跳走后上一条分支的内容不在模型上下文里了——需要的话靠 branch_summary 带过来或探索前先落盘。探索结论一定要落成文件。否则跳回主线后那条分支的产出就看不见了它还在文件里但不在上下文里Agent 也不会主动读。/fork和/clone别混。/fork从历史某点起新文件/clone复制当前这条激活分支。会话文件就是 JSONL。删文件 删会话在/resume里按CtrlD删除Pi 会优先用trashCLI如果装了而不是直接永久删。给会话起名。用/name或pi --name否则/resume里一堆无标题会话很难找。参考资料Pi 官方《Sessions》https://pi.dev/docs/latest/sessionsStackToHeapManaging Context Windows with pi /tree — https://stacktoheap.com/blog/2026/02/26/pi-tree-context-window-managementdevelopersdigestHands-On With Pi: Run Modes and JSONL Session Trees — https://www.developersdigest.tech/blog/pi-hands-on-run-modes-session-trees-guideGitHub 讨论What is the point of the tree functionality? — https://github.com/earendil-works/pi/discussions/5205Pi 会话内部结构分析Inside Pi Agenthttps://edward40.com/p/pi-agent-internalpi-session-tree-browser可视化浏览会话树https://pi.dev/packages/pi-session-tree-browser上一篇13 · 技能实战篇