为什么写这个系列
我平时和 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.tsx | 4,683 |
| toolExecution.ts | 1,745 |
| query.ts(主循环) | 1,729 |
| AgentTool.tsx | 1,397 |
| QueryEngine.ts | 1,295 |
| runAgent.ts | 973 |
| prompts.ts | 914 |
| 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 官方观点。