第八章 会话、分支与上下文压缩
8.1 会话记住了什么
你今天让 Pi 解释项目,明天继续问实现细节,程序需要知道前面谈过什么。会话就是这段工作的记录,包含消息、工具调用与结果、模型选择、压缩摘要以及其他条目。它保存的是对话相关历史,不是整个电脑在每个时刻的镜像。
默认情况下,Pi 把会话放在 ~/.pi/agent/sessions/ 下,并按工作目录分组。文件采用 JSONL 格式:每一行都是一个独立的 JSON 对象。JSON 是一种表达对象和数组的文本格式;JSONL 让程序能够逐行追加记录,不必每次重新写出一个越来越大的数组。
会话文件的第一行是头部,保存版本、会话标识和工作目录等信息。后面的树状条目有自己的 id 和 parentId。id 标识“我是谁”,parentId 标识“我接在哪个条目之后”。头部不属于这棵条目树,因此不能把“每一行都有父节点”当成通用规则。
初学者可以先把文件想象成一份不断追加的工作记录。SessionManager 负责读取和组织它,AgentSession 负责把完成的消息等内容交给它保存。默认持久化意味着关闭程序后能再次找到历史;加上 –no-session,则使用内存会话,退出后不能再从会话文件继续。
8.2 继续、切换和另起一条路
从同一项目目录启动时,可以用下面两个命令:
pi --continue
pi --resume
–continue 打开当前工作目录最近的会话,–resume 打开选择器。它们的简写分别是 -c 和 -r。在已经运行的交互界面里,/resume 也能选择保存的会话;/new 开始新会话;/name 设置便于辨认的名字;/session 显示当前文件、标识、消息数、用量和费用。
如果已经知道会话路径或标识,可以使用 –session;–session-dir 能指定保存及查找目录。初学阶段不用手工设计这些路径,先通过 /name 给重要工作起名,通常更容易找回。多个项目的会话不是一份公共聊天记录,工作目录仍然影响它们的发现与资源加载。
“分支”指在历史某个位置继续,形成另一条后续路径。例如第一次要求“采用文件存储”,讨论后想尝试“采用数据库”,如果直接在最后补一句,模型会带着完整旧讨论继续;如果从较早位置另开分支,就能让两种方案拥有各自后续。
本版本的交互命令是 /tree、/fork 和 /clone,没有必要凭概念猜一个 /branch。/tree 在当前会话文件内移动;/fork 从较早用户消息创建一个新会话;/clone 把当前活动分支复制进新会话。命令选择取决于你希望相关尝试留在一起,还是形成可以单独命名的工作。
在 /tree 中选用户消息,会把文字放回编辑器,修改后提交即可形成新分支;选择助手回答或其他条目,则从该条目之后继续,编辑器为空。离开分支时,还可以用摘要保存那条路径的重要结论。这种摘要有助于带走经验,并不意味着旧分支所有消息都进入新请求。
这里必须分清两份历史:对话树与磁盘文件。假设旧分支已把 config.json 改成数据库配置,再回到较早消息,文件仍然保持已经写入的内容。树导航没有调用 Git 恢复,也没有撤销数据库或网络操作。需要恢复文件时,应另外检查差异,并使用版本控制或备份处理。
8.3 模型看见的是活动上下文
上下文是模型本次请求能够看见的信息集合,主要来自系统指令、有效工具、项目说明、技能描述和活动分支消息。它有长度上限,通常以 token 计量。磁盘上存了多少条目、界面能翻到多少历史,都不直接等于这一次实际发送了多少内容。
用一棵小树说明:A 后面接 B,B 后面先接 C,后来又从 B 接 D。当当前位置在 D 时,活动路径是 A、B、D;C 所在分支仍可保存,但不会因此自动全部发送。会话管理器从当前条目沿父节点回溯,才能确定该次历史。若已有压缩或上下文编辑,还会应用相应规则。
压缩是把较早的对话整理成摘要,再保留一段近期消息。它解决的是“历史越来越长,模型请求放不下”的问题。用户可以输入 /compact,也可在其后附加要求,例如重点保留接口约定。自动压缩则根据上下文使用情况和配置阈值触发。
程序生成摘要后追加 compaction 条目,其中记录摘要及保留边界;之后的请求根据这个边界重建上下文。原始条目仍在会话树中,压缩不会删除旧讨论。因此它也不是把 JSONL 文件打包成更小文件的压缩算法,文件体积甚至可能因为新增摘要而增长。
摘要会丢失细节。一个长讨论中的错误尝试、完整命令输出和个别措辞,未必都会被保留。重要接口、验收条件和未决问题应写清楚;遇到事实不确定时,重新读取文件。更长上下文和更好摘要能够改善工作,但不能让模型拥有不会遗忘、不会误解的记忆。
压缩摘要通常还需要一次模型请求,因此服务不可用时可能失败,也可能产生费用。失败后应先处理服务或认证问题,再手动重试 /compact。关闭自动压缩不等于禁止手动压缩。分支摘要与上下文压缩目的相近,但触发场景不同,前者主要保留离开分支时的重要工作。
8.4 持久化会话与 durable 的边界
看到仓库还有 packages/durable/README.md,容易以为它就是 coding-agent 的会话存储实现。当前结构并非如此。coding-agent 使用自己的 AgentSession 和 JSONL SessionManager;durable 是另一个实验性智能体运行框架,建立在模型访问层与 Chord 文档状态之上。
durable 所说的“可持久恢复”,重点包括把任务状态、检查点和提交写入存储,进程中断后由调度器继续未完成工作。检查点可以理解为“下次从哪里继续”的可靠记录。coding-agent 的 –continue 则首先解决“重新打开最近对话”的用户需求,二者不能仅凭都能保存文件就画等号。
这一区别会影响你对故障的预期。终端突然关闭后,保存的对话通常可以重新打开,但不能据此承诺任何运行到一半的外部命令都会精确接着执行。即使使用 durable,也需要按其工具和任务契约理解恢复与副作用,不能从“有检查点”推导出所有外部系统都会自动回滚。
本书在这一章介绍 durable,只是为了给当前仓库中的两个方向划清边界。日常继续 Pi 工作,使用已核对的会话命令即可;开发需要跨进程恢复的任务系统时,再研究 durable 的 Harness、存储和调度器接口。它的文档明确标记实验性,接口可能随版本变化。
本章小结
会话负责记录工作,树负责保存不同路径,上下文决定本次模型看见什么,压缩通过摘要减少需要发送的历史。恢复聊天、恢复文件和恢复运行中的任务是三个不同问题,分别需要会话机制、文件版本管理以及相应任务运行框架。
练习与参考答案
- 想把两个设计尝试放在同一会话文件中,应该优先考虑什么命令?
参考答案:/tree。它允许回到已有位置继续,保留其他分支;如果需要独立会话,再考虑 /fork 或 /clone。
- 压缩后原始消息是否从 JSONL 文件中删除?
参考答案:不会。程序追加摘要条目,并改变后续模型请求采用的上下文。存储历史和发送给模型的历史要分别判断。
- /tree 回到改文件之前,文件是否恢复原状?
参考答案:不会。它移动对话位置,不回滚磁盘写入。需要使用 Git、备份或明确的恢复操作。