首页
/ pi 会话管理实战:JSONL 树形存储、/tree 分支导航与分支摘要机制

pi 会话管理实战:JSONL 树形存储、/tree 分支导航与分支摘要机制

2026-09-05 10:02:22作者:廉皓灿Ida

本篇指南围绕 pi(AI agent toolkit)coding agent 的会话(Session)体系展开:从会话的自动存储与命令行控制,到 /resume/tree/fork/clone 等交互命令的完整用法,再到 JSONL 树形结构、分支切换与分支摘要的底层实现原理。读完后,你将能够熟练地续接、命名、分支和复用 pi 的历史会话,并理解其源码中 SessionManager 的组织方式与上下文重建逻辑。

pi 会话树视图:/tree 命令在树形会话上的分支导航界面

会话存储:按工作目录组织的 JSONL 树

Pi 将每次对话保存为会话(session),让你可以续接工作、从较早的轮次分叉、回溯之前的路径。会话自动保存到 ~/.pi/agent/sessions/,并按启动 pi 时的工作目录(cwd)分子目录组织,每个会话是一个 JSONL 文件,内部采用树形(tree)结构而非线性列表。

从源码可以确认这一布局:config.tsgetSessionsDir() 返回 ~/.pi/agent/sessions 路径,而 session-manager.tsforkFrom() 实现了文件命名规则——时间戳(冒号、点替换为连字符)加上下划线和会话 UUID,即 <timestamp>_<uuid>.jsonl。会话文件的第一行是 SessionHeader(不含 id/parentId,不属于树本身),后续每一行都是一个带 idparentId 的树节点,例如:

{"type":"session","version":3,"id":"uuid","timestamp":"2024-12-03T14:00:00.000Z","cwd":"/path/to/project"}

对于通过 /fork/clonenewSession({ parentSession }) 创建的子会话,header 会额外携带 parentSession 字段指回源会话文件路径。当前仓库实现的会话版本为 3(CURRENT_SESSION_VERSION = 3),旧版本会话在加载时会自动迁移。各版本区别详见 Session Format 文档

会话相关的 CLI 启动参数

以下参数用于控制 pi 启动时的会话行为,均可通过 pi --help 确认(参数解析见 args.ts):

pi -c                  # Continue most recent session
pi -r                  # Browse and select from past sessions
pi --no-session        # Ephemeral mode; do not save
pi --name "my task"    # Set session display name at startup
pi --session <path|id> # Use a specific session file or partial session ID
pi --fork <path|id>    # Fork a session file or partial session ID into a new session

这些参数在源码中的落点各有不同:

  • -c / --continue:调用 SessionManager.continueRecent(cwd),在默认会话目录(按当前 cwd 编码出的子目录)中找到最近修改的会话文件直接打开;若不存在则新建一个空会话。
  • -r / --resume:启动时打开交互式会话选择器,功能与交互模式下的 /resume 一致。
  • --no-session:走 SessionManager.inMemory(cwd) 路径,构造一个不持久化到磁盘的内存会话(persisted 标记为 false),适合临时试错。
  • --name / -n:在会话中追加一条 session_info 类型条目,设置人类可读的显示名。
  • --session <path|id>:支持完整路径或部分会话 ID,通过 SessionManager.open() 打开指定会话文件。
  • --fork <path|id>:将某个会话文件(或部分 ID)复制成一个新会话。源码中的 forkFrom() 会为新文件生成新 UUID、写入带 parentSession 的 header,再逐行拷贝源会话的所有非 header 条目。它甚至支持跨项目分叉——目标 cwd 与源不同,新会话存入目标项目的目录。

在交互模式下,随时用 /session 查看当前会话文件路径、会话 ID、消息数、token 消耗与费用。

会话命令一览

交互模式下的斜杠命令覆盖了会话的生命周期操作:

命令 说明
/resume 浏览并选择历史会话
/new 开始新会话
/name <name> 设置当前会话显示名
/session 显示会话信息
/tree 在当前会话树中导航
/fork 从某条历史用户消息创建新会话
/clone 将当前活动分支复制为新会话
/compact [prompt] 摘要较早的上下文,详见 Compaction 文档
/export [file] 将会话导出为 HTML
/share 上传为私有 GitHub gist,获得可分享的 HTML 链接

