小墨同学札记 · 译文串读
Pi 为什么把会话与上下文留在你手里
读完《无法随身带走的会话》、《Pi 中的压缩机制》和《Agent 中的提示缓存》,我越来越觉得,它们讨论的不是三个孤立的功能,而是同一个问题:一次 Agent 工作,究竟有多少部分是我们自己能带走、检查和修改的?
想象你让 Agent 修一个持续两天的故障。它读了日志,改过代码,排除了几种猜测,最后还没修完。此时你想换模型继续。如果你手里只有一个厂商的会话 ID,或者一段你无法打开的“压缩后状态”,新模型拿到的究竟是什么?它能知道已排除的错误原因吗?你能看见遗漏了哪一步吗?
这是我读这几篇译文时找到的主线:
保留记录,是为了能追溯;控制上下文,是为了能决定模型现在看什么;控制转换层,是为了不让某一家模型的接口决定你如何工作。
三层连在一起,才接近一个属于自己的 Agent Harness。
先分清三样东西
- 会话记录回答“任务发生过什么”。本地 JSONL 会话、会话树和 Extension 条目,留下分支、工具调用与摘要。
- 本轮上下文回答“下一次让模型看到什么”。当前分支、压缩摘要、近期消息和 Extension 的
context处理,共同决定本轮材料。 - 厂商请求回答“这些材料怎样交给模型”。Pi 的适配层转换输入;必要时,Extension 可以检查或调整请求载荷。
会话是长期记录;本轮上下文从当前会话路径重建,还会加入系统提示和工具定义;厂商请求再把这些材料转换为具体接口需要的形式。“我的会话里有”不等于“模型此刻看到了”;“模型看到了”也不等于“换一家厂商还能原样接上”。 Pi 的会话文件格式说明列出了会话条目如何构成模型消息;Extension 文档则提供了在请求前调整消息和请求载荷的入口。
这里的“可自定义”也有边界:Pi 的会话文件有自己的 JSONL 结构,并不是用户随意发明一种磁盘格式,Pi 就会自动识别。真正值得关注的是,记录留在本地且可读,Extension 能增加条目或消息,也能改变从记录派生出的本轮上下文。
图解:会话留下“发生过什么”,本轮 Context 决定“模型现在看什么”。这张图展示的是基本关系;下面两张继续解释前缀变化与跨模型交接。打开原图
为什么先要握住会话
会话可移植性那篇译文让我意识到,保存聊天文字还不够。编程 Agent 的“经历”还包括系统指令、工具调用、工具结果、模型切换、分支和压缩。如果关键步骤只藏在厂商内部,离开它的服务以后,即使导出了一份漂亮的聊天记录,也未必能重建当时的工作状态。
Pi 默认把会话保存在本地 JSONL 文件中,按工作目录组织,并以树结构保留不同路线。你可以继续、回到分叉点,或另开会话。压缩也会作为条目写入记录:旧内容可能不再进入下一轮模型上下文,但原来的会话条目仍可回看。这给了我们两个不同的能力:继续当前路线,以及事后追问某个判断从哪里来。Pi 会话文档和文件格式说明描述了这些机制。
本地会话仍不是万能备份。模型提供商没有返回的隐藏推理、托管搜索内部过程,Pi 不可能凭空记录;会话树也不会替你恢复磁盘文件。要让另一个模型接手,至少还应保存目标、关键决策、已改文件、验证结果和未解决的问题。代码本身仍需靠文件与 Git 核对。
为什么平时尽量不动前缀,必要时才压缩
长会话有一个容易忽略的成本:下一轮通常会再次发送前面的大部分内容。提示缓存能复用完全相同的输入前缀。所以,一边工作一边不断从中间删除旧工具结果、重排工具定义、修改系统提示,虽然看起来把提示变短了,却可能让后面本可复用的内容重新计算。
这解释了为什么 Pi 偏好稳定、以追加为主的记录。前缀稳定,缓存才有机会命中;但“有机会”不是保证,提供商仍可能过期、淘汰缓存,或把请求路由到别处。会话 ID 也不能把两个不同的前缀变成同一个前缀。提示缓存译文把这笔成本讲得很透。
压缩是另一件事。当上下文接近模型窗口,或者你主动输入 /compact,Pi 会把较早的一段内容整理为可读摘要,并保留近期消息,再用“摘要 + 近期内容”继续。这一步有意改写了输入前缀,原来的提示缓存通常要重新建立。换句话说,Pi 不是“保留原前缀来压缩”:它是平时保持前缀稳定,确有必要时接受一次缓存重置,换取更短、还能继续工作的上下文。Pi Compaction 文档说明了摘要、保留近期消息及可自定义的压缩流程。
图解:第二轮只是继续追加,因此相同前缀有机会命中缓存;压缩会改写较早部分,近期消息虽然保留,旧的完整前缀也不再相同。手机上可左右滑动,或打开原图放大查看。
这也是一个取舍,而不是神奇的“无损记忆”。摘要会省掉细节。如果某个报错原文、用户约束或验收结果不能丢,就要让它在摘要中明确出现,或写进可重新读取的项目文件。压缩后让 Agent 复述目标和下一步,再与文件核对,比只看“压缩完成”四个字可靠。
为什么还要能改“模型看到的内容”
如果会话记录等于每次都原样发给模型,那我们只能在“永久删除记录”和“永远发送全部记录”之间选。可真正需要的是第三种选择:记录保持可追溯,本轮输入按任务需要组织。
例如,一次工具运行产生了上千行日志。你可能希望会话保留完整结果,方便追责;下一次调用只给模型错误摘要、关键行号和原始文件路径。或者换了一个上下文窗口更小的模型,就需要更紧凑的交接内容。Pi 的 Extension context 事件可以在每次模型调用前,对消息副本做非破坏性调整;自定义压缩钩子可以提供自己的摘要;自定义消息可以进入上下文。Extension 文档和压缩文档分别给出了这些入口。
我会把这种能力理解为“上下文策略可以自己定”:决定哪些事实必须原样保留,哪些可以摘要,哪些只在特定任务中提供。它并不意味着 Pi 自动为所有模型找到最优格式,更不意味着一套摘要能在任何厂商那里维持同一个压缩比。模型的分词方式、上下文窗口、工具协议和缓存计费都可能不同;换模型后的 KV 缓存也不能直接搬过去。可移植的是你能读懂、能修改的工作记录与交接规则,不是某个提供商内部的计算状态。
图解:换模型时,延续的是可读记录和你制定的交接规则。A、B 的请求需要分别适配,厂商内部缓存不会跟着迁移。打开原图
真正值得问的不是“能压到多短”
我现在会用一个很朴素的交接来检验这些能力:假设明天必须换模型,甚至换一套 Agent,我能否只靠手里的记录回答以下问题?
- 用户最初要完成什么,哪些要求不能改变?
- 哪些事情已经做完,证据在什么文件或工具结果里?
- 哪些判断只是猜测,哪些方案已经被排除?
- 压缩时删去了什么细节,有没有必要回查原会话?
- 下一步交给另一个模型时,它实际会收到什么?
如果答不上来,问题不一定出在模型“不够聪明”。也可能是会话不完整、摘要丢了关键约束,或我们根本没有检查过发送前的上下文。
《什么是 Agent Harness?》说的是系统提示、工具、循环与模型转换层如何一起托住 Agent。在我看来,保留会话、前缀稳定、压缩和上下文可调整,正是把这副“安全带”留在自己手里的具体做法。它们不保证每一步都正确,却让错误有机会被发现,规则有地方可改,换路线时不必从一片黑箱重新开始。
想亲手验证,可以从会话的保存与续写、上下文与压缩和CASE 02 · 压缩前后对照开始;再按上文顺序读三篇译文。本文是我的串读与判断,不代表 Earendil 原作者把这些文章合写成了同一个论点。