面试官问:"Claude Code 源码泄露,你看出了哪些关键设计?"
“3 年 AI 应用开发经验,深度使用 Claude Code、Cursor 等 AI 编程工具,主导过多个 Agent 项目落地”
看到这份简历,我问了个直球:上周 Claude Code 源码泄露,51 万行 TypeScript 全曝光了,你看了吗?看出了什么?
候选人说看了,但只是”大概扫了一下目录结构”。
这就有点可惜了。这次泄露堪称 AI Agent 领域的”维基解密”——Anthropic 因为一个 npm 发包失误,把 source map 文件连同 v2.1.88 版本一起推了上去。59MB 的 .map 文件可以直接还原出完整 TypeScript 源码,1906 个文件,覆盖了 Agent 循环、工具系统、上下文压缩、权限沙箱、缓存策略、反蒸馏防护等所有核心模块。这是业界第一份完整的生产级 AI Agent 参考实现。
今天这场面试,就围绕这份源码里最值得关注的设计来聊。
面试官:”Claude Code 的 Agent 主循环是怎么实现的?传统的 while(true) 加状态判断?”
候选人:”应该就是一个循环吧,每轮调 API、解析工具调用、执行工具、把结果拼回去,然后继续下一轮。ReAct 那一套。”
(面试官内心 OS):方向对,但完全没抓到精髓。
正解:
Claude Code 的主循环不是 while 循环,而是一个 Generator 函数——用 yield 逐步产出消息流,调用方按需消费。这个设计在 Agent 框架里非常少见。
核心代码在 query.ts,共 1729 行,函数签名长这样:
export function* query(params: QueryParams): Generator<Message | StreamEvent, void>
为什么用 Generator 而不是普通循环?三个工程原因。
流式反压控制。 Generator 天然支持 pull 模式——消费端不拉,生产端就不跑。对 IDE 插件和 WebSocket 会话来说,UI 渲染跟不上时 Agent 循环自动降速,不会堆积内存。传统的 while 循环 + 回调做不到这种粒度的流控。
暂停/恢复能力。 Generator 的执行上下文在 yield 时冻结,下次 .next() 时恢复。Claude Code 利用这个特性支持会话级别的暂停与继续,不需要额外的序列化/反序列化逻辑。
多层事件产出。 一次 Agent 循环里,yield 出去的不只是最终回复,还有 stream_request_start、tool_progress、compact_event 等中间事件。消费方可以选择性处理,CLI 渲染进度条,SDK 只要最终结果。
每一轮循环内部的执行管线:
消息预处理 → 三层压缩管道 → 构建系统提示 → API 调用(带重试)→ 流式工具执行 → 状态更新还有两个关键的恢复机制:模型输出被截断(max_output_tokens)时自动降速重试,上限 3 次;累计 token 超过 500K 触发自动继续,注入 sleep 后开启新 turn。
常见的坑:很多人自己写 Agent 循环时把所有状态放在闭包里,Generator 一 GC 状态就丢了。Claude Code 的做法是把关键状态(turnCount、compactTracking、tokenBudget)显式挂在外部 state 对象上,Generator 只引用不持有。
要点速记
– 主循环是 Generator 函数,1729 行,支持流式反压和暂停/恢复
– 每轮经过 6 个阶段:预处理 → 压缩 → 构建提示 → API 调用 → 工具执行 → 状态更新
– max_output_tokens 截断自动重试(上限 3 次),500K token 触发 auto-continue
– 状态不放闭包,显式挂外部对象,避免 GC 丢失
面试官:”Claude Code 有 45 个以上的内置工具,模型一次可能调多个。这些工具调用是串行跑还是并行跑?”
候选人:”并行吧,不然多个文件读取串行跑太慢了。可能加了个并发池?”
正解:
不是简单的并行或串行,Claude Code 实现了一套读写分离的并发分区策略——读工具并发执行,写工具独占执行,而且是基于每次调用的输入参数动态判定的。
核心逻辑在 toolOrchestration.ts 的 runTools 函数:
for (const { isConcurrencySafe, blocks } of partitionToolCalls(toolUseBlocks)) {
if (isConcurrencySafe) {
yield* runToolsConcurrently(blocks, ...) // 最多 10 个并发
} else {
yield* runToolsSerially(blocks, ...) // 独占,带上下文更新
}
}
每个工具定义上有一个 isConcurrencySafe(input) 方法,注意参数是 input——同一个工具,不同输入可能返回不同结果。比如 Bash 工具,ls 是只读的可以并发,rm 是写操作必须独占。这个判定逻辑不是 LLM 做的,是硬编码的规则。
最大并发数默认 10,可通过环境变量 CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY 调整。
工具定义本身的结构值得看:
interface Tool {
name: string
description: string
inputSchema: ZodSchema // Zod 运行时校验
execute: (input, context) => Promise<ToolResult>
isConcurrencySafe: (input) => boolean
isEnabled?: (context) => boolean // 动态启用/禁用
maxResultSizeChars?: number // 结果截断阈值
}
用 Zod 做运行时校验是个亮点。模型返回的 JSON 参数不一定合法,Zod schema 在执行前拦截非法输入,比 JSON Schema 的 ajv 验证更适合 TypeScript 生态,而且 schema 定义可以直接复用为 API 的工具描述。
还有一个叫 ToolSearch 的延迟加载机制。Claude Code 不会一次性把 45 个工具的 schema 全塞进系统提示,那要吃掉大量 token。它先注册工具名列表,模型需要某个工具时调 ToolSearch 拿到完整 schema,按需加载。这在 MCP 场景下尤其重要,外部 MCP server 可能注册了几十个工具,全量注入不现实。
权限检查嵌在工具执行链里,每次调用前都过 canUseTool() 检查,走四种模式:default(弹窗确认)、auto(自动分类器判断)、plan(计划模式限制子集)、bypass(管理员)。auto 模式下有两个分类器做共识:bashClassifier 检测危险命令模式(rm -rf、dd 等 23 项),yoloClassifier 做机器学习风险评分,两个都通过才放行。
另一个细节:同一个工具被用户拒绝 3 次后自动加入当前会话黑名单,不再询问。
要点速记
– 读写分离并发:读工具最多 10 并发,写工具独占执行
–isConcurrencySafe(input)基于输入动态判定,不是静态标记
– 工具 schema 用 Zod 做运行时校验 + API 描述生成,一份定义两处复用
– ToolSearch 延迟加载工具 schema,避免全量注入浪费 token
– 权限双分类器共识 + 拒绝 3 次自动黑名单
面试官:”Claude Code 能跑几个小时的长会话,上下文窗口肯定会爆。它怎么处理上下文压缩?”
候选人:”应该是到了一定长度就做个摘要吧,把前面的对话总结一下,类似 ChatGPT 那种 memory。”
(面试官内心 OS):只知道摘要这一招。实际上 Claude Code 做了三层递进压缩,每层的成本和效果完全不同。
正解:
Claude Code 的上下文管理不是单一策略,而是三层递进管道:Snip → Microcompact → AutoCompact。从零 API 调用的本地裁剪,到利用缓存编辑的增量压缩,再到调模型做完整摘要,成本逐级递增,只有前一层不够用时才触发下一层。
第一层:Snip Compaction(零成本裁剪)。 不调 API,纯本地操作。策略是移除对话中间的”用户提问→助手回答→工具结果”三元组,只保留开头和最近几轮的边界消息。适用场景:消息条数多但 token 总量还没到上限,先把冗余轮次砍掉。
第二层:Microcompact(缓存感知压缩)。 这层利用了 Anthropic 的 cache_edits API。连续的工具结果块被聚合成更短的摘要块,然后通过 cache_edits 的”删除旧块”操作直接在服务端缓存中替换。效果是压缩 30-40% 的历史 token,但因为用的是缓存编辑而非重新发送完整上下文,实际成本极低。
这个设计背后是一笔经济账:Anthropic 的 Prompt Cache 定价是读 0.1 倍、写 1 倍。Microcompact 把”发送完整历史”变成”编辑缓存中的旧块”,token 少了,而且走缓存读价格,成本降一个数量级。
第三层:AutoCompact(模型摘要)。 只在接近 token 上限时触发。流程是 fork 一个独立 Agent,用专门的压缩提示对整段对话生成摘要。摘要完成后有一个 post-compact cleanup 阶段:恢复最近 5 个文件(50K token 预算)和最近 5 个技能(每个截断到 5K)。
为什么 fork 独立 Agent?因为压缩本身消耗输入 token(整段对话都得发过去),在主循环里做等于系统提示膨胀一倍。fork 出去的 Agent 有自己的上下文,压缩完只返回摘要文本,主循环零开销。
还有一个实验性的第四层 Context Collapse,把多条消息合并成一条摘要,只读视图不修改实际历史。
三层管道的触发顺序严格递进:
Snip(免费)→ Microcompact(便宜,缓存编辑)→ AutoCompact(贵,fork Agent 调模型)要点速记
– 三层递进:Snip(零 API 调用)→ Microcompact(cache_edits,成本 1/10)→ AutoCompact(fork Agent 做摘要)
– Microcompact 利用缓存读写价差(0.1x vs 1x),压缩 30-40% 历史 token
– AutoCompact 的 post-compact 恢复最近 5 个文件(50K 预算)+ 5 个技能(5K/个)
– Context Collapse 为实验性第四层,只读折叠不改历史
面试官:”源码里有两个很有意思的机制:Anti-distillation 和 Undercover Mode。你怎么看?”
候选人:”反蒸馏…是防别人抄模型吗?Undercover 我没注意到。”
正解:
这两个机制揭示了 Anthropic 在商业竞争和开源社区策略上的深度布局,也是这次泄露中引发最大争议的部分。
反蒸馏有两层防护。
第一层是假工具注入。claude.ts 中有一个 ANTI_DISTILLATION_CC 标志,启用后在 API 请求中附带 anti_distillation: ['fake_tools'] 参数。服务端收到后静默注入虚假的工具定义到系统提示中。目标场景:如果有人用中间人代理截获 Claude Code 的 API 流量来训练竞品模型,这些假工具会污染训练数据——模型会学到根本不存在的工具调用模式。这个特性由 tengu_anti_distill_fake_tool_injection 功能开关控制。
第二层是连接器文本摘要。服务端对助手在工具调用间产出的推理文本做缓冲和摘要,返回加密签名的摘要而非完整推理链。录制流量的人只能拿到压缩后的碎片,还原不出完整的推理过程。
不过这套防护有已知弱点:设置 CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS 环境变量可以禁用整个反蒸馏系统。
Undercover Mode 的争议更大。
undercover.ts 大约 90 行代码。当 Anthropic 员工在非内部仓库使用 Claude Code 时自动启用,指示模型:不许提及 Capybara、Tengu 等内部术语;不许提及 Claude Code;用人类开发者的风格写 commit message,去掉所有 AI 署名。
代码注释写得很直白:”strip all AI attribution, write commits as a human developer would.” 没有强制关闭选项。
Anthropic 一直在用 Claude Code 匿名向公开的开源项目贡献代码,并且专门做了一套系统来隐藏 AI 身份。这在开源社区引发了激烈争论。讽刺的是,Undercover Mode 就是为了防止内部信息泄露而设计的,结果整套系统连同源码一起泄露了。
源码中还有三个未公开的模型代号:Capybara 对应 Claude 4.6 标准版(还有 capybara-fast 和 capybara-fast[1m] 两个变体),Fennec 对应 Opus 4.6,Numbat 仍在测试。内部注释显示 Capybara 已迭代到 v8,但虚假声明率从 v4 的 16.7% 回升到 v8 的 29-30%。
另外代码中发现了 KAIROS 自主模式:包含 /dream 技能(夜间记忆蒸馏)、GitHub Webhook 订阅、后台守护进程、5 分钟定时刷新,指向一个完全自主运行的 Agent 形态。以及一个叫 Buddy 的 Tamagotchi 风格终端宠物系统,18 个物种(包括 capybara 和 axolotl),1% 概率传说级,五项属性:DEBUGGING、PATIENCE、CHAOS、WISDOM、SNARK。这个大概率是愚人节彩蛋——泄露日期恰好是 3 月 31 日。
要点速记
– 反蒸馏两层:假工具注入污染训练数据 + 推理文本摘要防截取
– Undercover Mode 在非内部仓库自动启用,去除所有 AI 署名,无强制关闭
– 三个内部模型代号:Capybara(4.6 标准)、Fennec(Opus 4.6)、Numbat(测试中)
– Capybara v8 虚假声明率 29-30%,较 v4 的 16.7% 有退化
– KAIROS 自主模式含夜间记忆蒸馏、后台守护进程和 Webhook 订阅
面试官:”Claude Code 跑一个长会话,每轮都要发系统提示和工具定义,缓存命中率直接决定成本。它怎么管理 Prompt Cache?”
候选人:”用 Anthropic 的 Prompt Caching 功能吧,在系统提示上打 cache_control 标记,尽量保持前缀不变。”
正解:
Claude Code 对 Prompt Cache 的管理精细到了”逐字段哈希比对 + 缓存失效自动诊断”的程度,远超大多数人对 Prompt Caching 的认知。
核心文件是 promptCacheBreakDetection.ts,追踪 14 个缓存破坏向量:
type PreviousState = {
systemHash: number // 系统提示(去掉 cache_control)的哈希
cacheControlHash: number // 带 cache_control 的哈希(捕捉 TTL 变化)
toolsHash: number // 所有工具 schema 的聚合哈希
perToolHashes: Record<string, number> // 逐工具哈希
systemCharCount: number
model: string
fastMode: boolean
globalCacheStrategy: string // 'tool_based' | 'system_prompt'
betas: string[]
// ... 更多字段
}
每次 API 调用后系统对比前后状态的 diff。如果检测到超过 2K token 的缓存损失,触发 tengu_cache_break_suspected 事件,同时把详细 diff 写入 /tmp/cache-break-*.diff 供排查。
TTL 动态管理也很讲究。 两档:标准用户 5 分钟,Max/Enterprise 用户 1 小时。判定条件不只是订阅类型,还检查 overage 状态和缓存编辑是否禁用。TTL 在会话期间使用 Latch(粘着)机制——一旦确定就不再变化,防止 TTL 切换本身破坏缓存前缀。
同样的粘着机制用在 AFK Mode 上。检测到用户长时间没操作时进入 AFK 模式,但模式切换不能破坏缓存前缀,所以用 Latch 锁定:首次设置后会话期间不变。
缓存策略分两个作用域:tool_based(MCP 工具变化时重算)和 system_prompt(系统提示变化时重算)。cache_control 标记不是无脑加在最后一个 block 上,而是智能计算位置,确保缓存前缀最大化。
这里有个被源码揭示的成本逻辑:Anthropic 的缓存读价格是输入 token 的 0.1 倍,写是 1 倍。Claude Code 每次调用可能发送上万 token 的系统提示和工具定义。缓存命中时这些 token 成本降 90%。一次意外的缓存失效会导致成本瞬间飙升 10 倍。这就是 Claude Code 投入大量精力做缓存失效检测的原因,它直接关系到 Anthropic 的运营成本。
还有一个细节:代码里有一个 原生客户端认证 机制。API 请求包含 cch=00000 占位符,Bun 的原生 HTTP 栈(用 Zig 编写)在请求离开进程前用计算哈希覆盖这五个零。这是为了验证请求来自真正的 Claude Code 二进制文件,第三方客户端无法通过此认证。技术上可以通过重建 JS 包规避,但门槛不低。
要点速记
– 追踪 14 个缓存破坏向量,每次 API 调用后做状态 diff
– 超过 2K token 缓存损失触发告警 + 写入 diff 文件
– 两档 TTL:标准 5 分钟 / Max 用户 1 小时,Latch 机制防切换破坏缓存
– 缓存命中率直接关系成本(命中 0.1x,未命中 1x),一次失效成本飙 10 倍
– 原生客户端认证:Bun/Zig 层注入请求哈希,第三方客户端无法伪造
这场面试考的不是”知不知道有泄露”,而是”拿到一份完整的生产级 Agent 源码,能从中提取多少工程智慧”。
候选人的典型问题是停留在表面的功能描述,缺乏对设计决策背后工程权衡的理解。”用了 Generator”不重要,重要的是为什么用 Generator 而不是 async/await;”做了上下文压缩”不重要,重要的是为什么分三层、每层的成本模型是什么。
建议:
- 去 GitHub 搜 claude-code-source,重点读
query.ts(Agent 循环)、toolOrchestration.ts(工具并发)、promptCacheBreakDetection.ts(缓存管理),比读十篇分析文章更直观 - Microcompact 利用 cache_edits 做增量压缩的思路可以直接搬到自己的 Agent 项目里,能显著降低长会话的 API 成本
- 反蒸馏和 Undercover Mode 反映的是 AI 公司在商业竞争中的攻防策略,值得跳出技术视角去思考
这次泄露最有价值的不是某个具体功能的实现,而是它展示了一个经过生产验证的 AI Agent 在工程层面需要解决多少”看不见的问题”:缓存经济学、并发安全、上下文退化、反竞品窃取。写 Agent Demo 很容易,让它在生产环境稳定跑起来,需要的工程量比大多数人想象的多一个数量级。
夜雨聆风