Skip to main content

架构全景

从外部视角勾勒 Claude Code 的全貌:在不深入源码细节的前提下,厘清技术栈选型、TAOR 核心循环、五大子系统构成、一次请求的完整旅程与整体代码规模。后续核心系统 / 高级架构 / 方法论板块会以此为坐标系,逐层拆解各系统的设计决策。

技术栈:每个选择都有理由

翻开 Claude Code 源码,技术选型是第一个让人停下来思考的地方——有些在意料之中,有些让人意外。

组件选择为什么
运行时Bun比 Node.js 快,冷启动和子进程创建尤为明显
UI 框架React + Ink终端 UI 的状态管理需求和 Web 前端本质相同
语言严格模式 TypeScript19000 个文件的项目,没有类型系统会崩溃
Schema 验证Zod v4运行时类型检查,防止 AI 返回格式错误的数据
入口文件main.tsx(785KB)单文件打包,减少模块解析开销
重模块加载懒加载OpenTelemetry、gRPC 等按需加载

下面拆开看每个选择背后的工程考量。

为什么选 Bun 不选 Node.js

Claude Code 对运行时有两个压力敏感点:频繁启停子进程(每次 Bash 工具调用都是一个子进程)和冷启动延迟——这是命令行工具,敲回车到响应的延迟直接影响体验。

Bun 在这两项上比 Node.js 强不少,冷启动快 3–5 倍。每天用几百次的工具,每次快 200ms 累积起来感受明显。

为什么终端界面用 React

乍看意外——终端程序用 React?

但 Claude Code 的交互复杂度解释了这一点:实时进度条、可折叠代码 diff、权限确认弹窗、嵌套的工具调用展示、多 Agent 分屏状态……这些和 Web 前端是同一个状态管理问题。用 console.log 拼字符串处理它们,代码会变成不可维护的面条。

React + Ink 让 Anthropic 用组件化思路构建终端:每个 UI 元素是一个组件,状态变自动重渲染。这是复杂度管理的选择,不是炫技。

社区在源码泄露后还发现了一些底层优化——ink/screen.tsink/optimizer.ts 借鉴游戏引擎技术:用 Int32Array 字符池代替字符串操作,用位掩码编码样式元数据;自清除的行宽缓存在 token 流式传输场景下减少了约 50 倍的 stringWidth 计算。Claude Code 每秒可能重绘几十次,这些是流畅体验的必要条件。

为什么 Schema 验证用 Zod

TypeScript 的类型在运行时被擦除,AI 返回的 JSON 在运行时可能是任何形状——模型输出是不确定的,不能假设它每次都返回正确格式。

Zod 在「不确定的 AI 输出」和「确定性的程序逻辑」之间建立了一道防线:提供运行时类型检查和转换。

单文件入口与懒加载

main.tsx 单文件 785KB 看似违反"分模块"直觉,但这是有意的——单文件打包减少模块解析开销,启动时不走标准模块解析流程。OpenTelemetry、gRPC 等重模块则采用懒加载,仅在需要时加载,避免拖慢启动。

核心循环:TAOR

Claude Code 的心脏是一个叫 TAOR 的 Agent 循环:Think → Act → Observe → Repeat。

你在终端输入一句话后发生的所有事情,都是这个循环在驱动:模型先理解你要什么(Think),选一个工具执行操作(Act),观察工具返回的结果(Observe),判断任务是否完成。没完成就回到 Think 继续。一个任务可能要转几十圈才结束。

TAOR 不是 Claude Code 独创的概念,但 Claude Code 的实现里有几个值得记住的设计细节。

工具调用是唯一的「行动」方式

模型不能直接操作文件系统或运行命令,必须通过工具间接执行。所有操作都经过一层中间件,可以在中间层做权限检查、日志记录、安全审查——这是 harness 工程中权限系统存在的前提。

每次循环都是一次完整的 API 调用

每个 Think-Act 周期都遵循同一个节奏:

  1. 把当前上下文发给 API
  2. 等待模型返回工具调用指令
  3. 执行工具,把结果加入上下文
  4. 再发一次 API 调用

这解释了为什么复杂任务耗费大量 token——不是因为模型「想太多」,而是循环结构决定了每一步都要把完整上下文发过去。这也是 harness 工程中上下文管理如此重要的原因。

停止条件是模型自己决定的

