Skip to content

Prompt Cache 过期之后,Coding Agent 应该告诉用户什么

最近读了 Claude 的一篇 Blog 《Maximizing the value of your Claude Code sessions》,这篇文章中有相当大的篇幅都是围绕着 token 和 Prompt Cache。如果一个请求的开头与服务器刚刚收到的请求完全相同,那么这两个请求共享的开头状态也相同,因此服务器可以保留上次的状态,只预填充其后的内容。当 token 命中缓存时,效率会更高,同时消耗也会更少。

恰好昨天我在使用 Kimi Code 的时候,当我准备给一个限制了一段时间的对话继续发送消息时,Kimi Code 弹出一个提示消息,大致内容是“当前对话的 Prompt Cache 已经过期 x 分钟,继续发送消息越额外消耗 x tokens”。

嗯,有点意思,我决定去研究一下现在市面上的 Agent Harness,当 Prompt Cache 过期的时候,每个 Harness 都是如何与用户交互的。

先区分四种看起来相似的机制

我对照这些实现时发现,围绕 Prompt Cache 的功能经常被放在一起讨论,但它们介入用户决策的时间完全不同。

层级典型表现用户能否避免本次冷缓存成本
事后观测响应结束后显示 cache miss、命中率或重计费 token不能
到期可见显示 TTL 倒计时,过期后改变状态可以,但要靠用户自己处理
发送前预警提交时提示缓存已过期,以及下一轮可能重新处理的上下文规模可以
决策拦截暂停发送,提供压缩、新会话或原样继续等选项可以,而且恢复路径明确

Aider 的 keep-alive、Pydantic AI Harness 的 CacheBustWarning、VS Code 的 Cache Explorer 都是有用的参照,但不属于同一种产品机制。Keep-alive 试图阻止缓存过期;CacheBustWarning 和 Cache Explorer 用于请求后的诊断。我想重点看的正是后两层:Agent 是否在消息发出前知道缓存已冷却,并把代价和选择交还给用户。

Kimi Code:目前最完整的发送前决策链

Kimi Code 在 PR #2646 中加入了 Cache Expiry Hint。它会在两种场景触发:恢复一个长时间闲置的会话,以及当前进程中的会话闲置较久后再次提交消息。

我最注意到的不是弹窗本身,而是决定是否显示弹窗之前的判断过程。

我先注意到,TTL 没有写死在客户端。Kimi Code 从公开的 client_configs 接口读取 estimated_cache_duration,按模型获得 cache_durationmin_tokens_to_hint。只有闲置时间越过对应模型的缓存期限,而且当前上下文达到提示门槛,才会打扰用户。当前实现只对 OAuth 管理的 provider 生效;缺少模型、用量或服务端配置时会跳过提示,而不是伪造一个确定的缓存状态。

第二个同样关键的细节是,它记录实际的 LLM 往返,而不是所有本地活动。权限确认、Plan 更新和配置变化可能修改会话文件,但并不会刷新服务端缓存。Kimi Code 在恢复会话时只从 message 和 compaction 记录计算 lastActiveAt;运行中的会话也只有完成一次 provider 请求才更新活动时间。对应代码可以在 cache-hint-controller.ts 中核对。

当规则命中后,用户原本提交的消息会被暂存,不会先发送再提示。对话框会显示闲置时间和当前上下文 token 规模,并提供四种处理方式:

  • 压缩上下文后继续;
  • 新建会话后发送;
  • 保留完整历史,原样继续;
  • 永久关闭这类提示。

取消对话框时,输入会回到编辑器;选择压缩或新会话后,暂存的消息才沿正常发送路径重新进入队列。官方配置文档也将这一行为暴露为默认开启的 cache_expiry_hint 设置。Kimi Code 配置文档

把这些部分放在一起看,Kimi Code 形成了一条完整链路:服务端提供 TTL,客户端识别真实模型活动,提交入口暂缓消息,界面解释即将发生的成本,用户选择恢复方式。它不只是“提醒缓存过期”,而是在一次可能昂贵的请求前增加了 preflight。

