PiAgent 10|会话树:JSONL 如何支持恢复与分支#
从
parentId、当前 leaf 到回退和分叉,理解 Pi 为什么不用“可变消息数组”保存 Agent 历史
如果会话只是一个数组,回退就意味着删除后面的消息;删除会让调试、审计和恢复失去证据。Pi 把会话保存成带父子关系的 JSONL 树:回退只移动当前 leaf,从旧节点继续工作自然产生新分支,旧路径仍然保留。
1. 文件里保存的不是只有消息#
会话文件通常包含一个 header 和多个 entry:
header
└─ model_change
└─ user message
└─ assistant tool call
└─ tool result
└─ assistant answerentry 至少需要自己的 ID、parentId、时间戳和类型。消息只是其中一种类型,其他类型还可以表达模型变化、压缩摘要、分支摘要、标签和扩展自定义数据。
这种设计解决了两个问题:历史结构不会被“重新拼字符串”破坏,恢复时也不需要猜某一行到底代表用户消息还是运行元数据。
2. 追加、回退与分支#
追加
新 entry 的 parentId 指向当前 leaf,旧 entry 不修改。追加是最常见的操作,也最容易保证不可变历史。
回退
回退把活动 leaf 移到较早节点,但不删除后续节点。当前上下文只沿新的 leaf 向父节点回溯,旧分支仍可查看。
分支
在历史节点上继续输入,会创建新的子节点。它不是复制整个文件,而是在同一棵树上产生另一条路径:
root → A → B → C 原路径
└→ D → E 从 B 继续的新分支3. 当前上下文是 leaf 的祖先路径#
恢复会话时,不能把 JSONL 所有行按文件顺序全部发送给模型。正确过程是:
- 找到当前 leaf。
- 沿
parentId向上收集祖先。 - 反转为从 root 到 leaf 的顺序。
- 过滤不进入 LLM 的元数据和 UI 消息。
- 交给消息转换和 Provider 适配层。
文件事实:所有分支都存在
当前上下文:只有当前 leaf 的祖先路径可见这一区分是分支功能正确性的核心。历史存在不代表当前模型可见。
4. SessionManager 的职责#
SessionManager 负责会话文件和树结构,不负责模型推理。它通常提供:
- 创建或打开 JSONL 会话。
- 追加 entry 与移动当前 leaf。
- 从路径恢复和遍历树。
- 创建分支、添加标签和记录模型变化。
- 写入压缩摘要与分支摘要。
- 在内存模式下提供相同的会话接口。
AgentSession 将 Agent 的消息生命周期接到 SessionManager:消息结束后,必要的 user、assistant 和 tool result 被持久化;UI 状态、瞬时进度和监听器本身不应被当作永久消息写入。
5. 为什么 JSONL 适合 Agent#
JSONL 的特点是“一行一个 entry,追加友好”:
- 崩溃前已经写入的历史更容易保留。
- 可以流式读取,不必一次解析整个巨大 JSON 数组。
- 每行是结构化对象,便于诊断和迁移。
- 父子 ID 让分支不依赖数组下标。
它并不自动保证强一致性。生产环境还需要处理半行写入、重复 entry、文件锁、并发追加、损坏恢复和版本迁移。
6. 本地 CLI 与 Web 服务的不同默认值#
本地 Coding Agent 通常按工作目录隔离会话,默认路径类似:
~/.pi/agent/sessions/<encoded-cwd>/<session>.jsonl这适合单用户、单机和人工调试。Web 服务则需要:
- 用户 ID 与 session ID 的双重隔离。
- 数据库或对象存储中的受控持久化。
- 并发请求的乐观锁或队列。
- 访问权限和删除策略。
- 不把服务器当前工作目录当作租户边界。
短期无状态服务可以使用 SessionManager.inMemory(),但这意味着进程重启会丢失历史。要恢复对话,就必须在消息或 entry 层把必要状态写入自己的存储。
7. 恢复不是重新 prompt 一遍#
一个可靠的恢复流程应该让模型看到与上次一致的工作上下文,而不是把用户最后一句话再发一次。至少要恢复:
- 当前分支路径。
- 模型和 thinking level 变化。
- 压缩摘要及其保留边界。
- 必要的工具结果。
- 扩展需要的自定义状态。
恢复后仍要重新装配当前的工具和资源,并重新验证权限。历史记录可以证明过去发生了什么,但不能自动授予现在仍然有效的权限。
常见误区
- 回退时物理删除后续 entry,失去调试证据。
- 将所有 JSONL 行都发送给模型,混入其他分支和 UI 元数据。
- 用数组下标配对 entry,插入或分支后关系失效。
- 认为
SessionManager会自动解决多用户隔离。 - 恢复旧会话时沿用旧权限,不重新检查当前身份和工具策略。
小结
会话树把 Agent 历史从“可变数组”变成“可导航的事实图”:追加只增加节点,回退只改变活动 leaf,分支不破坏旧路径,当前上下文由祖先路径投影得到。JSONL 负责保存结构,SessionManager 负责导航,AgentSession 负责把运行消息接入这棵树。
源码定位
- 会话管理:
packages/coding-agent/src/core/session-manager.ts - Session 与持久化连接:
packages/coding-agent/src/agent-session.ts - 会话格式说明:官方文档
session-format - 压缩与分支摘要:
packages/coding-agent/src/