循环什么时候停?当模型判断任务完成时,它不再调用工具,直接输出文本回答。

这正是给 Claude 明确的验证标准特别重要的原因——如果需求描述模糊,模型不知道什么时候算「做完了」,循环就会一直转下去。

五大子系统

TAOR 是骨架,让 Claude Code 真正好用的是围绕这个骨架构建的五个子系统:

子系统职责代码量
系统提示(System Prompt)定义 AI 的身份、能力边界和行为准则
工具系统(Tools)⭐59 个工具的注册、调用和权限管理~50,000 行
查询引擎(Query Engine)⭐所有 LLM API 调用、流式传输、缓存和编排~54,000 行
权限系统(Permissions)操作的安全审查和访问控制
记忆系统(Memory)跨会话的持久化偏好和上下文

工具系统查询引擎 是两个最大、最具体的子系统——一个管「能做什么」,一个管「怎么调模型」。其余三个更接近策略层:System Prompt 决定 AI 怎么回答,权限系统决定哪些操作能放行,记忆系统让 AI 跨会话保持连续性。

除了这五个核心子系统,还有几个重要的辅助模块:

  • IDE Bridge:VS Code 和 JetBrains 与 CLI 之间的双向通信层。用 JWT 做认证,支持两个方向的消息传递。IDE 可以向 CLI 发任务,CLI 也可以向 IDE 推状态更新——这是 Claude Code 能同时作为独立 CLI 和 IDE 插件运行的基础。
  • 上下文管理器:负责在对话变长时进行结构化压缩,以及管理 prompt 缓存策略。
  • 多 Agent 协调器:管理多个 Agent 的创建、通信、隔离和结果合并。

一次请求的完整旅程

把前面的概念串起来:当你输入「帮我写一个排序函数」时,完整旅程是什么样?

  1. System Prompt 拼装 系统从缓存加载静态 prompt 段(身份定义、安全准则、工具说明),再拼接动态段(你的 CLAUDE.md、当前目录信息、记忆文件、git 状态)。这可能是一个数千 token 的巨大 prompt

  2. API 调用(Think) 你的消息连同 system prompt 和对话历史一起发给 Claude API。查询引擎处理流式传输,你看到文字逐渐出现。

  3. 工具调用(Act) 模型决定先读一下当前目录的文件结构(调用 Glob 工具),再看看有没有已有的排序相关代码(调用 Grep 工具)。每个工具调用都经过权限系统审查。

  4. 结果注入(Observe) 工具的返回结果被添加到对话上下文中。模型现在知道了目录结构和现有代码。

  5. 循环继续(Repeat) 模型决定用 Write 工具创建文件,然后用 Bash 工具运行测试。每一步都是一次新的 API 调用,带上所有之前的上下文。

  6. 完成 测试通过后,模型判断任务完成,输出总结文本。如果对话触发了记忆提取条件,后台还会 fork 一个子 agent 来提取值得记住的偏好信息。


看起来简单?实际上即使是「写一个排序函数」这样的小任务,也可能涉及 3–5 次 TAOR 循环,每次循环都是一次完整的 API 调用。复杂任务可能有几十次循环,消耗几万 token。

但这就是 TAOR 的魅力:它不是在执行预设的脚本,而是在实时做决策。每一步都根据最新的观察调整策略——试了一个方案发现不行,回退换另一条路。这不是 bug,是设计的一部分。

代码规模总览

最后看几个数字,帮你建立对这个项目规模的直觉:

指标数量说明
源文件总数~1,900全部 TypeScript
代码总行数~510,000不含 node_modules
工具系统~50,000 行最复杂的模块之一
查询引擎~54,000 行最大的单一模块(services/ 目录)
内置工具59每个独立权限控制
斜杠命令~50用户可调用的快捷命令
Feature Flags44控制未发布功能
src/ 子目录41高度模块化的项目结构

这是一个严肃的工程项目——不是三五个工程师周末写的 hobby project,而是一个大团队持续投入、精心设计的产品级软件。

Hacker News 上有人评论说 Anthropic 的代码像是 vibe coded——「感觉对了就行」的写法。这个评价是否公允另说,但它说明一件事:Claude Code 的竞争力不在代码是否优雅,而在系统设计的决策是否正确。做对了几个关键的架构选择,比把每一行代码写得漂亮重要得多。

后面核心系统 / 高级架构 / 方法论三个板块,会逐一拆解这些关键决策。