Claude Code:还未覆盖所有过期场景

看 Claude Code 的演进,我能看到缓存状态正从调试信息进入正式交互。

v2.1.84 加入了 idle-return prompt:用户离开 75 分钟以上再返回时,客户端会建议使用 /clear,减少为陈旧会话重新建立缓存的成本。

v2.1.92 又向前走了一步。Pro 用户回到 Prompt Cache 已经过期的会话时,会在底部看到提示,其中包含下一轮预计以未缓存方式发送的 token 数。这个版本还修复了 /clear to save X tokens 使用累计会话 token、而不是当前上下文大小的问题。

当前 Claude Code 文档进一步确认,客户端掌握每类请求实际使用的 TTL:订阅计划额度内的主会话通常使用一小时,API Key、云 provider、超出计划后使用 usage credits 的主会话通常是五分钟;新版本还提供 promptCacheTtlsubagentPromptCacheTtl,让用户选择五分钟或一小时。对于模型切换、插件重载等会导致下一轮完整重读的操作,Claude Code 也会结合缓存是否仍然有效决定是否确认或阻止。Claude Code Prompt Caching 文档

不过,Claude Code 仍不是覆盖所有请求的发送前 gate。v2.1.84 明确记录的 idle-return 阈值是 75 分钟,v2.1.92 的 footer hint 明确提到 Pro 用户;在使用五分钟 TTL 的大上下文中,仍有用户在 v2.1.114 报告闲置 45 分钟后没有得到发送前警告。Claude Code #51218

所以,我不会把 Claude Code 描述成“没有缓存过期提示”。更准确的说法是:它已经具备 TTL-aware 的成本提示和 /clear 恢复建议,但提示范围与当前请求真正采用的 TTL 还没有完全对齐。

Windsurf:把缓存剩余时间放进上下文指标

Windsurf 采用了一种更常驻的表达方式。2026 年 4 月,Windsurf 将 Prompt Cache Timer 直接集成到上下文窗口指标中,让用户在发送下一条消息之前就能看到缓存剩余时间;每次响应后的卡片还会展示 token 数,解释该消息的成本构成。Windsurf 官方博客

官方文章没有详细说明倒计时归零后的交互。Windsurf v2.3.9 的用户实测显示,悬停用量指标时可以看到剩余 TTL,归零后文案会变成 Prompt cache has expired. Higher cost expected.。这条证据来自用户报告,不等同于官方行为规范,但能确认产品界面已经存在“有效、倒计时、已过期”的状态变化。Windsurf 用户报告

Windsurf 让我觉得有价值的一点,是状态持续可见,而不是等用户按下发送才突然弹窗。局限也很明确:目前没有证据表明它会估算下一轮重新处理多少 token,或直接提供压缩、新会话等操作。它把风险展示出来,但仍由用户自己推导下一步。

Pi:社区扩展从实际请求载荷推导 TTL

Pi 核心的 showCacheMissNotices 属于事后告知:请求完成后,根据用量判断是否发生了显著 miss,并估算重计费 token 和金额。最近出现的社区扩展 @siddr/pi-cache-expiry-warning 则把时机提前到了下一轮之前。

扩展会检查实际发给 provider 的请求载荷,识别 Anthropic、Bedrock 和 OpenAI 的缓存控制字段,再从请求发出时间启动对应的计时器。短缓存默认按五分钟处理;载荷中出现一小时或 24 小时 retention 时,则采用实际序列化的 TTL。倒计时结束后,警告显示在编辑器上方,不会写入模型上下文。

这套机制没有 token 估算和动作菜单,却给了我一个重要的工程参考:不要只读取环境变量,也不要通过 cache read 归零反推过期;应该尽量从最终出站请求确认本轮真正采用的缓存策略。 自定义 provider 没有序列化可识别的 TTL 时,该扩展选择不显示提示,这比给出一个看似精确但可能错误的倒计时更可靠。

