乐于分享
好东西不私藏

深度学习 Claude Code 源码:拆解 Anthropic 的迭代思路

深度学习 Claude Code 源码:拆解 Anthropic 的迭代思路

深度拆解 Claude Code 源码:Anthropic 如何构建一个 Agentic AI 编程助手


Claude Code 是 Anthropic 推出的官方 CLI AI 编程助手。它不是一个简单的”终端聊天机器人”,而是一个能直接读写文件、执行命令、搜索代码、甚至派出多个 AI Agent 协同工作的全功能开发工具。

我对 Claude Code 的 TypeScript 源码进行了完整的逆向分析——1,905 个源文件、47 个工具模块、89 个命令、94 个 Feature Flag——试图还原 Anthropic 是如何一步步将一个 LLM 对话框架,迭代成一个成熟的 Agentic AI 工程系统的。

这篇文章不是使用教程,而是一份架构拆解与工程哲学分析


一、技术全景

先用一张表看清全貌:

维度
选型
语言
TypeScript 5.7 (strict mode)
运行时
Bun(启动快、内置 bundler、支持编译时 DCE)
终端 UI
React 19 + Ink(自定义 reconciler 驱动终端渲染)
校验
Zod(Schema-first,运行时校验 + 类型生成)
CLI 框架
Commander.js
AI API
Anthropic SDK v0.50 + Bedrock SDK + Vertex SDK
扩展协议
MCP (Model Context Protocol) SDK v1.11
Feature Flag
编译时 Bun DCE + 运行时 GrowthBook
可观测性
OpenTelemetry (traces / metrics / logs)

两个值得注意的选型决策:

为什么用 React 写终端 UI? Ink 库让 React 组件可以渲染到终端而非浏览器。Claude Code 的 UI 复杂度已经超出了传统 CLI 框架的能力——权限对话框、多 Agent 状态面板、流式 Markdown 渲染——React 的组件模型和状态管理在这里是合理选择。

为什么用 Bun 而不是 Node.js? Bun 的启动速度更快,且内置 bundler 支持 feature() 函数的编译时求值,使得 Dead Code Elimination 可以在打包阶段完成,而非运行时。这对一个有 94 个 Feature Flag 的项目至关重要。


二、架构五层模型

┌─────────────────────────────────────────────────────────┐│  CLI Layer      │ Commander.js → 89 slash commands      │├─────────────────┼───────────────────────────────────────┤│  UI Layer       │ React/Ink 组件树 → 自定义 Reconciler  │├─────────────────┼───────────────────────────────────────┤│  Query Engine   │ LLM 流式交互 → Tool 调度 → 压缩      │├─────────────────┼───────────────────────────────────────┤│  Tool/Skill     │ 47 Tools + Skill System + MCP         │├─────────────────┼───────────────────────────────────────┤│  Service Layer  │ API / OAuth / MCP / Analytics / LSP   │└─────────────────┴───────────────────────────────────────┘

核心模块的代码量可以说明项目的重心分布:

• main.tsx(4,683 行)—— 启动编排,是整个系统的引导器

• QueryEngine.ts(1,295 行)—— LLM 交互循环,是运行时核心

• Tool.ts(792 行)—— 工具类型系统,是扩展性基石

接下来,我按照推导出的 9 个迭代阶段逐一拆解。


三、Phase 1:Tool Loop —— Agentic AI 的基石

传统 LLM vs Tool Use

传统 LLM 只能生成文本。Claude Code 使用 Tool Use 模式:AI 可以发出”调用工具”的结构化指令,系统执行后将结果喂回 AI,形成闭环。

用户: “帮我修复 login.ts 的 bug”  → AI 思考 → 调用 FileReadTool 读取文件  → 读取结果喂回 AI → AI 分析 bug  → 调用 FileEditTool 修改代码  → 修改结果喂回 AI → AI 确认修复完成  → 回复用户

这个循环就是所谓的 Query Loop,也是所有 Agentic AI 系统的基础架构。

AsyncGenerator:流式循环的实现

