最近 GitHub 上有个 issue 很有意思:Codex Desktop no longer shows visible context/token usage indicator

用户反馈很直接:Codex Desktop 更新之后,原来能看到的 context usage / token usage indicator 消失了。对于长线程 coding session 来说,这个信息很重要。你需要知道什么时候快到 context window 上限,什么时候应该 compact,什么时候应该开新 thread,什么时候应该少贴一点大段日志。

这看起来像一个很明显的 UI regression。

更有意思的是,OpenAI developer 的回复大概不是“我们漏掉了”,而是:这是有意改掉的。

Codex context window indicator discussion

这里最关键的不是那个 indicator 本身。

关键是这句话背后的产品判断:Codex 希望用户不要再把 context window 当成一个需要持续盯着的资源表。

换句话说,OpenAI 对自动 compaction 已经有了足够强的信心,至少强到他们愿意把这个指标从默认体验里拿掉。你仍然可以通过 /status 看当前状态,也可以手动 /compact,但默认心智变了。

以前用户需要担心 context。

现在 Codex 想让用户假设自己有“无限 context”。

当然,物理上不可能真的无限。模型还是有 context window,工具输出还是会吃 token,历史消息还是需要被处理。真正变化的是:context management 这件事正在从用户心智里下沉到 agent runtime 和服务端系统里。

我们通常怎么做 Context 压缩

如果自己做 agent,最容易想到的 context 压缩大概有几种。

第一种是工具输出截断。

Tool use 是 context 大户,尤其是 search、shell、API 返回的原始数据。最常见的做法是只保留头尾,中间部分截掉;或者把完整输出写到文件里,在 context 里只保留路径。

command output:
  first N lines
  ... truncated ...
  last N lines

full output saved at:
  /tmp/run-logs/build-2026-05-30.txt

第二种是历史消息丢弃。

只保留当前 task 相关的核心 prompt、当前步骤的输入、最近几轮对话。更老的消息直接删掉,或者降级成短摘要。

第三种是让 LLM 给历史任务写 summary。

当 context 达到阈值时,后台触发一个 compaction,把前面大段交互压缩成一段高密度 handoff summary,再把它作为新的 context 输入继续执行。

真实 agent 实现会比这复杂。通常会混合几种方式:工具输出裁剪、历史消息过滤、summary、retrieval、文件化状态、任务 checkpoint。重点不是单一算法,而是 runtime 要能在不让 agent 失忆的前提下,把当前任务继续往前推。

Codex 里能看到的两条路径

Codex 开源代码里能看到一个很直接的分叉。

let _ = if crate::compact::should_use_remote_compact_task(ctx.provider.info()) {
    if ctx.features.enabled(codex_features::Feature::RemoteCompactionV2) {
        crate::compact_remote_v2::run_remote_compact_task(session.clone(), ctx).await
    } else {
        crate::compact_remote::run_remote_compact_task(session.clone(), ctx).await
    }
} else {
    crate::compact::run_compact_task(session.clone(), ctx, input).await
};

先判断 provider 是否支持 remote compact。支持就走 remote。不支持就走 local compact。

这个分叉很关键。因为 local compact 和 remote compact 的语义不一样。

Local Compact:模型自己写交接摘要

Local compact 的机制很朴素。

它本质上就是让当前模型给自己写一份 handoff summary。

核心流程大概是:

  • 把一段 compaction prompt 作为 user input 追加到历史里
  • 用普通 ModelClientSession::stream() 发起模型请求
  • 如果超出 context window,就从最旧的 item 开始删,然后重试
  • 请求完成后,取最后一条 assistant message 作为 summary
  • 在 summary 前面拼上一段 handoff prefix
  • 收集历史里的真实 user messages,最多保留约 20K token
  • 用 summary 加最近 user messages 构造新的 replacement_history
  • 替换旧历史
  • 发一条 warning:长线程和多次 compaction 可能让模型准确性下降

这里的 prompt 也很直白。

You are performing a CONTEXT CHECKPOINT COMPACTION.
Create a handoff summary for another LLM that will resume the task.

它要求 summary 包含当前进展、关键决策、重要约束、用户偏好、剩余步骤、关键数据和引用。

handoff prefix 也很像交接班:

Another language model started to solve this problem and produced a summary
of its thinking process. You also have access to the state of the tools that
were used by that language model. Use this to build on the work that has
already been done and avoid duplicating work.

这套方案不神秘,但有效。

它的问题也很清楚:summary 是明文文本。压缩质量取决于模型有没有抓住重点。它会丢信息,而且多次 compaction 之后误差会累积。

这也是为什么 local compact 后面要提醒用户。

Remote Compact:摘要不再给人看

Remote compact 就不是这个味道了。

Remote V1 的流程大概是:

  • clone 当前 history
  • 如果超过 context window,从尾部删除 Codex 生成的 item
  • 构造完整 prompt,包括 input、tools、instructions、reasoning 控制等
  • 调用 `POST /v1/responses/compact`
  • 服务端返回一组 ResponseItem
  • 客户端后过滤,丢掉 stale developer messages、raw tool output、reasoning、web search 等内容
  • 只保留真实 user messages 和 Compaction { encrypted_content }

这里最值得看的是 encrypted_content

OpenAI 的 Responses API 文档里也能看到,compaction item 里有一段 encrypted content。它不是一段给人读的明文 summary,而是 opaque payload。客户端不会解析它,只会在后续请求里继续携带。

这说明 remote compact 至少不是简单地“服务端帮你写一段 summary 然后原样塞回来”。

从客户端视角看,它更像是服务端生成了一段不可见的 agent state。

Remote Compact V2:从专用接口变成协议事件