第三方壳层正在补主流 Agent 留下的空白

开源项目 Better Agent Terminal 在 Claude Code SDK 外层实现了另一套完整度很高的 Cache Cost Awareness:

  • 可选的浮动倒计时,同时显示五分钟和一小时 TTL;
  • 每 30 秒更新,闲置一分钟后出现;
  • 保存每轮 cache read/write 和按模型计算的成本历史;
  • 当超过 15 万 cached token、且缓存已过期超过一小时后,在发送前弹出确认。

它不是一个独立的模型 Agent,而是 Agent 客户端。正因为如此,我才觉得这个例子很有价值:当底层 Agent 没有提供完整的成本保护时,上层宿主仍然可以根据会话 usage、TTL 和提交事件补上一道防线。对于同时承载多种 Agent 的桌面应用,这类机制甚至更适合放在统一的会话层,而不是分别等待每个 Agent 实现。

一个可靠的 Cache Expiry UX 至少需要什么

对照这些实现后,我不会只用一个 setTimeout 来构建发送前提示。客户端至少要回答五个问题。

1. 这一轮实际采用什么 TTL

同一产品里的主会话、子 Agent、API Key、订阅额度和额外付费流量,可能使用不同 TTL。优先级应当是最终请求载荷、服务端下发配置、明确的 provider 能力,最后才是客户端默认值。

2. 哪个时间点真正刷新了缓存

输入框活动、权限审批和本地状态写入都不算。只有真正到达 provider、并建立或命中缓存的模型请求,才能刷新过期时间。请求失败、被 Hook 拦截或尚未发出时,不应提前重置倒计时。

3. 是 TTL 到期,还是前缀被破坏

切换模型、改变 effort、修改工具定义或重排系统提示,都可能让仍在 TTL 内的缓存失效。时间判断只能识别“可能到期”,usage 能识别“已经 miss”,稳定的客户端还需要记录模型、effort、工具集合和缓存 key 的变化,才能给出不同原因。

4. 用户真正承担多大的风险

“缓存已过期”对 2,000 token 和 200,000 token 会话的意义完全不同。提示至少应展示可能重新处理的上下文 token;如果客户端掌握确定的 provider 价格,还可以进一步给出成本区间。估算无法覆盖 gateway、路由切换或服务端策略时,应明确写“预计”而不是伪装成账单。

5. 提示之后能做什么

只有警告、没有恢复动作,会把技术判断再次推给用户。一个可操作的提示至少应该允许:压缩后继续、创建新会话、原样发送和取消。对不想被打断的用户,可以提供“不再提醒”,但不宜默认静默。

从用户角度看,界面并不需要解释 Prompt Cache 的全部实现,三行信息已经足够:

text
Prompt Cache 已过期 12 分钟
下一次请求预计重新处理约 184K token
[压缩后继续] [新建会话] [原样发送] [取消]

Prompt Cache 应该成为会话状态

Kimi Code 目前提供了最完整的决策拦截;Claude Code 已经开始显示未缓存 token 并给出 /clear 建议;Windsurf 让 TTL 倒计时持续可见;Pi 社区扩展证明了从最终请求载荷判断 TTL 的可行性;Better Agent Terminal 则展示了统一客户端如何为现有 Agent 补上发送前保护。

这些产品没有采用同一种界面,但我在它们身上看到的是同一个方向:Prompt Cache 不再只是 provider usage 里的一个数字,而是会改变下一次操作成本的会话状态。

对于 Agent 客户端,真正有用的问题不再是“上一轮有没有 miss”,而是:在用户按下发送之前,我们是否已经知道这次请求会变贵;如果知道,是否给了他一个更便宜、也更符合当前任务阶段的选择。 这是我评估缓存过期提示时会采用的标准。

参考资料

基于 MIT 发布