π Pi Agent Study
Chapter 05 · session

05 · Session

长期会话如何保存和重建

这一章看 AgentSessionManagerAgentSession:多轮历史如何保存成 JSONL tree,如何用 /tree 回到旧节点继续,以及 compaction 如何缩短发给模型的上下文。

三层职责

05 的主线是分清运行时状态、持久化历史和产品层会话控制。

Agent

  • 内存里的运行控制器。
  • 持有 messages/tools/model
  • 管理 active run、abort、steering/followUp。
  • 把 snapshot 交给 runAgentLoop()

SessionManager

  • 管理 session id/file/dir。
  • 保存 append-only JSONL entries。
  • 维护 id/parentId/leafId
  • buildSessionContext() 构造上下文。

AgentSession

  • 产品层会话控制器。
  • 把 agent events 写入 session。
  • 接 extensions、UI/RPC、compaction。
  • 负责状态同步。

为什么保存 toolResult

session 不是只保存聊天文本,还要保存模型调用工具后的观察结果。

user: 看一下 a.ts
assistant: toolCall read(a.ts)
toolResult: a.ts 内容...
assistant: 根据文件内容回答

缺 toolResult 会怎样

  • 恢复后模型不知道工具返回了什么。
  • assistant 的最终回答缺少证据链。
  • provider payload 可能缺少 tool call/result 配对。
  • 分支、压缩、审计都会不稳定。
核心链条 user 是用户意图,assistant 是模型行为,toolResult 是外部世界返回给模型的信息。

Session 是 append-only tree

分支不是靠删除旧消息实现,而是靠 parentId 和 leafId 选择当前路径。

01fileEntriesJSONL 里的完整追加日志。
02parentId每条 entry 指向上一条 entry。
03leafId当前选中的分支末端。
04append新 entry 的 parentId 等于当前 leaf。
05branch移动 leaf,不删历史。
06getBranch从 leaf 追 parent 到 root。
07context把当前路径变成 LLM messages。
A -> B -> C -> D
     \
      E

为什么不删 C/D

  • 旧分支仍然是事实历史。
  • 后续可以回去继续。
  • 导出和审计需要完整记录。
  • append-only 写入更安全。

/tree /fork /clone /new

这些命令的差异主要是:在当前文件内导航,还是创建新的 session 文件。

/tree

同一个 JSONL 文件里切分支,移动 leaf,并重建当前 context。

/fork

从某个 user message 复制当前路径,创建新 session 文件。

/clone

从当前 leaf 复制当前路径,创建新 session 文件。

/new

创建新的空 session,重新开始。

Compaction 是上下文视图重写

压缩不会删除旧 entries,而是追加一个 compaction entry,让 buildSessionContext 改变发给模型的 messages。

compact()
  -> getBranch()
  -> prepareCompaction()
  -> generate summary
  -> appendCompaction(summary, firstKeptEntryId)
  -> buildSessionContext()
  -> agent.state.messages = context.messages

模型看到什么

  • compaction summary。
  • 最近保留的一段原始消息。
  • 压缩之后的新消息。
  • 不是完整旧历史。

overflow

  • 模型返回上下文溢出错误。
  • 自动 compact。
  • 自动 retry 一次。

threshold

  • 上下文快满时触发。
  • 保留 reserveTokens。
  • 不自动 retry,等用户继续。
不要把 compaction 当成清理历史 fileEntries 仍然保留原始 JSONL 历史;改变的是 buildSessionContext() 生成的模型视图。

手动画 session tree

学 session tree 最快的方法是自己画一次 leaf 怎么移动。

练习

起点:
A -> B -> C -> D
leafId = D

操作:
/tree 选中 B
输入 E
  • 画出新的 tree。
  • 写出新的 leafId。
  • 说明 fileEntries 里是否还保留 C/D。

检查答案

A -> B -> C -> D
     \
      E

leafId = E
fileEntries = A/B/C/D/E 全部保留
  • parentId 决定树边。
  • leafId 决定当前路径。
  • fileEntries 是完整历史,不是当前上下文。
自测 为什么 session 必须保存 toolResult?为什么 /tree 选中 user message 时要回到它的 parent?为什么 compaction 不删除旧 entries?

05 完成度

session 主干已经看完,下一节进入扩展系统。

已经完成

  • 理解三层职责边界。
  • 理解为什么保存 toolResult。
  • 理解 append-only session tree。
  • 理解 /tree 分支导航。
  • 理解 compaction entry 的作用。

下一章继续

  • skills 如何加载。
  • extensions 如何注册工具和命令。
  • prompt templates 如何进入上下文。
  • packages 如何扩展 pi。