Claude Code 源码架构深度解析(一):你以为 Claude Code 是个 CLI,其实它更像一个 Agent 操作系统

0 阅读10分钟

为什么写这个系列

我平时和 AI Agent 打交道比较多,也看过不少开源的 coding agent。坦白说,很多项目看到 src/ 目录,架构大概就能猜个八九不离十:一个主循环、几个工具、一段 prompt,基本就把核心链路搭出来了。

直到我认真去读 Claude Code 这套实现。

最先让我停下来的,不是某个具体功能,而是规模:4756 个文件。看到这个数字的时候,我第一反应不是“功能真多”,而是——这背后一定埋着大量已经被工程化验证过的设计决策。

继续往下读,果然如此。 工具调用不是简单的 function call,而是一条 14 步的执行流水线;子 Agent 不是临时拉起跑个任务,而是有完整的生命周期管理;prompt cache 不是一个模糊概念,而是精确到字节级前缀对齐;权限系统也不是“加一层确认弹窗”这么简单,而是多层机制之间互不绕过。

我花了不少时间把核心模块过了一遍。读完之后最大的感受是:

今天很多关于 Agent 的讨论,还停留在“怎么让模型调工具”这一层;但一个真正能跑在生产环境里的 Agent 系统,要解决的问题,远比这复杂得多。

这也是我写这个系列的原因。 一方面,算是给自己做一次系统化整理;另一方面,我也觉得这些工程细节,对于正在做 Agent 产品、Agent 平台或者 coding agent 的人,应该会有一些参考价值。

几点说明:

  • 本系列内容基于 Claude Code 相关公开可获得代码与工程实现线索的阅读、整理和分析
  • 文中观点主要是我个人的理解,不代表任何官方立场;如果有理解偏差,欢迎讨论和指正
  • 全系列一共 5 篇,从整体架构到主循环、工具治理、多 Agent 调度,再到产品化设计,尽量每篇只回答一个核心问题

你以为的 Coding Agent,和真实的 Coding Agent

如果你用过 Cursor、Windsurf、Devin,或者任何一个开源的 coding agent,你大概率形成了一个认知框架:

  • 一个 main 文件负责启动
  • 一个 prompt 文件写系统提示词
  • 几个 tool 文件封装文件读写、shell 执行
  • 一个 utils 目录放杂项

打开 src/ 目录,文件不超过 20 个,逻辑链路一目了然。

然后你打开 Claude Code 的源码。

4756 个文件。50+ 个顶层模块目录。多入口、多 Agent、多运行模式。你意识到,这不是你以为的"coding agent"。

这是一个用 TypeScript 写的 Agent Operating System。

这篇文章是整个系列的第一篇。我不会急着带你看代码细节,而是先帮你建立一个关键认知:Claude Code 不是一个能写代码的 CLI 工具,而是一个完整的 Agent 运行平台。理解这个定位,是理解后续所有架构决策的前提。


一、4756 个文件意味着什么

先看一组数字,直观感受这个工程的规模:

模块文件数
utils/564
components/389
commands/207
tools/184
services/130
hooks/104
ink/(TUI 框架)96
bridge/(远程/IDE 集成)31
skills/20
tasks/12

再看几个核心文件的行数:

文件行数
main.tsx4,683
toolExecution.ts1,745
query.ts(主循环)1,729
AgentTool.tsx1,397
QueryEngine.ts1,295
runAgent.ts973
prompts.ts914
Tool.ts(工具基类)792

光一个 main.tsx 就 4683 行。一个工具执行模块 1745 行。一个 Agent 调度总控 1397 行。

这些数字本身就在传递一个信息:这不是一个"能用就行"的 prototype,而是一个经历了大量工程迭代的产品级系统。

我在看很多开源 coding agent 的时候,常常有一种感觉:它们解决的是"模型怎么调用工具"这个问题,但没有解决"工具调用出问题了怎么办"、"上下文太长了怎么压"、"多个任务怎么不互相干扰"、"权限怎么管控"这些真正的工程问题。

