Claude Code 是典型的“氛围编程”产物:乱,但有些部分确实不错

Raed's blog 2026-08-12T12:28:07.613758

上周 Claude Code 源码泄露,一共约 1884 个 TypeScript 文件。整体来看,这份代码库符合你对一支高速迭代的 AI 产品团队的预期:杂乱、膨胀、风格不一。但里面确实有一些想得很清楚的点子,值得其他 agent 项目借鉴。

工具的延迟加载

Claude Code 自带 50 多个工具,每个工具都配了一个 Zod schema(一种 TypeScript 数据结构校验定义)。如果每次请求都把所有这些 schema 发给模型,对话还没正式开始,光是 token 就会烧掉几万。

他们的方案分两层。第一层是 lazySchema():把每个工具的 Zod schema 包在一个工厂函数里,只有在第一次访问时才真正构建。Zod schema 的构建开销不小,如果 50 多个工具都在 import 时构建,每次冷启动都会为那些用户可能根本不会碰到的工具白白买单。

第二层更有意思:大多数工具被标记为“延迟加载”(deferred),模型在上下文里只能看到它们的名字。当它需要某个还没加载的工具时,会调用一个名为 ToolSearch 的元工具,按需取回完整的 schema。这个搜索本身是带权重的:名称部分记 10 分,搜索提示记 4 分,描述记 2 分。你也可以直接按名字查找(select:ToolName),完全跳过评分。

返回结果以 tool_reference 块的形式呈现,这是 Anthropic API 的一个扩展,会在服务端把 schema 展开。整个过程不需要额外的往返通信,完整定义就能注入到上下文中,客户端也不用重新发送所有内容。

这是一个两阶段的延迟加载策略:进程层面延迟(需要时才构建 schema),上下文层面也延迟(模型问了才发 schema)。任何要构建带十几个以上工具的 agent 的人,都应该认真考虑这种做法。

Token 预算里的收益递减检测

大多数 agent 主循环处理 token 预算的方式都一样:数 token,达到上限就停。Claude Code 多了一条启发式判断:它还会观察模型的输出是否在逐渐缩水。

这段逻辑会检查模型是否已经跑过至少 3 次续写(continuation),以及最近两轮各自生成的新 token 数是否都少于 500。两个条件同时满足时,就提前终止,并在 StopDecision 对象上把原因标记为 diminishingReturns(收益递减)。

const isDiminishing =
  tracker.continuationCount >= 3 &&
  deltaSinceLastCheck < DIMINISHING_THRESHOLD &&
  tracker.lastDeltaTokens < DIMINISHING_THRESHOLD;

调用方借此可以区分两种情况:「模型预算用完了」和「模型在原地打转」。这个区分很重要——预算用完意味着任务可能还没做完;原地打转则说明模型其实已经完成,只是不知道该怎么停下来。

这段逻辑不大,但解决了一个跑过续写循环的人都会遇到的真实问题:模型不断重复自己、生成凑数内容,或者对已经写好的东西做无意义的小修改。用程序来检测这种情况,比指望模型自己结束要靠谱得多。

时间感知的上下文压缩

大语言模型(LLM)API 提供方会在服务端缓存对话前缀。如果你的下一个请求和上一个请求共享一段很长的前缀,提供方就可以跳过重复处理。但用户一旦闲置几分钟,这个缓存就会过期。

Claude Code 正是利用这一点来做聪明的上下文管理决策。当上一条助手消息和当前请求之间的时间间隔超过一个可配置的阈值时,它知道缓存反正已经冷了,于是会在发送请求之前,主动把旧工具结果从消息历史里删掉。

const gapMinutes =
  (Date.now() - new Date(lastAssistant.timestamp).getTime()) / 60_000;
if (gapMinutes < config.gapThresholdMinutes) return null; // cache warm, skip

如果缓存还是热的,就什么都不做。那些旧工具结果几乎是免费的,因为提供方已经处理过它们了。如果缓存已经冷了,这些结果就得付全价重新处理,所以干脆剥掉它们,只保留最近 N 条。

还有一种变体使用 Anthropic 的 cache_edits API,精准地删除内容,同时不会使剩余的缓存前缀失效。这是最高效的版本:你既能从过时的工具结果中回收 token,又能保留仍然有效的那部分缓存。

合并式后台记忆提取

记忆提取(从对话中抽取事实并跨会话持久化)在后台派生的子进程中运行。并发处理才是需要小心的地方。

如果提取已经在运行,而新消息又触发了一次提取请求,它们不会排队,也不会取消后重来。它们会把最新的上下文存进一个单独的 pendingContext 槽位,覆盖掉之前在那里的一切。当当前提取结束,它会检查是否有待处理的上下文,然后恰好再执行一次收尾提取。

if (inProgress) {
  pendingContext = { context, appendSystemMessage };
  return;
}

这意味着:如果有五条消息接连快速到达,总共只会执行两次提取。一次用于处理已经在进行中的内容,另一次用于最新状态。中间状态都无关紧要,因为最新的上下文总是包含完整对话。没有级联重跑,也没有徒劳的重复工作。

光标跟踪使用 UUID 而不是数组索引,因此能在消息压缩(系统为了节省 token 而重写消息历史)时存活下来。还有互斥机制:如果主代理在当前轮次中写入了内存文件,后台提取就会跳过并推进光标。另外,drainer() 会在关闭时等待进行中的工作,采用 60 秒的软超时。

这是一个小巧、自包含的并发模式。同样的问题在别处会用完整的任务队列和重试逻辑来解决,结果为了同样的结果做了 10 倍的工作。

所以呢

查看原文