Remote Compact V2 更进一步。

它不再调用专用 /responses/compact 接口,而是在普通 Responses stream 的输入末尾追加一个 trigger。

let mut input = prompt_input.clone();
input.push(ResponseItem::CompactionTrigger);

然后仍然走普通 stream。服务端通过 stream 返回一个 Compaction { encrypted_content }

这个变化看起来只是协议层重构,但我觉得意义很大。

compaction 不再像一个外部维护任务,而是变成 Responses 协议里的一个原生 item。agent loop、model request、compaction state 被放进同一条协议链路里。

也就是说,context management 正在变成模型服务的一部分,而不是客户端 harness 临时补出来的一段逻辑。

Remote Compact 到底压缩了什么

我们不知道。

encrypted payload 的意义就在这里:客户端看不到真实内容。

但可以合理推测的是,服务端不会只做一段纯文本摘要。因为如果只是纯文本摘要,就没必要把它做成 opaque encrypted state。

更可能的情况是:服务端保留了某种结构化 compaction state,后续请求可以继续把它交给模型或服务端调度层使用。它可能包含摘要,也可能包含引用、索引、历史片段指针、工具调用状态,甚至和 Responses API 的 conversation state 绑定。

这里不能说得太满。

但方向是清楚的:context 压缩从“客户端裁剪 prompt”变成了“服务端管理 agent state”。

这就是 Codex 敢移除 context usage indicator 的底气来源。

不是 context window 消失了,而是用户不再被要求手动盯着它。

Prompt 泄露反而说明了另一件事

X 上有人用 prompt injection 的方式,试图让 remote compact 暴露自己的 compaction prompt:Kangwook Lee 的文章

泄露出来的 compaction prompt 和 handoff prompt,据说和开源的 prompt.mdsummary_prefix.md 高度接近。

这件事有点反直觉。

如果 remote compact 的 prompt 和 local compact 差不多,那 remote compact 的优势在哪里?

我的理解是,prompt 可能不是关键差异。

关键差异在于它运行在哪里。

Local compact 只能看到客户端准备喂给模型的 history。它只能产出一段明文 summary。后续恢复也只能靠这段文本。

Remote compact 在服务端跑。它可以和 Responses state、模型侧协议、历史 item、加密 state、可能的检索机制放在一起。即使 compaction prompt 看起来差不多,系统能保留和召回的东西也可能完全不同。

同一段 prompt,放在不同 runtime 里,能力不是一个量级。

这对 Agent 开发意味着什么

以前我们做 agent,经常默认 context 压缩是 provider-independent 的。

你用 OpenAI、Anthropic、Gemini、DeepSeek,本质上都可以在 harness 里做一套自己的 context pruning、summary、retrieval。模型厂只提供模型,agent runtime 自己负责把上下文整理好。

Codex remote compact 暗示了一个不同方向:

模型厂自己的 harness 控制力会更强。

因为模型、协议、工具 schema、reasoning item、compaction item、server-side state 都可以一起设计。第三方 harness 当然还能做很多事,但它不一定能拿到模型厂内部那套最贴合模型的 state 管理能力。

这和 The Harness: The Moat for AI Model Providers? 里那个观点很接近:

The model's instincts are baked into the weights during post-training.

模型不是在真空里训练出来的。它是在某个 harness 里被 post-train 的。它习惯什么工具格式,习惯什么编辑协议,习惯什么 memory ritual,习惯什么 compaction 边界,这些东西都会进入模型行为。

这也是为什么“只换模型”越来越没那么简单。

你换的不只是一个 API endpoint。你也在换模型背后那套 runtime 假设。

Harness 开始变成护城河

最近几个信号放在一起看,很明显。

Kimi 在讲 K2.6 agent swarm:Kimi Moonshot 的发布推文。重点不是单个模型回答得更好,而是模型被强化学习到更适合 multi-agent 架构。

DeepSeek 也开始招聘 harness engineers:相关推文

这些都不是孤立现象。

Agent 产品的竞争点正在从“模型会不会回答问题”,转向“模型能不能在某个 runtime 里稳定完成任务”。

这个 runtime 包括:

  • tool schema
  • sandbox
  • file editing protocol
  • permission policy
  • event stream
  • memory
  • compaction
  • retrieval
  • subagent dispatch
  • verification loop

这些东西以前看起来像外部工程。

现在它们越来越像模型能力的一部分。

我怎么看这个变化

Codex 移除 context usage indicator,表面上是一个小 UI 变化。

但我觉得它是一个信号。

OpenAI 想把用户从 context anxiety 里拉出来。用户不应该一直盯着 40%、70%、90% 这种数字。用户应该描述任务、让 agent 执行、在关键节点 review。

从产品体验上,这是对的。

但从 agent 开发者视角,这件事也提醒我们:不要再把 context compaction 当成一个简单的 prompt engineering 问题。

真正的问题是:

  • 哪些状态应该进入模型 context
  • 哪些状态应该进入外部 memory
  • 哪些状态应该由服务端协议保存
  • 哪些状态应该被检索回来
  • 哪些状态应该被压缩成 summary
  • 哪些状态必须保持原始形式,不能丢

如果你只是写一个 chatbot,summary 可能够了。

如果你在做 coding agent、long-running agent、multi-agent runtime,summary 只是最表层的东西。真正有价值的是一整套 state management。

Codex 现在把这套东西往服务端收。

这可能会让用户体验更好,也会让模型厂自己的 agent 产品更难被复制。

所以这个 issue 有趣的地方不是“indicator 被删了”。

而是 OpenAI 在告诉用户:别盯 context window 了。

也在告诉 agent 开发者:context window 这件事,正在变成 harness 和 model provider 的深水区。