01 - Claude Code Architecture


一、概念解释

什么是 Claude Code?

Claude Code 是 Anthropic 官方推出的 CLI Agent(命令行智能体)。它不是一个简单的命令行工具,而是一个在终端中运行的、具备自主编程能力的 AI 编程助手。

与普通 CLI 工具的区别:

维度普通 CLI 工具(如 git, npm)CLI Agent(Claude Code)
交互模式命令 -> 结果,单轮对话式,多轮持续交互
能力边界执行预定义命令自主决策、调用工具、编写代码
输入方式固定参数和选项自然语言 + 上下文理解
输出方式文本/JSON代码修改、文件操作、终端命令执行
状态管理无状态或简单状态完整的对话历史和上下文管理

核心能力

Claude Code 能够:

  • 读写文件 – 浏览项目结构,阅读源码,编辑和创建文件
  • 执行命令 – 在沙箱中运行 shell 命令(构建、测试、部署等)
  • 搜索代码 – 全局搜索文件名(Glob)和文件内容(Grep)
  • 管理任务 – 创建、追踪和管理子任务,支持多 Agent 并行
  • 访问网页 – 获取网页内容、搜索互联网
  • 操作 Jupyter Notebook – 读取和编辑 notebook 单元格
  • 连接 MCP 服务 – 通过 Model Context Protocol 扩展能力
  • 多 Agent 协作 – 通过 Task 工具启动子 Agent 并行处理

解决什么问题?

Claude Code 的本质是将大型语言模型(LLM)的能力与本地开发环境深度集成,解决以下核心问题:

  1. AI 与开发环境的隔阂 – 传统 AI 编程助手只能通过 Web 界面交互,无法直接操作本地文件和终端。Claude Code 让 AI 直接在开发者的终端环境中工作。

  2. 工具调用的编排 – LLM 本身只能生成文本。Claude Code 构建了一套完整的工具系统,让 LLM 能够"动手操作" – 读写文件、执行命令、搜索代码。

  3. 上下文管理 – 大型项目的代码量远超 LLM 上下文窗口。Claude Code 实现了上下文构建、压缩、历史管理等机制,让 LLM 始终能看到最相关的信息。

  4. 多步骤任务的自主执行 – 从理解需求到编写代码、运行测试、修复 bug,Claude Code 能自主完成多步骤的开发任务,而不需要人类逐步指挥。


二、核心流程图

分层架构图

基础设施层

Anthropic SDK

React/Ink

Zod 校验

WebSocket

服务层

API 服务

OAuth

MCP 服务

Analytics

压缩服务

工具层

Tool 接口

工具注册 (tools.ts)

~50 个工具: Bash, FileEdit, Grep, Glob, Web, MCP, Task...

核心引擎层

QueryEngine

Agent Loop (query.ts)

上下文构建 (context)

应用控制层

主循环 (main.tsx)

REPL 循环

命令路由 (commander)

用户交互层

CLI 入口 (cli.tsx)

命令系统 (commands/)

UI 组件 (ink)

各层职责说明:

