你无法带走的会话
摘要
一篇博客文章认为,AI 推理提供商通过返回不透明、绑定提供商的状态(加密推理令牌、隐藏的子代理消息、不可导出的上下文)而非用户拥有的转录文本,从而损害了会话的可移植性,并提出了针对会话所有权的实用测试。
暂无内容
查看缓存全文
缓存时间: 2026/07/31 04:54
# 你带不走的会话
| EARENDIL 来源:https://earendil.com/posts/session-portability/
推理 API 最初的承诺简单得惊人:发送输入,接收输出。如果你把两者都留住了,你就拥有了这段对话。你可以检查它、归档它、重放它,或者把它交给另一个模型。这个抽象从来就没有完全成立过。提示词缓存(https://earendil.com/posts/prompt-caching/)活在别人的 GPU 上,不同模型的分词方式各不相同,采样也不可复现(而且是有意为之)。但会话以转录文本形式存在的**语义记录**,仍然可以属于用户。一份转录文本应当包含指令、消息、工具调用和工具结果。另一个能力足够的模型或许无法完全一致地继续,但它能够理解发生了什么并接手。推理 API 正在令人沮丧地偏离这一特性,至少在一定程度上是这样。它们越来越多地返回文本与供应商绑定状态的混合物,而这种状态是刻意不可移植的。
- 推理 token 计费在用户头上,却只返回为不透明、加密的数据块,最好的情况下也只是一些无用的摘要
- 网络搜索中,模型看到了客户端从未见过的原始材料
- 压缩后的上下文只有原供应商能够解密
- 子代理指令和消息以加密负载的形式隐藏起来,运行代理的应用程序看不到
- 文件、向量存储、容器和缓存引用在其他任何地方都无法解析
- 响应和对话状态完全由 ID 索引,而这些 ID 完整地存储在供应商的服务器上
每个特性都带着一个供应商随口就能给出的基本理由,以及一套“这对用户有好处”的说辞。所有这些加在一起,改变了一个 AI 会话的所有权现实:你机器上的转录文本不再是你自己的会话,而是会话的一个局部视图,这个会话的运行状态属于推理供应商,而不是你。我们不喜欢这个方向,我们想谈谈这对你(作为用户)意味着什么,也对我们(作为这个领域的工具开发者)意味着什么。
## 会话所有权的实用测试
所谓可移植会话,不是说从一个模型切换到另一个模型必须产生相同的下一个 token。那是不可能的,因为模型有不同的能力、训练出来的人设、上下文窗口,以及使用工具的方式。而且,说到底这一切本来就很不确定。可移植性意味着更朴素的东西:
```js
const transcript = session.export();
revokeCredentials(oldProvider);
session = newProvider.continueFrom(transcript);
```
归档中应当包含足够多的可理解信息,让另一个模型能够继续这项工作。它不应该要求旧供应商去解引用某个 ID、解密某个数据块、记住某次搜索结果,或者重建某个摘要。这就给我们提供了五个有用的测试标准:
1. **检查:**用户能否看到模型看到了什么、工具做了什么、代理之间相互说了什么?
2. **导出:**除了那些可以正常下载的普通工件之外,会话是否自包含?
3. **重放:**另一个实现能否重建语义等价的上下文?
4. **审计:**事后,人类能否解释系统为什么会采取某个动作?
5. **删除:**用户能否识别并移除这个会话所依赖的所有服务器端副本?
一个响应 ID 不是转录文本;用户无法解密的密文不是用户可控状态;一份引用列表不是搜索结果真正放进模型上下文中的证据。
## 加密是为了谁?
围绕这些功能的命名和营销可能具有误导性。`encrypted_content` 听起来像是一个由用户掌控的隐私特性。实际上它通常是一个客户端读不了、只有供应商能打开的密封舱。供应商选择密钥,为它自己的模型解密内容,并规定这些数据可以在哪里重放。更好的说法是**供应商密封状态**。供应商密封确实可以带来真正的隐私收益。例如,OpenAI 可以在 `store: false` 的情况下向客户端返回加密的推理内容,然后在下一个请求时在内存中解密,而不持久化中间状态。这比要求服务器端存储对话更好,尤其对零数据保留客户来说是这样。但请记住,这里本来就没有什么真正需要加密的东西!最重要的是,这种加密并不会向推理供应商隐藏数据;它把你自己的数据藏起来不让你看。
## 存储的对话把转录文本变成指针
OpenAI 的 Responses API 默认存储响应。它的文档说响应对象默认至少保留 30 天;附着在 Conversation 上的条目不受这个 30 天 TTL 的限制。`store: false` 是可用的,应该被使用,因为它让 API 更接近 completions 模式:数据不会存储在 OpenAI 的服务器上。新的 Gemini Interactions API 也做了类似的选择。它默认 `store: true`;付费层级上交互保留 55 天,免费层级保留 1 天。显然,在服务器上存储状态这个想法相当诱人:
```js
const first = responses.create({
model: "frontier-model",
input: "Investigate this production failure",
store: true,
});
const second = responses.create({
model: "frontier-model",
previousResponseId: first.id,
input: "Now implement the fix",
store: true,
});
```
应用发送的数据更少了,供应商可以保留隐藏的推理和工具状态,缓存路由也更容易。但如果本地应用只记录了用户消息和最终文本,`first.id` 就成了一张它并不控制的数据库表的外键。
## 没有推理给你
所有主要实验室都声称,他们有正当理由不暴露原始思维链。结果就是,在非开放权重模型上,我们通常看不到这些 token。原始推理通过 API 是不可见的。使用存储的响应时,之前的推理可以通过 `previous_response_id` 恢复。使用 `store: false` 时,API 返回 `encrypted_content`,客户端必须保存并重放它。即使 `reasoning.context: "all_turns"` 允许后续采样使用上下文,持久化的推理仍然是不透明的。Anthropic 在 `signature` 字段中返回加密的完整思考过程。可读的思考文本(如果启用的话)是由另一个模型生成的摘要,而不是原始思维链。在工具调用回合中,thinking block 必须原样传回。Anthropic 的文档还说,thinking block 与生成它的模型绑定,切换模型时应该去掉。所以这些推理痕迹并没有尝试在 Anthropic 内部实现可移植。所有闭权重模型都重复着同样的故事。这些加密机制允许在一个*生态系统内部*保持连续性,但它们并不能创造出可以带到另一个供应商模型那里的可移植转录文本。一份会话归档可以包含那个数据块,但另一个模型无法使用它的含义:
```json
{"type": "reasoning", "encrypted_content": "gAAAAAB..."}
{"type": "thinking", "thinking": "", "signature": "EqQBCg..."}
{"type": "thought", "summary": [], "signature": "EpoGCp..."}
```
## 隐藏的搜索
服务端网络搜索是转录文本出现空洞、并把空洞藏起来不让用户看到的最清晰例子之一。一个客户端搜索工具的行为就像其他任何工具一样:
```js
const result = search(query);
record({
query,
retrievedAt: now(),
results: result.map((item) => ({
url: item.url,
title: item.title,
passages: item.passages,
})),
});
model.send({ toolResult: result });
```
用户可以检查排序和段落、重新抓取页面、缓存副本,或者把同样的证据交给另一个模型。而使用托管搜索时,供应商执行的是一个私有的工具循环。OpenAI、Google 和 Anthropic 暴露搜索动作、引用,以及可选的来源 URL 列表,但不会暴露用来生成答案的完整文本上下文。URL 不是稳定的重放素材:它的内容可能变化、消失、被个性化,或者在模型看到之前就已经被削减成供应商特有的片段。最终答案可能完全没问题。问题出现在下一回合:
> 把第三个来源和第一个比较一下,重新核对有争议的数字,然后用另一个模型继续这项研究。
新模型收到一个答案和几个 URL。它没有收到结果排序、提取的段落、被过滤掉的材料,或者第一个模型实际使用的证据。即使下一个请求发往别处,旧供应商仍然是这个会话的一部分。托管搜索应该有一个全保真导出模式,包含查询、结果元数据、抓取的段落、时间戳、内容哈希和过滤步骤。简洁的引用可以继续作为用户界面;但它们不应该是唯一的记录。
## 不透明的压缩
长时间的代理会话最终都需要压缩。一个可见的、客户端可控的摘要有损,但至少是可检查、可转移的。用户可以审查它、编辑它,或者让另一个模型再生成一份。OpenAI 的服务端压缩则产生一个加密的压缩条目。文档将其描述为“不透明的,不打算给人解释”。独立的 `/responses/compact` 端点返回一个“规范的下一个上下文窗口”,客户端被要求原样传递。从概念上讲,这个转换看起来像这样:
```js
// Before: expensive but portable
let history = [
userMessage,
assistantMessage,
toolCall,
fullToolResult,
// ... 200,000 more tokens of intelligible history
];
// After: cheap to continue only with the original provider
history = [
{ type: "compaction", encryptedContent: "enc_provider_only_state..." },
...recentItems,
];
```
OpenAI 可以继续使用压缩后的含义,但另一个供应商看到的只是一个不可读的字符串加上一段最近的后缀(好吧,我们根本不会把这种信息传给另一个供应商)。这在技术上并不是必需的。Anthropic 的服务端压缩返回一个`compaction`块,带有可读的`content`字段。它允许客户端提供自定义摘要指令,生成的摘要可以检查并传递给另一个模型。任何供应商也都可以做客户端压缩。OpenAI 的密封工件可能比普通摘要保留更多模型特有的状态,而且在原始模型上可能表现更好。这是一个合理的可选优化。它应该附带一份可读的交接摘要,而不是取代摘要。但同样,这很大程度上还有一个额外的好处:把你更进一步锁定在一个生态系统中。
## 子代理带着隐藏指令而来
多代理系统让问题更加严重,因为现在不再只有一份转录文本了。那里有一棵会话树,以及它们之间流动的消息流。通常,这些消息看起来像是人类写的提示词,只是现在由机器为另一台机器编写。OpenAI 托管的 Responses Multi-agent 测试版返回三种新的条目类型:`multi_agent_call`、`multi_agent_call_output` 和 `agent_message`。`spawn_agent` 的例子包含一个加密的 `message` 参数,而代理之间的消息只包含 `encrypted_content`。启用 Multi-agent 后,每个代理都会隐式启用自动服务端压缩,即使客户端没有要求。推理摘要不受支持。API 还会注入根代理和子代理指令,开发者不能编辑或移除。这是一堆不可转移的状态:密封的委派、密封的代理消息、各自自动压缩的上下文、隐藏的推理,以及供应商托管的编排。
一个相关的改动在 2026 年 6 月落地到了开源 Codex 客户端。这个提交标题为“Encrypt multi-agent v2 message payloads”(https://github.com/openai/codex/commit/5f4d06ef186b896d316620556e561d59206c3ebf),直接解释了整个流程:
```js
// Parent model's tool call, as persisted by Codex
{
"name": "spawn_agent",
"arguments": {
"task_name": "worker",
"message": ""
}
}
// Child model's input
{
"type": "agent_message",
"author": "/root",
"recipient": "/root/worker",
"content": [{ "type": "encrypted_content", "encrypted_content": "" }]
}
```
Responses API 加密父模型发出的工具参数,Codex 转发它,API 在内部为子模型解密。Codex 自己的 `InterAgentCommunication.content` 是空的。具体任务不在可读的 rollout 和历史记录中。可以想象,这并不只是一个抽象的“换模型”顾虑。你可以设想,如果子代理改错了文件、泄露了秘密、重复了另一个代理的工作,或者遵循了一个错误的假设,用户就回答不了那个最简单的问题:*那个代理到底被要求做什么?* 一个 open Codex issue(https://github.com/openai/codex/issues/28058)要求加密传输保留一份单独的可读审计副本。这是最低限度可接受的设计。更好的做法是,明文代理间消息应该继续作为常态。
## “大多数人在会话中途不会切换模型”
可能确实不会。大多数人也不会每周换操作系统或手机运营商。但即使你不利用这种自由,它仍然很重要,因为它改变了你与供应商之间、以及供应商与你的关系。作为用户,你也可能因为以下原因需要转移会话:模型退役、服务宕机、价格变动、政策阻止下一次请求(你好 fable)、机密阶段必须在本地运行,或者审计员需要重建发生过的事情。代理也在让会话变得长得多。一个编程或研究会话可以积累数天的决策和证据,而一个个人助理可能会积累数年的会话转录文本(大概我们还没有那么长的历史)。离开的选择权也会带来纪律。如果供应商知道用户可以随时去别处继续,它就必须在模型质量、价格、可靠性和信任上竞争。如果用户积累的上下文只能被一家供应商解读,那就会形成非常糟糕的激励机制。
## 可移植的推理 API 应当承诺什么
我们希望推理供应商和代理构建者采纳一小套规则。
1. **本地事件日志是权威的。**服务器存储可以是它的镜像或加速器,但客户端可以不通过解引用服务器 ID 就重建整个会话。
2. **存储是显式的。**`store: false` 应该简单易用、有文档可查,并且最好作为默认值。需要保留数据的功能应该在使用时明确说明。
3. **没有任何不透明项是意义的唯一载体。**加密的推理、压缩和工具签名可以为了同供应商质量而保留,但每一个都应该有可读的、供应商中立的交接表示。
4. **托管工具要有全保真日志。**记录确切的输入、输出、证据、过滤、来源、时间戳和内容哈希,而不只是一个精致的答案和引用。
5. **子代理通信必须可审计。**持久化每个代理的精确可读任务、消息、结果、谱系、模型和工具权限。
6. **压缩必须可检查。**返回一份可读的摘要、用于生成它的指令,以及足够的谱系信息来理解什么被丢弃了。
7. **工件必须可导出。**文件、容器输出、搜索快照和生成的媒体都要能下载到内容寻址的本地归档中。
## 蒸馏其实很棒
在模型层面还存在一种相关的锁定形式。美国一些最大的闭权重实验室越来越敌视外部蒸馏。Anthropic 在 2026 年 2 月的文章(https://www.anthropic.com/news/detecting-and-preventing-distillation-attacks)中谈到 DeepSeek、Moonshot 和 MiniMax 所谓的“攻击”,并称之为“蒸馏攻击”。它的商业条款说客户拥有自己的输出,但禁止使用该服务来训练竞争模型。
相似文章
AI代理与上下文可移植性
关于AI代理中上下文可移植性的讨论,将其比作更换手机时联系人依然保留的场景,但当前的AI平台却锁定用户上下文。文章质疑供应商对可移植性的承诺是否只是包装更好的锁定策略。
@LakshOnline: Agent 的用户体验在会话边界处仍然会中断。真正的解锁在于让一次有用的运行变成一个带有记忆的可复用工件……
一条推特帖子强调了AI Agent的局限性:有用的运行会随会话结束而消失,并提出了将AI工作流转化为可复用的、带记忆的工件,这些工件可以作为桌面应用部署且不消耗token。
尝试让智能体记忆跨会话持久化所学的经验
本文反思了AI智能体记忆的复杂性,远超简单的存储问题,强调了诸如判断真实性、优先级变化、区分决策与噪音以及何时恰当地呈现上下文等挑战。
窃取AI推理痕迹 (2分钟阅读)
研究揭露了LLM API加密推理痕迹中的漏洞,使攻击者能够提取专有模型推理、个人数据,并实现恶意提示注入。
从专有LLM API窃取推理痕迹
一项研究论文揭示了专有LLM API中的一个架构漏洞:加密的推理痕迹可被拦截并注入到较弱的模型中,以提取思维链、私有数据,并实现对Anthropic、OpenAI和Google的隐形提示注入。该攻击还能从公共代码库中恢复PII和凭据。