Claude Code 解决的正是后面这些问题。


二、不是一个入口,而是一个平台

大部分 coding agent 只有一个入口:命令行启动,进入交互循环,结束退出。

Claude Code 有多个入口:

  • cli.tsx:命令行交互入口
  • init.ts:初始化入口
  • mcp.ts:MCP 协议入口
  • sdk/:SDK 编程接口

同一个 Agent 运行时,可以同时服务于:

  • 终端里敲命令的用户
  • 通过 MCP 协议连接的外部系统
  • 通过 SDK 调用的程序
  • IDE 插件(如 VS Code 扩展)

这就是平台化设计。

为什么这很重要?因为它意味着 Claude Code 的核心不是"命令行应用",而是一个可以被多种前端接入的 Agent 运行时。CLI 只是它暴露给用户的其中一个界面。

这个设计选择决定了很多后续架构:状态管理必须解耦、工具系统必须标准化、权限模型必须可复用、Bridge 系统必须存在。如果你一开始只为 CLI 设计,后面想加 MCP 支持、IDE 集成,几乎要重写核心。

这让我想到操作系统的类比。操作系统不关心你是通过终端、GUI 还是远程 SSH 操作,它提供的是底层的进程管理、文件系统、权限控制。Claude Code 做的事情类似——Agent 调度相当于"进程管理",工具系统相当于"文件系统",三层安全防护相当于"权限控制"。传统 AI 工具和 Agent 系统的分水岭,可能不在模型能力,而在运行时的组织能力。


三、Fast-path:一个被忽略的产品化细节

来看 cli.tsx 的入口逻辑。你可能以为它就是 import main; main.run() 这么简单。实际上,它有一套精心设计的 fast-path 分发——在加载完整运行时之前,先用极轻量的判断把"不需要完整能力"的请求拦截掉。

直接看源码(src/entrypoints/cli.tsx):

/**
 * Bootstrap entrypoint - checks for special flags before loading the full CLI.
 * All imports are dynamic to minimize module evaluation for fast paths.
 * Fast-path for --version has zero imports beyond this file.
 */