层次核心模块职责
用户交互层cli.tsx, commands/, components/接收用户输入,渲染终端 UI
应用控制层main.tsx初始化应用,管理 REPL 主循环,路由命令
核心引擎层QueryEngine, query.ts, context.ts管理对话生命周期,执行 Agent Loop,构建上下文
工具层Tool.ts, tools.ts, tools/*定义工具接口,注册和执行具体工具
服务层services/*API 通信、认证、分析、MCP 协议等
基础设施层第三方依赖提供 React 渲染、API 调用、数据校验等基础能力

核心模块关系图

--version

--dump-system-prompt

默认路径

bootstrap-entry.ts

ensureBootstrapMacro()

cli.tsx
入口分发

直接退出

直接退出

main.tsx
主控制器

注册命令

Ink 渲染

REPL 循环

Commander
命令路由 (90+ 命令)

React UI

QueryEngine
对话生命周期

query.ts
Agent Loop

构建上下文
context.ts

调用 API

解析工具调用

执行工具
tools.ts

追踪消耗
cost-tracker

Bash, FileEdit, Grep, Glob, Web...

数据流概览

解析参数

创建

纯文本回复

工具调用请求

用户输入

CLI 入口

main.tsx

QueryEngine

构建上下文
context.ts

加载工具
tools.ts

调用 Anthropic API

解析 LLM 响应

显示给用户

执行对应工具

将结果加入上下文

继续 Agent Loop

等待用户输入


三、架构解决的核心问题

Claude Code 的架构围绕以下核心问题展开设计:

  1. AI 与开发环境的隔阂 – 传统 AI 编程助手只能通过 Web 界面交互,无法直接操作本地文件和终端。Claude Code 的分层架构(CLI 入口 -> 应用控制 -> 核心引擎 -> 工具层)让 AI 直接在开发者的终端环境中工作。

  2. 工具调用的编排 – LLM 本身只能生成文本。通过统一的 Tool 接口和注册中心,Claude Code 构建了一套完整的工具系统,让 LLM 能够"动手操作" – 读写文件、执行命令、搜索代码。工具系统采用策略模式,新增工具只需实现接口并注册,不影响核心循环。

  3. 上下文管理 – 大型项目的代码量远超 LLM 上下文窗口。Claude Code 实现了多级上下文构建(系统提示 + 项目规则 + 对话历史)和自动压缩机制,让 LLM 始终能看到最相关的信息。

  4. 多步骤任务的自主执行 – Agent Loop 以 while(true) 循环驱动"思考-行动-观察"的 ReAct 模式,从理解需求到编写代码、运行测试、修复 bug,全程自主完成。

  5. 启动性能优化 – 通过 Fast-path 分发模式,简单请求(如 --version)毫秒级响应,复杂请求按需加载重量级模块。


四、核心代码详解

4.1 启动流程

Claude Code 的启动分为三个阶段:引导注入参数分发主循环初始化

阶段 1: 引导注入
  bootstrap-entry.ts  -->  bootstrapMacro.ts
  (注入构建元数据到全局对象)

阶段 2: 参数分发
  cli.tsx
  (解析命令行参数,决定走哪条路径)

阶段 3: 主循环初始化
  main.tsx
  (注册命令、初始化 Ink、进入 REPL)
阶段 1:引导注入

src/bootstrap-entry.ts 是整个应用的绝对入口,仅有约 5 行代码:

// bootstrap-entry.ts -- 整个 Claude Code 的起点
// 核心思想:在加载任何业务代码之前,先注入构建时元数据

import { ensureBootstrapMacro } from "./bootstrapMacro.js";

// 第一步:确保全局 MACRO 对象存在
// 在原始构建流程中,这些值由打包工具在编译时注入
// 在恢复版中,我们从 package.json 读取默认值
ensureBootstrapMacro();

// 第二步:动态导入 CLI 主入口
// 使用动态 import 是为了确保 MACRO 注入先于 cli.tsx 的执行
import("./entrypoints/cli.js");

src/bootstrapMacro.ts 注入的全局对象结构:

// bootstrapMacro.ts -- 向 globalThis.MACRO 注入构建元数据

declare global {
  var MACRO: {
    version: string;          // 例如 "999.0.0-restored"
    buildTimestamp: string;   // 构建时间戳
  };
}

export function ensureBootstrapMacro() {
  // 如果全局已存在(真正构建的版本),则跳过
  if (globalThis.MACRO) return;

  // 恢复版:从 package.json 填充默认值
  globalThis.MACRO = {
    version: "999.0.0-restored",
    buildTimestamp: new Date().toISOString(),
  };
}

教学要点:为什么需要引导注入?

在真正的构建流程中,版本号、构建时间等是在 CI/CD 打包时确定的。但源码中的模块可能会在启动时就读取这些值(例如 MACRO.version)。因此必须在所有模块加载之前完成注入。这就是为什么 bootstrap-entry.ts 要先用同步代码注入,再动态 import 主入口。

阶段 2:参数分发(Fast-path 模式)

src/entrypoints/cli.tsx 实现了高效的参数分发策略。它并不是把所有请求都交给主应用处理,而是对常见场景实现了"快速路径":

// cli.tsx -- 参数分发逻辑(伪代码,展示核心思路)

async function main() {
  const args = process.argv.slice(2);

  // Fast-path 1: --version
  // 零依赖:不加载 React、不加载 SDK、不加载任何重量级模块
  if (args.includes("--version")) {
    console.log(globalThis.MACRO.version);
    process.exit(0);
  }

  // Fast-path 2: --dump-system-prompt
  // 输出系统提示词后退出,用于调试和审计
  if (args.includes("--dump-system-prompt")) {
    const context = await import("../context.js");
    console.log(await context.buildSystemPrompt());
    process.exit(0);
  }

  // Fast-path 3: --claude-in-chrome-mcp
  // 启动 Chrome MCP 服务器(浏览器自动化能力)
  if (args.includes("--claude-in-chrome-mcp")) {
    const { startChromeMCP } = await import("../services/mcp/chrome.js");
    await startChromeMCP();
    return;
  }

  // Fast-path 4: --daemon-worker
  // 守护进程模式,用于后台持续运行
  if (args.includes("--daemon-worker")) {
    const { runDaemonWorker } = await import("../services/daemon.js");
    await runDaemonWorker();
    return;
  }

  // Fast-path 5: 后台会话管理命令
  // ps / logs / attach / kill -- 管理后台运行的 Claude Code 会话
  if (args[0] === "ps") { /* 列出会话 */ }
  if (args[0] === "logs") { /* 查看日志 */ }
  if (args[0] === "attach") { /* 连接到会话 */ }
  if (args[0] === "kill") { /* 终止会话 */ }

  // Fast-path 6: 远程控制 / Bridge 模式
  // 用于 IDE 集成,接受远程指令
  if (args.includes("--remote-control") || args.includes("--bridge")) {
    const bridge = await import("../bridge/index.js");
    await bridge.start();
    return;
  }

  // ---- 默认路径:启动完整的 CLI 应用 ----
  // 这是最重的路径,会加载 React、Ink、所有工具等
  const mainModule = await import("../main.js");
  await mainModule.cliMain();
}