恢复与删除会话

/resume 打开当前项目的交互式会话选择器,pi -r 则在启动时打开同一个选择器。选择器中的数据来自 SessionManager.list(cwd)——它扫描当前工作目录对应的会话子目录,聚合出每个会话的路径、ID、cwd、显示名、创建/修改时间、消息数与首条消息预览。

在选择器中可以:

  • 直接输入文字进行模糊搜索
  • Ctrl+P 切换路径显示
  • Ctrl+S 切换排序模式
  • Ctrl+N 过滤到已命名的会话
  • Ctrl+R 重命名会话
  • Ctrl+D 删除会话,随后按确认键

当系统安装了 trash CLI 时,pi 会优先用它删除会话而不是永久删除文件,降低误删风险。另外,直接删除 ~/.pi/agent/sessions/ 下对应的 .jsonl 文件也能移除会话。

命名会话

给会话取一个人类可读的名字,是长期管理多个工作线程的关键习惯:

/name Refactor auth module

也可以在启动时通过 --name / -n 一并设置:

pi --name "Refactor auth module"
pi --name "CI audit" -p "Review this build failure"

从存储角度看,命名操作会在会话中追加一条 session_info 类型条目({"type":"session_info","name":"Refactor auth module"});显示名取自最新一条 session_info 条目,即后续 /name 会覆盖旧名。在选择器中,已命名的会话会显示该名字而非首条消息文本,使 pi -r 中的检索更直观。

/tree 在树中分支

会话以树的形式存储:每个条目有 idparentId,当前位置(leaf)就是树的某个活动叶子节点。/tree 让你在同一个会话文件内跳到任意历史节点并从中继续,无需新建文件。

一个典型的分支形态如下(某条助手回复后分出了两条不同的探索路径,A 是活动分支):

├─ user: "Hello, can you help..."
│  └─ assistant: "Of course! I can..."
│     ├─ user: "Let's try approach A..."
│     │  └─ assistant: "For approach A..."
│     │     └─ user: "That worked..."  ← active
│     └─ user: "Actually, approach B..."
│        └─ assistant: "For approach B..."

在源码中,SessionManager.branch(entryId) 负责把 leaf 移动到某个更早的条目上,getTree() / getBranch(fromId) / getChildren(parentId) 提供完整的树查询能力,/tree 视图就是基于这些 API 渲染的。

树视图操作键

动作
↑/↓ 在可见条目间导航
←/→ 上/下翻页
Ctrl+← / Ctrl+→ 或 Alt+← / Alt+→ 折叠/展开,或在分支段之间跳转
Shift+L 为选中条目设置或清除标签(label)
Shift+T 切换标签时间戳显示
Enter 选中条目
Escape / Ctrl+C 取消
Ctrl+O 循环切换过滤模式

过滤模式共五种:defaultno-tools(隐藏工具调用)、user-only(只看用户消息)、labeled-only(只看打过标签的条目)、all。可以在 Settings 配置 中用 treeFilterMode 字段设定打开 /tree 时的默认模式(该字段在 settings-manager.ts 中定义为这五个字面量类型之一)。

选中条目的行为语义

不同条目类型被选中后,行为有明确区分:

选中用户消息或自定义消息时:

  1. leaf 移动到该消息的父条目(即把该消息"退回到"待发送状态);
  2. 该消息文本被放入编辑器;
  3. 你可以编辑后重新提交,从而创建一个新分支——这是"修改早前指令、换一种问法"的标准操作。

选中助手、工具、compaction 或其他非用户条目时:

  1. leaf 直接移动到该条目;
  2. 编辑器保持为空;
  3. 从这一点继续对话即可。

选中根用户消息时: leaf 重置到空对话(resetLeaf() 语义),原始 prompt 放入编辑器供你修改重发——相当于"带着原始需求重新开始"。

/tree/fork/clone 的取舍

三个命令都提供"从历史某点继续"的能力,但输出形态不同:

特性 /tree /fork /clone
输出 同一会话文件 新会话文件 新会话文件
视图 完整树 用户消息选择器 当前活动分支
典型用途 就地探索多种替代方案 从较早的 prompt 开一个新会话 继续当前工作前先复制一份
摘要 可选的分支摘要

