- Published on
Pi 源码解读(五):会话与上下文,聊久了怎么继续?
- Authors

- Name
- Stone
本篇目录
前面几课讲的是“怎么把一步步操作做下去”。这一课换个问题:任务持续很久,历史越来越多,Pi 怎么接着做,而不是每次都从头开始?
NOTE
Session 像整本工作笔记,Context 像这一轮摊在桌上的材料。
笔记可以保存很多内容,但模型这一轮能处理的材料有上限。两者不能混为一谈。
本课对照 Pi v0.84.1。继续用修改 Vue 页面标题的教学例子;只讲保存、整理、继续这条主线。
一、你只说“继续”,它为什么可能接得上?
假设此前已经做了这些事:你要求只改 src/App.vue 的标题;Pi 读取文件、执行修改;工具返回修改成功,但还没有检查页面。
接下来你说:
继续检查一下,别改其他文件。
这句话单独看,并没有交代“检查什么”。要接上任务,Pi 得把前面需要的信息一并提供给模型。不是“继续”两个字有特殊魔法,而是本次请求带上了相关历史。
Pi 的 SessionManager 管理会话记录,并根据当前会话位置构建用于运行的消息列表。保存到磁盘时使用 JSONL;先把它理解成“一行一条记录的文本文件”就够了,不需要先研究存储格式。1
这像交接工作:接手的人不需要亲历之前的每一分钟,但必须拿到足够准确的记录。
二、既然能保存,为什么不每次全部发给模型?
因为保存得下,不等于本次模型请求装得下。模型有上下文窗口和输出限制;Pi 也会依据上下文用量与预留空间判断是否需要压缩。23
这里提到的 token,可以先理解成模型处理内容时使用的计量单位,不要直接当成汉字数。
想象这个标题任务后来又涉及样式、构建报错和多次文件读取。工作笔记里积累了很多内容,但下一步可能只需要知道:目标是什么、哪些地方不能动、已经改了什么、还没验证什么。
如果机械地保留所有细节,材料可能太长;如果直接扔掉前半段,又可能把“不要动其他文件”这种关键要求丢掉。
所以真正的问题是:怎样减少材料,又尽量保留继续做事需要的信息?
三、Compaction:不是压成 ZIP,而是整理交接摘要
在 Pi 里,这种上下文整理叫 Compaction。默认流程会保留一段近期消息,把较早的内容交给模型生成摘要,再用于后续会话。它不是把文本无损压缩后,让模型自己解压。3
先看一份不够好的摘要:
用户让我们改页面,已经处理了一些内容。
接手的人还是不知道改哪儿、能改什么、目前是什么状态。
一份更有用的教学示例是:
目标:把 src/App.vue 的页面标题从 Hello 改成你好。
约束:不要修改其他文件。
进展:修改工具已返回成功。
待办:尚未检查页面效果,也没有运行测试,不能宣称验证通过。
重点不是“写得短”,而是短了以后仍然能指导下一步。Pi 默认的摘要提示词也会要求保留目标、约束、进展、决定和后续步骤,并强调文件路径、函数名和错误信息的准确性。4
但摘要会遗漏细节,也可能写错。假如把“修改成功”总结成“测试通过”,下一轮就可能基于错误前提继续。这是摘要方案必须考虑的风险,不能把压缩理解成保证不丢信息的能力。
四、压缩之后,历史记录还在吗?
在 Pi 的这条流程里,旧历史仍留在会话记录中;变化的是后续使用哪些内容构建上下文。 Pi 会追加一条压缩记录,保存摘要及近期消息的保留边界,再重建运行时消息。5
可以把主要思路写成下面这段伪代码。这里只描述压缩成功的主流程,不展开取消和错误恢复:
需要压缩时:
找出要保留的近期消息
把较早的内容整理成摘要
保存摘要和保留边界,不抹掉旧历史
用“摘要 + 保留的消息”重建工作记录
下一次调用模型时:
再带上本轮需求、规则和工具说明
为什么近期消息通常要保留原文?沿用我们的例子,刚读到的文件片段、刚返回的修改结果,可能就是下一步直接要用的材料。把它们也概括成一句“看过文件”,会丢掉具体内容。
还有一个容易忽略的细节:保留工具结果时,要照顾它与工具请求的对应关系。 Pi 的切分逻辑不会把工具结果直接选为切分起点,避免把请求丢掉、只留下一个不知道对应谁的回执。4
WARNING
“磁盘上还留着”不等于“这一轮模型看到了”。
以后排查 Agent 为什么忘了某条约束,不能只看聊天记录里有没有,还要看它是否进入了实际使用的上下文,或者被摘要准确保留。
五、重新打开会话,与回滚代码是两回事
假设你关闭 Pi,后来重新打开已保存的会话。恢复过程可以从历史记录重建当前消息;如果其中已经发生过压缩,就要考虑摘要和保留的消息,而不是不加选择地把全部历史塞回去。buildSessionContext() 负责这一类重建工作。1
这里必须分清两种状态:会话记录描述之前发生过什么;磁盘文件是现在实际存在的内容。
如果你在关闭 Pi 后手动改过 App.vue,旧记录不会因此自动变成最新文件内容。因此,恢复聊天后涉及文件的判断,可能仍然需要重新读取和确认。这是两套状态分开后自然得出的工程要求。
扩展理解:为什么切换对话分支,也不等于回滚文件?
Pi 的历史记录带有分支关系。选择某条分支时,会沿对应路径构建消息,而不是把互相分叉的对话全部混在一起。
这改变的是“从哪段对话继续”,并没有自动执行文件回滚。工作目录要恢复到旧状态,需要另外的版本管理或快照措施。第一遍理解到这里就够了。1
六、谁来做这些事?只记住三个职责
SessionManager 管记录和重建;Compaction 逻辑管整理摘要;AgentSession 把它们协调起来,让 Agent 接着使用整理后的消息。 例如源码中,摘要生成后,会追加压缩记录,再重建上下文并更新 Agent 的消息。5
不需要把这些类的全部方法背下来。第一遍只追一个问题:摘要做好以后,是在哪里替换了 Agent 接下来要用的工作记录?
对照源码时,再展开这三个路标
compaction.ts 的 prepareCompaction:准备哪些内容要总结,哪些内容要保留。
同一文件的 compact:生成压缩结果。
session-manager.ts 的 buildSessionContext:按当前路径和压缩记录构建消息。
需要看它们怎样接起来,再去 agent-session.ts 搜索 appendCompaction,看紧接着的上下文重建和消息更新。
面试时,可以这样讲
对照源码看过之后,可以这样表达:
我看了 Pi 的会话和压缩机制。它把完整历史与模型当前使用的上下文分开:历史负责保存过程,模型每次只使用整理后的材料。内容太多时,会总结较早的部分,保留近期消息,再重建上下文继续运行。这里的取舍是用更少内容保留任务进展,但摘要可能丢信息,所以关键约束、修改状态和未完成验证必须表达准确。恢复会话也不等于恢复文件系统,必要时还得重新确认文件状态。
一个不用写代码的小练习
用户最早说过“不要改其他文件”。压缩后的摘要漏掉了这句话,近期消息里也没有它;原始历史还在磁盘上。能否据此保证模型下一轮仍然知道这条要求?
想过之后再看答案
不能。保存在磁盘上,不代表进入了本轮模型输入。需要在摘要、保留消息或其他明确提供给模型的材料里保留这条约束。
同时,要求模型遵守某条文字约束,不等于程序已经建立了权限限制;真正禁止某类修改,仍然要有相应的执行边界。这与第三课的“参数校验不等于权限控制”是一类问题。
这一课记住:历史负责留下过程,上下文负责支持这一轮,压缩负责在有限空间里保留继续工作需要的信息。