QueryEngine 的 submitMessage() 使用 TypeScript 的 async * 语法实现流式输出:

async *submitMessage(): AsyncGenerator<SDKMessage> {  for await (const message of query({    messages, systemPrompt, canUseTool, maxTurns  })) {    switch (message.type) {      case ‘assistant’:     // AI 的回复(可能包含 tool_use block)        yield normalizedMessage        break      case ‘user’:          // 工具结果(包装为 user 消息喂回 AI)        this.mutableMessages.push(message)        yield message        break      case ‘progress’:      // 工具执行进度        yield progressMessage        break    }  }  yield { type: ‘result’, usage: this.totalUsage, num_turns }}

为什么用 AsyncGenerator 而非普通 async 函数?因为普通 async 函数要等全部完成才能返回,用户面对的是长时间空白后突然出现的大段结果。AsyncGenerator 每产生一条消息就 yield 出去,UI 立即渲染——这就是 Claude Code 实现”打字机效果”的底层机制。

工具的泛型类型系统

每个工具都是 Tool 三参数泛型:

Tool<Input, Output, P extends ToolProgressData>

• Input:用 Zod Schema 定义,运行时严格校验

• Output:执行结果类型,编译时检查

• Progress:进度数据类型,支持流式进度条

配合 buildTool() 函数的默认值注入(Builder Pattern 变体),工具开发者只需要定义核心逻辑,其他方法使用安全默认值:

const TOOL_DEFAULTS = {  isConcurrencySafe: () => false,   // 默认不可并行(保守)  isReadOnly: () => false,  isDestructive: () => false,  checkPermissions: () => Promise.resolve({ behavior: ‘allow’ }),}function buildTool(def) { return { …TOOL_DEFAULTS, …def } }

工具排序——一个容易忽略的性能细节

工具注册表 assembleToolPool() 在合并内置工具和 MCP 工具时,会按名称排序:

return uniqBy(  […builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),  ‘name’,)

原因:Claude API 支持 Prompt Caching。如果两次请求的 system prompt + tools 定义完全一致,API 复用缓存,节省计算。工具顺序不稳定会导致缓存失效——排序是为了保证幂等性。


四、Phase 2:权限系统 —— 信任是分层的

四级权限模式

模式
行为
default
每次调用都要确认
plan
只读自动通过,写操作需确认
auto
安全操作自动通过,破坏性操作需确认
bypass
全部自动通过(管理员模式)

三路由架构

权限检查不是简单的 if/else,而是根据运行模式路由到不同处理器:

const result = await hasPermissionsToUseTool(tool, input, context)switch (result.behavior) {  case ‘allow’: return grant()  case ‘deny’:  return reject(result.reason)  case ‘ask’:    if (isCoordinatorMode())  return handleCoordinatorPermission()    if (isSwarmWorker())      return handleSwarmWorkerPermission()    return handleInteractivePermission()}

Swarm Worker(多 Agent 场景中的工作者 Agent)的权限处理尤其有趣:

async function handleSwarmWorkerPermission() {  // 1. 先用分类器尝试自动批准  const autoApproval = await bashClassifier(input)  if (autoApproval) return grant()  // 2. 先注册回调,再发送请求(防竞态!)  const promise = registerPermissionCallback(requestId)  // 3. 通过文件 mailbox 发请求给 leader  await sendToMailbox(leaderName, { type: ‘permission_request’, tool, input })  // 4. 等待 leader 审批  const response = await promise  return response.approved ? grant() : reject()}

注意第 2 步在第 3 步之前——先注册监听,再发消息。如果顺序反过来,leader 的回复可能在注册之前到达,导致消息丢失。这是并发编程中的经典竞态条件防护。

25+ Hook 事件

系统定义了 25+ Hook 事件(PreToolUsePostToolUsePermissionRequestSessionStart 等),用户和插件可以拦截几乎所有操作。比如 PreToolUse Hook 可以返回 { behavior: 'deny' } 来阻止某个工具执行——这就是自定义安全策略的入口。


五、Phase 3:五代上下文压缩 —— 最大的工程挑战

上下文管理是整个项目中工程量最大、迭代次数最多的部分。核心矛盾很简单:

压缩太激进 → AI 忘记关键信息 → 做出错误决策压缩太保守 → Token 超限 → API 报错

五代技术就是在这两个极端之间不断逼近最优解。

第一代:Full Compaction

调用一个”压缩 Agent”把整个对话历史总结为 9 段式摘要(目标/概念/文件/错误/推理/用户消息/待办/当前/下一步),然后用摘要替换原始对话。

关键设计:压缩 Agent 复用主对话的 Prompt Cache,不额外消耗完整的 system prompt 传输。

第二代:Auto-Compact

当 Token 使用量超过 85% 时自动触发压缩。加入了 Circuit Breaker(熔断器):连续 3 次失败后停止尝试,避免无限重试导致的资源浪费。

if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {  return  // 停止尝试,等待人工 /compact}

第三代:Cached Microcompact(最精妙的设计)

不修改本地消息,而是生成 cache_edits 指令告诉 API “在缓存层删除某些工具结果”:

return {  type: ‘cache_edits’,  edits: toDelete.map(id => ({ action: ‘delete’, tool_use_id: id }))}

为什么巧妙?传统压缩修改消息列表,不可逆。Cached Microcompact 只在 API 缓存层操作,本地数据完好——如果 API 调用失败需要重试,完整上下文仍然可用。这是关注点分离的典范。

只有 8 种”可压缩”工具的输出会被清理(FileRead、Shell、Grep、Glob、WebSearch、WebFetch、FileEdit、FileWrite),因为它们的输出要么可以重新获取,要么已经生效。

第四代:Time-Based Microcompact

利用 Prompt Cache 的 TTL(约 1 小时)过期时机:既然缓存已经过期、下次请求必须重发完整前缀,那就趁这个机会顺便清理旧数据。

这是一种机会主义优化——零额外成本。

第五代:Context Collapse

与 Auto-Compact 互斥运行。不是简单的”压缩全部”或”删除旧的”,而是基于语义理解哪些上下文当前有用,选择性折叠。


六、Phase 4:模型无感迁移 —— 别名 + 幂等迁移

Claude 的模型一直在更新(Opus 4.0 → 4.1 → 4.5 → 4.6),如何让用户无感升级?

别名系统:用户配置中写 'opus' 而非 'claude-opus-4-6-...',别名在运行时解析为最新版本。

幂等迁移函数:系统有 11 个迁移函数,启动时自动运行。每个都遵循相同模式:

function migrateLegacyOpusToCurrent() {  if (config.hasMigrated) return                    // 幂等守卫  const settings = getUserSettings()                // 只读 user 级(不读 merged)  if (legacyModels.includes(settings.model)) {    updateSettingsForSource(‘user’, { model: ‘opus’ })  }  markCompleted()                                    // 即使无需修改也标记  logEvent(‘migration_completed’)}

一个微妙但关键的细节:迁移只读 userSettings 而不读合并后的 mergedSettings。如果读 merged 写 user,project 级别的设置会被意外提升到 user 级别,影响所有项目。配置系统的层级语义必须在迁移中得到尊重。


七、Phase 5:Plan Mode —— 从”直接做”到”先想再做”

Plan Mode 引入了 EnterPlanModeTool / ExitPlanModeTool / VerifyPlanExecutionTool 三个工具,实现了”规划 → 确认 → 执行 → 验证”的四步工作流。

在 Plan 权限模式下,AI 可以自由搜索和阅读代码(只读操作自动通过),但修改文件和执行命令需要等用户批准计划后才能进行。

后续还引入了 ULTRAPLAN(扩展规划)和 ULTRATHINK(深度思考),以及 Thinking Configuration 的三种模式(Adaptive / Enabled / Disabled)——让 AI 在规划阶段投入更多”思考预算”。


八、Phase 6:多 Agent 协作 —— AsyncLocalStorage 隔离

演进路径

单 Agent → SubAgent → Team 系统 → Coordinator Mode

核心挑战:同进程隔离

多个 Agent 跑在同一个 Node.js 进程内,如何隔离上下文?答案是 AsyncLocalStorage

const teammateContext = new AsyncLocalStorage<TeammateIdentity>()function runWithTeammateContext(identity, fn) {  return teammateContext.run(identity, fn)}

AsyncLocalStorage 类似 Java 的 ThreadLocal,但适用于 Node.js 的异步模型。每个异步调用链拥有独立的”局部变量空间”——Agent A 调用 readFile() 时看到的身份是 “researcher”,Agent B 同时调用 readFile() 看到的是 “coder”,互不干扰。

双路径通信

Agent 之间的通信有两条路径:

1. 内存回调(in-process agent,优先路径)——直接把权限请求放入 leader 的 React UI 确认队列

2. 文件 mailbox(跨进程 agent,退路)——写入 ~/.claude/teams/{team}/mailbox/ 目录,每 500ms 轮询

文件系统是唯一可靠的跨进程通信通道(Agent 可能跑在不同的 tmux pane 中)。

共享任务列表

团队成员通过 ~/.claude/tasks/{team-name}/ 目录共享任务。工作流:完成任务 → 标记完成 → 检查列表 → 认领下一个未阻塞的任务。


九、Phase 7:从封闭到开放 —— MCP / Skills / Plugins

MCP:AI 工具接入的 USB 标准

MCP (Model Context Protocol) 的设计灵感来自 LSP (Language Server Protocol):

LSP:任何编辑器 ←→ 任何语言服务器MCP:任何 AI Agent ←→ 任何工具服务器

在 Claude Code 中,MCP 工具和内置工具对 AI 完全等价——它们出现在同一个工具列表中,AI 自主决定调用哪个。第三方能力成为一等公民。

Skill 系统

Skill 是 Markdown 定义的可复用工作流模板。用户可以在 ~/.claude/skills/ 下创建自定义 Skill(比如 deploy-staging.md),之后用一句话触发整个流程。

压缩时 Skill 有专门的 Token 预算(每个 5K,总计 25K),确保压缩后 AI 仍然记得可用的 Skill。


十、Phase 8:全场景覆盖 —— 不止是终端

Claude Code 从纯 CLI 扩展到了多个运行环境:

模式
Feature Flag
描述
终端 CLI
原始形态
IDE 插件
BRIDGE_MODE
VS Code / JetBrains 双向通信(JWT 认证)
远程容器
CCR_AUTO_CONNECT
代码在远程容器中执行
服务器模式
DIRECT_CONNECT
作为服务端运行
后台常驻
DAEMON
消除冷启动,毫秒级响应
SSH 远程
SSH_REMOTE
远程 SSH 执行
自带计算
BYOC_ENVIRONMENT_RUNNER
用户自带计算环境

Bridge 架构使用 JWT 认证的双向消息协议,IDE 端可以发送权限请求、文件附件,CLI 端返回执行结果。


十一、Phase 9:KAIROS —— 从工具到助手

KAIROS 是目前最新的方向,标志着 Claude Code 从”被动的工具”向”主动的助手”转型:

特性
Feature Flag
描述
持久化会话
KAIROS
跨会话记忆和上下文
主动推送
KAIROS_PUSH_NOTIFICATION
AI 主动通知(如”你的 PR 构建失败了”)
GitHub Webhook
KAIROS_GITHUB_WEBHOOKS
监听代码库变化
Channel 系统
KAIROS_CHANNELS
类 Slack 频道通信
每日摘要
KAIROS_BRIEF
总结当天发生的事
Dream 模式
KAIROS_DREAM
空闲时自主探索项目

这不再是”你问一句我答一句”,而是一个有记忆、会主动思考、能监听外部事件的 AI 同事。


十二、启动优化 —— 毫秒级的工程较量

一个 CLI 工具的启动速度直接影响用户体验。Claude Code 在这方面做了极致优化:

Fast Path(快速退路)

claude --version 不加载 React、Ink、SDK——直接打印编译时内联的版本常量后退出,整个过程 < 10ms。

Parallel Prefetch(并行预取)

在模块 import 之前就启动 I/O 操作:

// main.tsx 顶部(import 之前的副作用)startMdmRawRead()          // 启动 MDM 子进程startKeychainPrefetch()    // 启动 Keychain 读取// 然后才 import React, Ink 等模块(~135ms)// MDM 和 Keychain 在这 135ms 内已经完成

串行需要 240ms,并行只需要 135ms——I/O 被模块加载时间”吸收”了。

TLS 证书时序陷阱

一个容易被忽略的细节:Bun 使用 BoringSSL,它在第一次 TLS 握手时缓存证书库。如果自签名 CA 证书在首次连接之后才设置,BoringSSL 不会重新加载——企业内网连接直接失败。所以 applyExtraCACertsFromConfig() 必须在任何网络请求之前执行。


十三、Feature Flag —— 编译时 DCE 的工程哲学

Claude Code 有 94 个 Feature Flag,使用双层架构:

编译时(Bun DCE)feature() 在外部构建中被 stub 为 return false,Bun 的 bundler 直接消除不可达分支。代码不存在于产物中——零运行时开销、减小包体积、消除攻击面。

// 源码const VoiceUI = feature(‘VOICE_MODE’)  ? require(‘./voice.js’)  : null// 外部构建产物const VoiceUI = null// voice.js 及其所有依赖完全不打包

运行时(GrowthBook):支持 A/B 测试、按比例灰度发布、实时开关。不需要重新构建和发布。

这种双层设计解决了一个根本矛盾:编译时 Flag 效率最高但不灵活,运行时 Flag 灵活但有开销。两者结合,各取所长。


十四、状态管理 —— React 在终端中的实践

Claude Code 使用自定义的 Zustand-like Store:

// Selector-based 精确订阅const model = useAppState(s => s.mainLoopModel)// 只有 model 变化时重渲染,其他 50 个字段变化不影响

内部使用 React 18 的 useSyncExternalStore,开发环境还有安全检查——如果 selector 返回整个 state 而非 slice,直接抛错。

大部分状态用 DeepImmutable 约束(递归 readonly),但 tasks 字段例外——因为它包含函数引用,不能被 readonly 约束。


十五、从源码中提炼的设计模式

最后,总结 12 个值得学习的工程模式:

模式
Claude Code 中的应用
通用场景
AsyncGenerator 流式输出
QueryEngine.submitMessage()
SSE 推送、流式 API
Circuit Breaker 熔断器
Auto-Compact 3 次失败后停止
API 调用、外部服务
Builder Pattern
buildTool() 默认值注入
复杂对象创建
Memoize 单次执行
init() 只跑一次
昂贵的初始化
Parallel Prefetch
启动时并行读 MDM + Keychain
CLI 启动、服务器 warmup
Selector Subscription
useAppState(s => s.field)
React 状态性能优化
AsyncLocalStorage
多 Agent 进程内隔离
多租户、请求级跟踪
幂等迁移
完成标记 + 层级语义
数据库迁移、配置升级
编译时 DCE
feature() + Bun bundler
多环境构建
机会主义优化
Cache TTL 过期时顺带清理
缓存刷新时附带操作
竞态条件防护
先注册回调再发消息
异步通信
配置层级语义
迁移只读 user 不读 merged
多层配置系统

结语

Claude Code 的迭代思路可以用一句话概括:

先让 AI 能动手,再让 AI 安全地动手,再让 AI 聪明地动手,再让一群 AI 一起动手,最后让 AI 成为你的长期搭档。

从 Tool Loop 到 KAIROS,Anthropic 走的每一步都遵循渐进增强的原则——用 Feature Flag 门控实验、用幂等迁移保证兼容、用编译时 DCE 控制产物体积。

没有”大爆炸式重写”,只有持续的、有意识的工程演进。

这或许才是构建一个复杂 AI 系统最值得学习的地方。


*本文基于 Claude Code TypeScript 源码(1,905 个文件)的完整逆向分析。所有代码片段均来自实际源码,经简化后用于说明架构设计。*