教学要点:Fast-path 设计模式

这是一种常见的 CLI 优化策略。--version 之类的操作用户期望瞬间完成,如果每次都要加载整个应用(React 渲染引擎、50 个工具、SDK 初始化等),响应时间可能达到数秒。通过在入口处提前拦截简单请求,可以做到毫秒级响应。

启动时间对比:

--version (Fast-path):
  bootstrap-entry.ts -> cli.tsx -> 打印版本 -> 退出
  耗时:< 100ms

默认路径 (Full-path):
  bootstrap-entry.ts -> cli.tsx -> main.tsx ->
  加载 React/Ink -> 初始化工具 -> 连接 API -> 进入 REPL
  耗时:1-3s
阶段 3:主循环初始化

当请求进入默认路径时,main.tsx 中的 cliMain() 被调用:

// main.tsx 中的 cliMain 函数(简化展示核心步骤)

export async function cliMain() {
  // 步骤 1: 初始化配置和认证
  // - 读取 ~/.claude/ 下的配置文件
  // - 检查 OAuth 认证状态
  // - 确认 API Key 可用
  await initializeConfig();

  // 步骤 2: 注册所有 CLI 命令
  // 使用 Commander.js 注册约 90 个命令
  const program = registerCommands();

  // 步骤 3: 启动 Ink 渲染
  // Ink 是一个用 React 组件渲染终端 UI 的框架
  // 将整个应用渲染为 React 组件树
  const { waitUntilExit } = render(
    <App program={program} />
  );

  // 步骤 4: 进入 REPL 循环
  // Read-Eval-Print Loop:读取用户输入、处理、输出结果、循环
  await waitUntilExit();
}

4.2 main.tsx — 主控制器

