会话与分支¶
Pi 的会话不是普通聊天数组,而是 append-only JSONL 树。这个设计支撑了 resume、fork、tree navigation、branch summary、compaction、label 和扩展自定义状态。
本章涉及的 entry 类型、buildSessionContext() 和 runtime replacement 行号见源码索引。
关键源码:
packages/coding-agent/src/core/session-manager.tspackages/coding-agent/src/core/agent-session-runtime.tspackages/coding-agent/src/core/messages.ts
JSONL Entry 模型¶
会话文件第一行是 SessionHeader,后续每行是一个 SessionEntry。每个 entry 都有:
idparentIdtimestamptype
主要 entry 类型:
| 类型 | 用途 | 是否进入 LLM context |
|---|---|---|
message |
user/assistant/toolResult 等标准消息 | 是 |
model_change |
记录模型切换 | 否,但恢复 session 时用于选择模型 |
thinking_level_change |
记录 thinking level | 否,但恢复时用于状态 |
compaction |
保存压缩摘要和 firstKeptEntryId |
通过 summary message 进入 |
branch_summary |
保存离开分支的摘要 | 通过 branch summary message 进入 |
custom |
扩展持久化私有状态 | 否 |
custom_message |
扩展注入上下文消息 | 是 |
label |
给 entry 加书签/标记 | 否 |
session_info |
会话名等元数据 | 否 |
为什么是树¶
普通聊天数组只能表示一条线。Pi 要支持:
- 从历史某点 fork。
- 在
/tree中切换 leaf。 - 保留旧分支但在新分支继续工作。
- 在离开分支时把分支工作总结带入另一边。
因此 SessionManager 维护 leafId,追加 entry 时把 parentId 指向当前 leaf,再把 leaf 前移到新 entry。
当前 leaf 决定了发给模型的路径。如果 leaf 在 F,上下文是 A -> B -> E -> F,不会包含 C -> D。
buildSessionContext()¶
buildSessionContext(entries, leafId, byId) 是会话树到模型上下文的关键转换:
- 从 leaf 沿
parentId回溯到 root。 - 反转得到当前分支路径。
- 扫描路径恢复最新 model 和 thinking level。
- 找最后一个 compaction entry。
- 如果有 compaction:
- 先插入 compaction summary message。
- 再插入从
firstKeptEntryId到 compaction 之前的保留消息。 - 再插入 compaction 之后的消息。
- 如果没有 compaction,就按路径插入所有可见消息。
这意味着会话文件保留完整历史,但 LLM 看到的是“当前 leaf 的解析视图”。
自定义消息与上下文¶
packages/coding-agent/src/core/messages.ts 通过 TypeScript declaration merging 扩展 AgentMessage:
bashExecution: 用户通过!运行 shell 的记录。custom: 扩展注入的消息。branchSummary: 离开分支的摘要。compactionSummary: 历史压缩摘要。
convertToLlm() 决定这些消息如何进入模型上下文:
bashExecution转成 user text,除非excludeFromContext。custom转成 user message。branchSummary和compactionSummary用固定前后缀包裹摘要。
这是一种很实用的模式:应用层可以拥有丰富消息类型,但 provider 层只看到标准消息。
Runtime 替换¶
AgentSessionRuntime 负责当前 session 与 cwd-bound services。切换 session、new session、fork 时,它会:
- 触发
session_before_switch或session_before_fork。 - 触发旧 session 的
session_shutdown。 - dispose 旧
AgentSession,使旧 extension context 失效。 - 用新的 cwd/sessionManager 重新创建 runtime。
- 调用 UI/宿主提供的 rebind 回调。
这个设计避免扩展持有旧 session context 后继续写入错误会话。源码里还专门有 stale context 的错误提示。
设计启发¶
- 对 agent 会话来说,append-only 树比可变数组更适合审计、分支和恢复。
- “存储全历史”和“发送给模型的上下文”应该是两个不同概念。
- 扩展状态分成
custom和custom_message很清晰:一个只给扩展恢复,一个参与 LLM context。 - 切换 session 时让旧 context 显式失效,可以减少扩展里的悬挂引用问题。