微信扫码
添加专属顾问
我要投稿
Claude Code意外开源揭示Agent设计的精妙之处:延迟曝光机制如何优化多轮交互成本。核心内容: 1. Claude Code源码意外泄露暴露的"延迟曝光"设计模式 2. 前缀缓存(prefix cache)对Agent性能的关键影响 3. 主流编码智能体采用相似架构的深层原因
事情的起点,其实有点戏剧性。
原本只是一个再普通不过的工作日,但开发者圈突然被一件事刷屏——
Claude Code 的源码,被“意外公开”了。
不是官方开源,也不是技术发布,而是一个很典型的工程事故:
npm 包里误带了 source map 文件。
这种文件本来只是用来调试的,但一旦放出来,就等于把压缩后的代码“反编译”回了原始实现。于是短短时间内,几十万行 TypeScript 代码被完整还原,整个工程细节几乎被摊在桌面上。
一时间TorchV的办公室里,同事们奔走相告,coding-agent和数字员工们被指派进行了深度的源码挖掘。大家纷纷对自己Focusing的方面开始了探索
说回来,这件事为什么会炸? 因为大家第一次有机会,看到公认的全球Top级“顶级 Agent”到底是怎么做的。 很快,讨论就集中在几个非常熟悉的话题上:
这些都很正常,也确实很有意思。
但如果我们把这些代码多翻一会儿,会慢慢注意到一个不那么起眼、甚至一开始很容易忽略的点:
👉 工具并不是一开始就全部给模型的。
而且不仅如此,它们:
这件事初看很像是某种 prompt 技巧,甚至有点“工程细节味太重”。
但再往深一点想,就会意识到:
这很可能不是技巧,而是一个被精心设计过的结构。
更有意思的是,当我们把视角从 Claude Code 稍微拉开一点,会发现:
这并不是它一家在这么做。
同样是顶级的编码智能体Codex,在开源代码中也能找到相同的做法,被称之为: delayed exposure
这时候,一个问题就变得值得认真对待了: **为什么这些系统都在做同一件事?
原因其实不在“能力”,而在一个很多人一开始不会注意到的地方:
多轮Agent的成本和速度,很大程度由 Context 的结构决定的。
更具体一点说,是由——
👉 前缀(prefix)是否稳定决定的。
而 Claude Code 这次“意外开源”,恰好让我给大家带来一段颇有意思的分享:
延迟曝光机制,和前缀缓存(prefix cache)有什么关系?
很多人真正开始关注 prefix cache,其实是从 2024 年的一次“价格变化”开始的。 当时 DeepSeek 在 API 里上线了一个能力:上下文硬盘缓存。
官方给出的描述很直接:
更关键的是价格:
👉 命中缓存的部分,成本可以降到原来的 0.1 倍
这事之所以重要,不仅因为“便宜了”,而是因为它首次将cache的复用做到了工程化中,让这个相对隐性的知识被知晓:
模型推理,并不是每次都必须从头计算。
要理解这件事,需要稍微回到一点点模型原理。
以 Transformer 为例(现在几乎所有大模型都是这个架构),推理过程大致是这样的:
换句话说:
模型在处理第 N 个 token 时,前面 N-1 个 token 的计算结果是已经存在的。
于是就有一个很自然的优化:
如果下一次请求的前缀完全一样,那这些中间结果(KV cache)其实可以直接复用。
也就是说:
DeepSeek 做的事情,本质上就是把这套机制“工程化”了:
而且它还有一个非常严格的条件:
只有从第 0 个 token 开始完全一致的前缀,才会命中缓存。
中间哪怕有一点不同:
👉 都不会命中
在普通单轮问答里,这个机制已经很有价值了:
都可以明显降成本。 但在 Agent 里,这件事的影响是被放大的。 因为 Agent 的调用方式是这样的:
如果一切都按理想情况发展,每一轮只是往后 append,前面的内容完全不变 那么:
👉 prefix cache 命中率会非常高
👉 推理成本接近“增量计算”
但现实情况下,模型提供商在收到消息后会这样格式化输入:
┌───────────────────────────────┐
│ System / Instructions │ ← 稳定前缀(基本不变)
├───────────────────────────────┤
│ Tool List / Schema │ ← 稳定前缀(最容易被破坏)
├───────────────────────────────┤
│ Environment / Context Info │ ← 通常稳定(但可能变化)
├───────────────────────────────┤
│ │
│ ┌───────────────────────┐ │
│ │ User Message (turn 1) │ │
│ ├───────────────────────┤ │
│ │ Assistant (reasoning) │ │
│ ├───────────────────────┤ │
│ │ Tool Call + Output │ │
│ ├───────────────────────┤ │
│ │ User Message (turn 2) │ │
│ ├───────────────────────┤ │
│ │ Assistant ... │ │
│ └───────────────────────┘ │
│ │
│ (持续 append) │ ← 可增长后缀
└───────────────────────────────┘
很多系统,会在不知不觉中破坏这一点。 最典型的,就是前面提到的:
👉 前缀被改写了。
而改写前缀的“重灾区”,往往就是工具列表,这会带来一个非常实际的结果:
同一个 Agent,随着对话轮次增加,成本和延迟不断“重算”。
也正是因为这个原因,prefix cache 在 Agent 里,从一个“可选项”,变成了一个必做项:
换句话说: DeepSeek 把这件事“显性化”了——用价格告诉你缓存有多重要;
而 Agent 系统,则必须把这件事“结构化”——从一开始就围绕缓存去设计上下文。
接下来,我们就可以回到最开始那个问题:
如果工具本身是动态的,那该怎么让它“不破坏前缀”?
答案就是——延迟曝光。
如果只停留在表层,Claude Code 做的事情其实并不新鲜:
但源码一旦被还原出来,有一件事会变得非常明显——它的重点根本不在“工具调用”,而在另一件更底层的事情:
工具是如何进入上下文的。
Claude Code 从一开始就把工具可见性,当成一个和缓存边界绑定的问题来处理。
它的基本思路可以用一句话概括:
工具不是一次性给模型,而是分阶段、分位置、分承载方式进入上下文。
首先,它在系统层面就把工具分成两类:
一类是立即可见的,模型从第一轮就可以直接调用;另一类则是延迟的,这部分工具不会在初始 prompt 里展开完整 schema。
这一步本身不复杂,但关键在于后面的处理方式。
对于这些延迟工具,Claude Code 并不是“什么都不给”,而是先给模型一个“线索层”:
这样,模型在第一轮看到的上下文,是一个轻量且稳定的前缀,而不是一整套庞大的工具 schema。
接下来,当模型真正需要某类能力时,它才会通过 Tool Search 去拿完整定义。
这一点和很多系统类似,但 Claude Code 的关键区别在于:
👉 “拿到定义”这件事之后,这些信息会以什么形式进入上下文。
如果只是简单实现,很容易走到一种直觉方案: 将搜索到的工具重新加入工具列表
这种方式简单直接,但有一个致命问题:
前缀变了,cache 直接失效。
Claude Code 的处理方式明显更“克制”。
它没有依赖单一表达,而是根据不同路径,把“延迟工具的存在”放在不同的承载层里。
有时候,它确实会用类似上面的提示块,让模型知道有哪些工具;但在另一条路径下,这些信息会变成一种 system-reminder 风格的消息,不再是直接拼在 prompt 前部的文本。
再往后,你会看到更进一步的做法:它会把这些变化整理成一种增量结构,比如:
普通提示块示例:
<available-deferred-tools>
Read
Edit
Grep
</available-deferred-tools>
系统提醒类信息示例:
Deferred tools appear by name in <system-reminder> messages.
更稳定的增量附加信息示例:
deferred_tools_delta:
- tool: Read
- tool: Edit
- tool: Grep
这种形式的本质,不再是“每轮重写一段文本”,而更像是:
👉 把工具池的变化,当作一次可追加的状态更新。
进一步看源码里的开关逻辑,这件事就更清晰了。
是否使用 <available-deferred-tools> 这种前置块,并不是固定的,而是取决于类似 isDeferredToolsDeltaEnabled() 这样的路径判断:
再往深一层看,还有一个更容易被忽略的点:
它在 cache 判断上,是有明确取舍的。
延迟工具的变化,本质上是“系统能力的变化”,但 Claude Code 的策略是:
👉 **这种变化不应该直接影响 prefix cache **
换句话说,它在做两件事:
把这些拼在一起,就可以得到一个非常清晰的结构:
它不是在解决“模型如何选工具”,而是在解决“工具系统如何不破坏前缀”。
如果只看 Claude Code,或许以为这是 Anthropic 的一套“特殊技巧”。
但当你再去看 OpenAI Codex CLI 的实现,会发现一个很有意思的现象:
它走的是相对不同的路径,但目标几乎一样。
Codex 的切入点不是“工具怎么放”,而是:
把“工具发现”这件事,变成一次正式的会话事件。
它的做法可以简单理解成三步:
第一步,初始阶段不暴露全部工具
模型一开始只看到:
tool_search 的入口其他工具,并不在顶层完整出现。
第二步,需要时让模型自己去搜
当模型判断需要某类能力时,它不会直接调用工具,而是:
👉 先调用 tool_search
这个动作的意义在于:
第三步,也是最关键的一步:
👉 搜索结果会写入会话历史
也就是说,这不是一段临时提示,而是一个正式存在的历史项:
从这一刻开始,这些工具就“存在于对话里”了。
这一步的工程价值非常大,但很容易被忽略。
因为它带来一个直接结果:
工具定义不需要反复出现在前缀里了。
后续轮次发生的事情是:
也就是说,Codex 做了一件和 Claude Code 本质类似的事情:
👉 把变化往“后面”移动,而不是改前面
如果你把两者放在一起看,会发现一个非常清晰的共识:
路径不同,但答案是一致的:
不要让“工具全集”成为一个每轮都会变化的 prompt 前缀。
而这,也正是“延迟曝光机制”真正要解决的问题。
如果只看 Claude Code 或 Codex,很容易把延迟曝光理解成一种“高级优化”。
但一旦你把视角从“某个实现”切换到“Agent 系统整体演进”,就会发现:
这其实不是优化,而是规模上来之后的必然结果。
先看一个趋势变化。
早期的 Agent,工具通常是这样的:
在这种情况下,把所有工具一次性塞进 prompt,完全没问题,甚至更简单
但现在的 Agent,工具形态已经完全变了:
尤其是 MCP 这一类能力,本质上是:
工具集合是“运行时生成”的,而不是静态配置的。
这时候,如果你还用“全量工具直接暴露”的方式,会发生什么?
第一层问题,是成本问题:
但更致命的是第二层问题:
👉 prefix cache 开始频繁失效
这时候,延迟曝光就不再是“优化”,而是一个结构性解法:
这样做的直接结果是:
换句话说:
当工具系统开始“动态化 + 大规模化”,延迟曝光几乎是唯一不会把系统拖垮的方案。
好,这一章我帮你做一次压缩 + 强化 TorchV 部分 + 收尾更有力,去掉重复表达,让节奏更干净。
如果把前面的分析收敛成一句话,其实就是:
延迟曝光真正保护的,不是“工具少一点”,而是“前缀稳定不被改写”。
可以把一次模型请求简单拆成两部分:
prefix cache 的规则非常简单,也非常严格:
👉 只有前缀完全一致,缓存才会命中。
延迟曝光本质上做了一件事:
把原本会进入前缀的变化,转移到后缀或更稳定的附加层。
这其实就是把“只追加、不修改”的设计,从对话内容,扩展到了工具系统本身——也就是一种更完整的 Append-Only Prompt 模式。
这个问题在真实系统里并不抽象。
在 TorchV 的 Agent 实践中,我们遇到过一个很典型的场景:有状态的沙箱工具。
如果直接把这些工具“刷新进前缀”,很容易出现:
我们的调整方式其实和前面两家的思路是收敛的:
这件事在 RAG 和企业知识库场景里尤为关键。
因为:
如果前缀不稳定:
👉 每一步几乎都在“重新算一遍”
而一旦前缀稳定:
👉 系统会明显进入“增量执行”的状态(速度和成本都会下来)
也正因此,在我们新的 Agent 产品线 Workstation 里,这一点被明确提炼成一个设计原则:
能力可以动态变化,但前缀必须尽量静态。
回头看 Claude Code 和 Codex,其实路径不同,但本质完全一致:
当 Agent 还很简单时,这件事不明显;
但当工具变多、链路变长、上下文变重时,
👉 谁能控制“变化发生在哪里”,
👉 谁的系统才能真正跑得稳、跑得久、跑得起。
往期文章:
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业
2026-04-01
Anthropic "开源"了一份 Agent Infra 创业的工具书
2026-04-01
说说Claude Code源码泄露
2026-04-01
Claude 代码已下架,爆料人身份曝光,他已经连夜重写了一版火速上架
2026-03-31
Claude Code 源码泄漏,全部细节与始末
2026-03-31
突发!Claude Code 源码泄露,扒出这些隐藏功能
2026-03-31
阿里云发布 Agentic OS:首个面向 Agent 的操作系统
2026-03-31
Stripe拿出一份机器支付协议,Agent可以给自己买买买了
2026-03-31
Claude Code 砍掉了RAG:不要给智能体一个海洋馆,给它一个太平洋
2026-01-24
2026-01-10
2026-01-26
2026-01-09
2026-01-09
2026-01-23
2026-01-14
2026-01-07
2026-03-13
2026-01-21
2026-03-31
2026-03-31
2026-03-22
2026-03-22
2026-03-21
2026-03-20
2026-03-19
2026-03-19