async function main(): Promise<void> {
    const args = process.argv.slice(2);

    // Fast-path for --version/-v: zero module loading needed
    if (
        args.length === 1 &&
        (args[0] === "--version" || args[0] === "-v" || args[0] === "-V")
    ) {
        console.log(`${MACRO.VERSION} (Claude Code)`);
        return;
    }
    // ...后面还有十几个 fast-path 分支

注释写得很直白:zero imports beyond this file--version 不加载任何其他模块就能返回。

整个 fast-path 链路还包括:

场景处理方式为什么不走主流程
查版本号直接打印,零依赖加载CI 里每天调百万次,能省一点是一点
远程控制模式走 bridge 通道不需要本地 REPL 交互
守护进程模式走 daemon 路径长驻进程有自己的生命周期
会话管理命令直接处理 ps/logs/attach/kill查看和管理后台会话不需要加载 Agent
worktree + tmux直接 exec 进 tmux只是准备执行环境

只有当没有命中任何 fast-path 的时候,才会通过 dynamic import 按需加载 main.tsx

你可能会觉得:至于吗?加载一个 main.tsx 能慢到哪去?

当你的 CLI 工具每天被上百万次调用的时候,这就很至于了。

--version 的响应速度直接影响 CI/CD 流水线性能。如果每次 claude --version 都要加载完整运行时(4683 行的 main.tsx + 所有依赖),CI 流水线里每次版本检查都要多等几秒。乘以百万次调用,这就是实实在在的资源浪费。

Fast-path 的设计思路很清晰:不需要加载整个运行时的场景,就不加载。

这种做法在 Web 框架领域很常见(Next.js 的按需加载、Webpack 的 code splitting),但在 AI Agent 领域我几乎没见过有人做。大部分 Agent 还在 prototype 阶段,根本没到需要优化启动性能的体量。Claude Code 已经在做了,说明它的工程化程度远超同行。


四、101 个命令:命令系统不是装饰品

src/commands/ 下面有 101 个命令

不是 5 个、不是 10 个,是 101 个。

从你可能听说过的:

  • /mcp:MCP 服务器管理
  • /memory:记忆管理
  • /compact:上下文压缩

到你可能没听过的:

  • /permissions:运行时权限管理
  • /hooks:Hook 系统控制
  • /plugin:插件管理
  • /skills:技能系统
  • /tasks:任务系统
  • /plan:规划模式
  • /review:代码审查
  • /agents:Agent 管理
  • /doctor:系统诊断
  • /bridge:远程桥接
  • /teleport:会话传送

而且命令系统不只是加载内置命令。它会统一加载:

  • Plugin 提供的命令
  • Skill 提供的命令
  • Bundled skills 的命令
  • 动态发现的 skills 命令

命令系统本身就是用户跟 Agent 运行时交互的控制面板,也是整个生态的入口。

说实话,我第一次看到 101 个命令的时候觉得有点夸张。但仔细想想,Docker CLI 也差不多——它的命令不只是"运行容器"的快捷方式,而是一套完整的系统管理界面。当你看到一个 CLI 工具有 101 个命令的时候,你应该意识到:这已经不是一个工具了,这是一个系统的控制台。


五、传统 AI 工具 vs Agent 系统:分水岭在哪

看完上面这些,我想提炼一个更本质的判断。

市面上的 AI coding 产品,大致可以分成两类:

第一类:AI 工具

  • 单一入口
  • 固定 prompt
  • 几个预置工具
  • 线性执行流程
  • 用完即走

这类产品解决的问题是:给开发者一个更好的代码补全 / 代码生成体验。

第二类:Agent 系统

  • 多入口、多前端
  • 动态 prompt 组装
  • 工具系统 + 治理流水线
  • 状态机驱动的主循环
  • 多 Agent 协作
  • 权限和安全层
  • 生态扩展(Skill / Plugin / MCP)
  • 上下文管理和 token 预算
  • 完整的生命周期管理

这类产品解决的问题是:构建一个可以长期、稳定、安全地自主执行复杂任务的 Agent 运行时。

Claude Code 显然属于第二类。从架构设计来看,它不仅解决了"Agent 怎么跑起来",还认真想过"跑出问题了怎么办"、"怎么和外部系统集成"、"怎么长期稳定工作"。

我越来越觉得,真正的 Agent 工程,核心不是"调模型",而是运行时的组织能力。模型能力可以通过升级 API 获得,但运行时的稳定性、安全性、可扩展性,只能靠工程来构建。


六、几个值得带走的东西

如果你正在做 Agent 产品,从 Claude Code 这套设计里至少能拿到两个启发:

一开始就想清楚运行时的边界。 你的 Agent 只服务 CLI?还是也要支持 API 调用、IDE 集成、MCP 协议?如果不只是 CLI,运行时就必须和前端解耦。Claude Code 从一开始就是多入口设计,后续的扩展代价很低。反过来,一开始只为 CLI 设计的系统,后面加 MCP 支持几乎要重写核心。

架构要为"第二天"设计。 第一天跑通一个 demo 不难。难的是第二天:用户量上来了怎么扩?出了安全问题怎么办?会话中断了怎么恢复?fast-path 不是过度优化,命令系统不是装饰品,它们都是在解答"第二天"的问题。


下一篇预告

这一篇我们建立了全局认知:Claude Code 是一个 Agent Operating System,不是一个 CLI 工具。

但光知道它是什么还不够。下一篇,我们要深入它的心脏——主循环引擎

一个用户请求从输入到输出,在这个 Agent 系统内部到底经历了什么?一个 1729 行的 query.ts 状态机是怎么驱动整个运行时的?Prompt 不是一段文本,而是一台精密的拼装机器——它是怎么组装的?模型的行为为什么不能靠"自觉",必须靠制度化约束?

这些问题,下一篇见。


本系列共 5 篇,源码来自 Anthropic 泄露的 npm 包中的 source map 还原。内容为个人理解与工程分析,不代表 Anthropic 官方观点。