main.tsx 是整个应用最核心的文件(约 4690 行),承担了以下职责:

职责说明
命令注册通过 Commander.js 注册所有 CLI 命令
Ink 渲染用 React 组件树渲染终端 UI
REPL 循环持续接收用户输入并处理
会话管理管理对话历史和上下文
工具调度根据用户意图选择并执行工具
错误处理全局错误边界和异常恢复

为什么 main.tsx 有 4690 行?在原始项目中,这个文件很可能被拆分成很多模块。但在源码恢复过程中,它被还原为一个单体文件。这提醒我们:真实项目中应该保持模块化,单个文件控制在 800 行以内。

4.3 QueryEngine — 对话生命周期

QueryEngine 是对话的"管理者",负责一次完整对话的生命周期:

QueryEngine 生命周期:

  创建 QueryEngine
       |
       v
  初始化上下文 -----> 加载对话历史
       |                  |
       v                  v
  等待用户输入 <----- 恢复会话状态
       |
       v
  构建 API 请求
  (系统提示 + 历史消息 + 用户输入 + 可用工具定义)
       |
       v
  调用 Anthropic API
       |
       v
  接收 LLM 响应
       |
       +---> 纯文本 --> 渲染给用户 --> 等待下一轮输入
       |
       +---> 工具调用 --> 执行工具 --> 结果加入上下文 --> 再次调用 API
       |
       v
  持久化消息(保存对话历史)

QueryEngine 的核心职责:

  1. 消息管理 – 维护消息数组(用户消息、助手消息、工具结果)
  2. 上下文窗口控制 – 当消息超过上下文窗口时触发压缩(compact)
  3. 持久化 – 将对话保存到磁盘,支持断点恢复
  4. 成本追踪 – 记录每次 API 调用的 token 消耗和费用

4.4 context.ts — 上下文构建

context.ts 负责构建发送给 LLM 的系统提示和上下文信息。它决定了 LLM 能看到什么信息,直接影响回答的质量。

系统提示的主要构成:

系统提示 (System Prompt)
  |
  +-- 身份定义:你是 Claude Code,一个编程助手
  |
  +-- 环境信息:操作系统、工作目录、Shell 类型
  |
  +-- 项目信息:目录结构、Git 状态
  |
  +-- 工具描述:每个工具的名称、功能、参数说明
  |
  +-- 项目规则:来自 CLAUDE.md 的项目定制规则
  |
  +-- 全局规则:来自 ~/.claude/rules/ 的用户偏好
  |
  +-- 上下文状态:当前 token 使用量、窗口余量

4.5 query.ts — Agent Loop

query.ts(约 1729 行)实现了 Claude Code 最核心的 Agent Loop。这是一个 while(true) 循环,模拟了"思考 -> 行动 -> 观察"的循环。Agent Loop 的完整实现分析详见 02-Agent Loop

Agent Loop 核心循环伪代码:

// query.ts 中 Agent Loop 的核心逻辑(伪代码)

async function agentLoop(queryEngine: QueryEngine) {
  // 构建初始消息列表
  let messages = [...historyMessages, userMessage];

  while (true) {
    // ---- 第 1 步:调用 LLM ----
    // 将完整的上下文发送给 Anthropic API
    const response = await anthropicClient.messages.create({
      model: selectedModel,
      system: systemPrompt,       // 来自 context.ts
      messages: messages,          // 对话历史 + 最新用户输入
      tools: toolDefinitions,      // 来自 tools.ts 的工具 schema
      max_tokens: 8192,
    });

    // 记录 token 消耗
    trackCost(response.usage);

    // ---- 第 2 步:解析响应 ----
    // LLM 的响应可能包含多个 content block:
    //   TextBlock:    纯文本输出(展示给用户的内容)
    //   ToolUseBlock: 工具调用请求(需要执行的操作)
    const assistantMessage = response.content;
    messages.push({ role: "assistant", content: assistantMessage });

    // 收集所有工具调用
    const toolCalls = assistantMessage.filter(
      (block) => block.type === "tool_use"
    );

    // ---- 第 3 步:如果没有工具调用,循环结束 ----
    // LLM 认为不需要再做任何操作,对话轮次结束
    if (toolCalls.length === 0) {
      break;
    }

    // ---- 第 4 步:执行所有工具调用 ----
    const toolResults = await Promise.all(
      toolCalls.map(async (toolCall) => {
        try {
          const tool = getToolByName(toolCall.name);
          const result = await tool.execute(toolCall.input);
          return {
            type: "tool_result",
            tool_use_id: toolCall.id,
            content: result,
          };
        } catch (error) {
          return {
            type: "tool_result",
            tool_use_id: toolCall.id,
            content: "Error: " + error.message,
            is_error: true,
          };
        }
      })
    );

    // ---- 第 5 步:将工具结果加入上下文 ----
    messages.push({ role: "user", content: toolResults });

    // ---- 继续循环 ----
    // LLM 将基于工具结果决定:继续调用工具?还是回复用户?
  }
}