经验法则是:想保留多种替代路径互相参照就用 /tree(所有分支共享一个文件,随时可以来回切换);想要相互独立、互不干扰的会话文件就用 /fork/clone/clone 对应源码中的 createBranchedSession(leafId),把当前活动分支抽取成独立文件;/fork 则提供一个用户消息选择器,让你指定从哪条历史 prompt 分叉出去。

分支摘要:离开分支前保留关键上下文

当你通过 /tree 从分支 A 切到分支 B 时,分支 A 中已积累的重要上下文(做过哪些尝试、读过哪些文件、得出什么结论)如果不加处理就会"丢失"。Pi 的解决方案是分支摘要(branch summary):切换分支时,pi 可以调用 LLM 对被放弃分支(从分叉点到 leaf 的那段路径)生成一份摘要,并把摘要作为 branch_summary 条目附加到新位置。这样你保留了离开路径的关键信息,又不用重放整个分支。

被提示时你可以三选一:

  1. 不生成摘要(no summary)
  2. 用默认 prompt 生成摘要
  3. 用自定义的关注点说明生成摘要

存储上,branch_summary 条目记录 fromId(从哪个条目分出的)与 summary 文本,可选携带 usage(生成摘要消耗的 LLM 用量,计入会话 token 与费用统计)、details(文件追踪数据,如 readFiles/modifiedFiles)和 fromHook 标记(扩展生成还是 pi 生成)。摘要生成时还会自动追踪该分支期间读取/修改过的文件并写入 details,这是摘要"有据可依"的来源。分支摘要的扩展钩子与内部实现细节见 Compaction 文档Session Format 文档

会话格式与 SessionManager API 概览

会话文件是 JSONL,每一行一个 JSON 对象且带 type 字段。除首行 header 外,所有条目都继承 SessionEntryBasetype、8 位十六进制 idparentId、ISO timestamp),并由此连成树。条目类型包括:

  • message:对话消息,message 字段是一个 AgentMessage(用户消息、助手消息、工具结果、bash 执行记录、自定义消息等);
  • model_change / thinking_level_change:记录会话中途切换模型或思考级别的时刻;
  • compaction:上下文压缩检查点,携带 summarytokensBefore,新式压缩还会把压缩后保留的尾部消息物化进 retainedTail,使检查点自包含;
  • branch_summary:如上所述的分支切换摘要;
  • custom / custom_message:扩展持久化状态(参与 LLM 上下文)与扩展注入消息(参与上下文);
  • label:用户对条目的书签标记;
  • session_info:会话元数据,如 /name 设置的显示名。

上下文重建由 SessionManager 的两个方法完成,这也是树形存储能正确处理压缩与分支的关键:

  1. buildContextEntries() 从当前 leaf 走到根,收集活动路径上的条目,并遵守压缩语义(retainedTail 存在时以压缩条目为自包含检查点,否则按 firstKeptEntryId 回溯保留区间);
  2. buildSessionContext() 在此基础上产出最终发给 LLM 的消息列表,同时从完整路径中提取当前模型与思考级别设置。

完整的字段定义(各消息接口的 TypeScript 定义、header 示例、树结构图解、解析示例代码)以及 SessionManager 的完整 API——静态方法(create / open / continueRecent / inMemory / forkFrom / list / listAll)、追加方法(appendMessage / appendCompaction / appendSessionInfo 等)、树导航方法(branch / branchWithSummary / getTree / getBranch 等)——都记录在 Session Format 文档 中;如果你要写扩展、SDK 集成或外部解析器,该文档是必读参考。

小结

pi 的会话系统用"JSONL + id/parentId 树"这一极简存储实现了三件大事:会话内分叉/tree 原地切换叶子节点并可选生成分支摘要)、会话间分叉/fork / --fork 生成带 parentSession 血缘的新文件)、上下文经济(压缩检查点与分支摘要共同避免上下文重复)。配合 /resume 选择器、命名过滤与 trash 安全删除,多任务、多假设的 agent 工作流可以在一套文件系统约定下长期演进。

登录后查看全文
热门项目推荐
相关项目推荐