工具系统 采用经典的接口 -> 注册 -> 实现三层架构,通过统一的 Tool 接口让 Agent Loop 不需要知道具体工具的细节。工具注册了约 50 个工具,按功能分为文件操作、命令执行、网络访问、任务管理、用户交互、模式切换、系统扩展等类别。(详见 03-工具系统

上下文压缩 – 当对话历史超过 LLM 的上下文窗口时,Claude Code 会触发压缩机制:

压缩前(可能 100K+ tokens):
  [系统提示] [用户消息1] [助手回复1] [工具调用1] [工具结果1]
  [用户消息2] [助手回复2] [工具调用2] [工具结果2]
  ... (大量历史消息) ...
  [最新用户消息]

压缩后(约 20-30K tokens):
  [系统提示]
  [压缩摘要:前 20 轮对话的关键信息概括]
  [最近 5 轮对话的完整内容]
  [最新用户消息]

压缩服务位于 src/services/compact/,它会调用 LLM 对历史消息生成摘要,用摘要替换原始消息,从而为新的对话腾出空间。


五、启动追踪示例

用户说:“帮我修复 src/utils.ts 中的 bug”

循环第 1 轮:
  LLM 思考:我需要先看看这个文件的内容
  LLM 行动:调用 FileReadTool 读取 src/utils.ts
  观察:获得文件内容

循环第 2 轮:
  LLM 思考:我发现了第 42 行的空指针问题
  LLM 行动:调用 FileEditTool 修复代码
  观察:文件修改成功

循环第 3 轮:
  LLM 思考:修复完成,运行测试验证
  LLM 行动:调用 BashTool 运行 npm test
  观察:测试通过

循环第 4 轮:
  LLM 思考:所有工作完成,向用户汇报
  LLM 行动:(无工具调用,纯文本回复)
  -> 循环结束

六、关键文件索引

核心文件职责

文件行数(约)职责
main.tsx4690主控制器,命令注册,REPL 循环
query.ts1729Agent Loop 核心实现
QueryEngine.ts1295对话生命周期管理
context.ts系统提示和上下文构建
Tool.ts工具接口定义
tools.ts工具注册中心
commands.ts命令注册和路由
bootstrapMacro.ts29全局构建元数据注入
bootstrap-entry.ts5应用绝对入口
cli.tsx300CLI 入口和参数分发

目录结构概览

src/
  |
  +-- entrypoints/          # 应用入口点
  |   +-- cli.tsx           # CLI 主入口(参数分发)
  |   +-- mcp.ts            # MCP 服务器入口
  |   +-- sdk/              # Agent SDK 入口
  |
  +-- bootstrap/            # 启动初始化逻辑
  |
  +-- commands/             # CLI 命令实现(约 90 个命令)
  |   +-- install-slack-app/
  |   +-- init/
  |   +-- ...
  |
  +-- tools/                # 工具实现(约 50 个工具)
  |   +-- BashTool.ts
  |   +-- FileEditTool.ts
  |   +-- GrepTool.ts
  |   +-- ...
  |
  +-- components/           # React/Ink UI 组件
  |
  +-- services/             # 服务模块
  |   +-- api/              # Anthropic API 通信
  |   +-- mcp/              # MCP 协议实现
  |   +-- oauth/            # OAuth 认证
  |   +-- analytics/        # 使用分析
  |   +-- compact/          # 上下文压缩
  |   +-- voice/            # 语音输入
  |   +-- plugins/          # 插件系统
  |
  +-- hooks/                # React Hooks(约 70 个)
  |   +-- IDE 集成 hooks
  |   +-- 快捷键 hooks
  |   +-- 设置 hooks
  |   +-- ...
  |
  +-- skills/               # 技能系统
  |   +-- 加载器
  |   +-- 内建技能内容
  |
  +-- assistant/            # 助手行为模块
  |
  +-- bridge/               # 桥接连接(IDE、远程)
  |
  +-- ink/                  # Ink 渲染扩展
  |
  +-- state/                # 应用状态管理
  |
  +-- types/                # TypeScript 类型定义
  |
  +-- screens/              # 全屏 UI 界面
  |
  +-- server/               # 服务端逻辑
  |
  +-- query/                # 查询处理
  |
  +-- vim/                  # Vim 模式支持
  |
  +-- utils/                # 工具函数
      +-- api/              # API 工具
      +-- auth/             # 认证工具
      +-- shell/            # Shell 工具
      +-- agent-context/    # Agent 上下文工具

核心技术栈

技术用途说明
TypeScript开发语言严格模式关闭,ESM 模块系统
React + Ink终端 UI用 React 组件模型渲染终端界面
Anthropic SDKAPI 调用与 Claude 模型通信
Commander.js命令解析CLI 命令注册和路由
Zod数据校验工具参数的 schema 定义和校验
WebSocket实时通信IDE 集成、远程控制
Bun运行时包管理和开发运行

设计模式

模式应用位置说明
ReAct 模式Agent LoopLLM 在循环中交替进行推理和行动
策略模式工具系统所有工具实现统一的 Tool 接口,可自由增删替换
注册表模式tools.ts, commands.ts集中管理所有可扩展的功能点
观察者模式React Hooks约 70 个 Hooks 管理 UI 状态和副作用
快速路径模式CLI 入口对简单请求零依赖处理,避免加载重量级模块

关键设计决策

  1. 为什么选择 React/Ink 而不是传统终端库? React 的组件模型使得复杂终端 UI 的管理变得可行。Claude Code 的终端界面包含流式文本输出、进度条、工具调用展示等复杂交互,React 的声明式 UI 和状态管理非常适合这种场景。

  2. 为什么使用 Zod 而不是手动校验? Zod 同时提供 TypeScript 类型推导和运行时校验。工具参数的 schema 定义一次,就可以同时在编译时获得类型检查、在运行时获得数据校验、还能直接转换为 JSON Schema 发送给 LLM。一举三得。

  3. 为什么 Agent Loop 使用 while(true) 而不是递归? while 循环比递归更安全 – 它不会导致栈溢出,内存使用更可控。Agent Loop 可能执行数十甚至上百轮迭代(处理大型代码库时),递归在这种场景下是有风险的。

延伸阅读

如果你希望深入了解以下主题,可以继续阅读:

  • Agent Loop 完整实现02-Agent Loop
  • 工具系统详解03-工具系统
  • 上下文压缩机制:阅读 src/services/compact/ 了解如何在有限上下文窗口中管理长对话
  • MCP 协议集成:阅读 src/services/mcp/ 了解如何通过 Model Context Protocol 扩展工具能力
  • React/Ink 终端 UI:阅读 src/components/src/hooks/ 了解如何用 React 构建终端界面
  • 命令系统:阅读 src/commands/ 了解 90+ 命令的实现方式

本文档基于 Claude Code 恢复版源码(version 999.0.0-restored)撰写。恢复版是从 source maps 重构的代码树,部分模块使用兼容性垫片或降级实现,与原始上游实现可能存在差异。

更多推荐