乐于分享
好东西不私藏

Claude Code 源码拆解:它其实是一套 Agent Runtime

Claude Code 源码拆解:它其实是一套 Agent Runtime

副标题:从 CLI 外壳、工具系统到 QueryEngine 内核,一口气看懂它怎么跑起来

仓库:https://github.com/freekatz/claude-code-src/tree/main

一开始我也以为,Claude Code 无非就是“把 Claude 塞进终端里”,做成一个能聊天、能执行命令的 CLI。
但把源码真正啃下来之后,我发现这个判断明显低估它了。

它的核心根本不是一个聊天壳,而是一套正在成形的 Agent Runtime
里面有会话内核,有工具协议,有消息流,有长会话治理,甚至已经长出了多代理协作的雏形。

所以这篇文章不准备停留在“它能做什么”这种表层介绍,而是想直接把它的源码骨架拆开:
cli.tsxmain.tsxQueryEngine.tsquery.tstools.ts,看清楚 Claude Code 到底是怎么跑起来的。


先说结论:它的本质不是聊天 UI,而是终端里的 Agent Runtime

如果只看表面,你会觉得这像是一个“命令行聊天工具”:

  • 能输入自然语言
  • 能显示回复
  • 能执行一些工具
  • 还能开子代理

但源码真正表达出来的,不是“聊天程序”思路,而是另一件事:

把用户输入,变成一个可持续推进的 Agent 回合(turn);让模型在回合中安全地调用工具、更新状态、写回消息流,并在必要时做压缩、恢复与继续执行。

换句话说,这套系统真正的主线是:

CLI 入口
  ↓
启动初始化(配置 / 环境 / 鉴权 / 网络 / 遥测)
  ↓
装配运行时(commands + tools + agents + settings + session)
  ↓
进入 REPL 或非交互 QueryEngine
  ↓
query loop
  ↓
模型输出 assistant / tool_use
  ↓
工具调度执行
  ↓
tool_result 回灌消息流
  ↓
继续下一轮,直到任务完成

图 1:先看全局总图

图注: 这套源码的中心不是 UI,而是 QueryEngine + tool runtime。入口层负责分流和装配,命令层负责用户控制,工具层负责模型行动,基础设施层负责托底。


一、这套源码最重要的设计目标是什么?

我看下来,它主要在解决 5 件事。

1)同一套内核,同时支持 CLI、REPL、SDK、远程/桥接模式

从目录和入口就能看出,这不是只为一个终端聊天壳写的。

它同时要支持:

  • 普通 CLI 启动
  • 交互式终端 REPL
  • 非交互 print / stream-json
  • daemon / bridge / remote-control
  • direct connect / ssh remote
  • MCP server / 资源读取
  • 子代理 / 多代理 / worktree / remote agent

所以它必须把“会话执行内核”独立出来,而不是把逻辑写死在 UI 里。

2)工具调用必须是一等公民,而不是外挂

它更像:

  • 模型输出是消息流的一部分
  • tool_use / tool_result 是消息流中的正式节点
  • 工具有 schema、权限、UI、结果格式、并发规则
  • 工具调用会反向影响上下文和会话状态

也就是说:

工具系统不是补丁,而是运行时的一部分。

3)权限与安全控制必须嵌进执行路径

比如 BashTool / PowerShellTool / FileEditTool 这些能力,并不是“能执行就行”,而是从设计上就包含:

  • schema 校验
  • destructive command 检查
  • sandbox 决策
  • 只读约束
  • 自动/手动权限模式
  • 是否允许后台运行

4)长会话必须可压缩、可恢复、可持久化

Agent 系统真正麻烦的,从来不是跑一轮,而是:

  • 上下文会越积越大
  • tool result 会越来越重
  • token 会爆
  • 中途中断后要恢复
  • 子代理和主代理之间还要共享一部分状态

5)启动性能被认真优化过

main.tsxcli.tsx 里有很明显的“启动性能工程”痕迹:

  • 提前做 profile checkpoint
  • 提前触发 MDM raw read
  • 提前做 keychain prefetch
  • 大量 feature-gated lazy import
  • 能并行的初始化尽量并行
  • fast path 尽量不加载重模块

二、别被目录吓到:真正主干其实只有 6 层

第一次看这个仓库,很容易被目录数量劝退。但按职责压缩,其实可以归纳成 6 层:

  • Entrypoints
  • Runtime Assembly
  • Conversation Core
  • Command Layer
  • Tool Layer
  • Infra Layer

这也是为什么你会觉得它目录很多,但真正的运行骨架其实很稳定。


三、Entrypoint 层:决定从哪条启动路径进入

核心文件:

  • entrypoints/cli.tsx
  • main.tsx
  • entrypoints/init.ts

这一层负责回答:

  • 现在是不是 --version 这种 fast path?
  • 是不是 daemon / bridge / remote-control?
  • 是不是非交互 print 模式?
  • 是不是要进入 REPL?
  • 初始化要做哪些最早期动作?

这一层最值得学的三个点

1. cli.tsx 很薄,但特别重视“分流”

它不是一启动就把整个程序都加载进来,而是:

  • 先读参数
  • 对 fast path 走最小化导入
  • 只有必要时才动态 import 大模块

2. init.ts 只做 runtime bring-up,不做业务逻辑

它主要负责:

  • enable configs
  • 应用安全环境变量
  • 配置 CA / mTLS / proxy
  • 预连接 API
  • 远程设置 promise 初始化
  • scratchpad 初始化
  • 清理钩子注册
  • Windows shell 处理

3. main.tsx 本质是“总装厂”

它不是简单入口,而是在做 runtime assembly:

  • 解析 CLI options
  • 准备 settings / model / permissions
  • 装配 tools
  • 装配 commands
  • 装配 agent definitions
  • setup cwd / worktree / session
  • 决定进入 REPL、resume、ssh、headless query 等不同路径

图 2:启动之后,一次请求到底怎么跑

图注: Claude Code 的核心不是“问一次答一次”,而是一个 可循环的 Agent 回合系统:模型思考 → 发起工具调用 → 工具结果回灌 → 再继续思考,直到任务完成。


四、Command 层:用户显式命令入口

核心文件:

  • commands.ts
  • commands/*

Command 层不是模型工具,而是面向用户的控制面

你可以把它理解成:

  • Commands:用户直达系统功能的入口
  • Tools:模型在推理过程中可调用的能力

这两层必须分开,不然系统很快会混乱。


五、Tool 层:模型可调用能力总线

核心文件:

  • Tool.ts
  • tools.ts
  • tools/*

这是整套源码里最关键的一层之一。

Claude Code 的工具系统,不是“命令集合”,而是一层完整的工具协议系统

每个工具大体都具备这些元素:

  • 名称
  • 输入 schema
  • 输出 schema
  • prompt 描述
  • 权限检查
  • UI 渲染
  • 执行逻辑
  • 并发规则
  • 是否会修改上下文

Tool.ts 更像工具层的接口定义;tools.ts 则负责统一注册、启停裁剪和组合最终 tool pool。


图 3:工具系统与调度图

图注: 工具层不是“很多工具摆在一起”,而是一个典型的 协议 + 注册表 + 实现 三段式设计。而且它已经有了成熟的调度策略:读型工具并发,写型工具串行。


六、为什么 Tool 层是全系统最值钱的地方?

因为 Agent 的关键不在“会聊天”,而在:

模型能不能在正确边界里稳定地行动。

这套代码为此做了几个关键设计:

1)每个工具都是结构化对象,不是裸函数

这带来的好处是:

  • 输入输出可校验
  • 可自动生成模型可见 prompt
  • 可做统一权限处理
  • 可做统一 UI 展示
  • 可做统一 telemetry
  • 可做统一并发调度

2)ToolUseContext 很重,但这是故意的

工具不是一个“独立函数”,而是运行在会话运行时内部的状态节点

3)不是一律串行,读型工具可以并发

这说明它不是“能跑就行”,而是已经非常在意效率与一致性。

4)工具不仅返回结果,还会影响后续上下文

比如:

  • 改 cwd
  • 更新 app state
  • 注册后台任务
  • 追加消息
  • 影响后续权限状态
  • 修改文件历史 / 快照

所以工具层不是配角,而是 Agent Runtime 的行动协议层。


七、QueryEngine:这才是 Claude Code 的会话内核

核心文件:

  • QueryEngine.ts
  • query.ts
  • services/tools/toolOrchestration.ts

源码里有一句很准确的话:

QueryEngine owns the query lifecycle and session state for a conversation.

翻译成人话就是:

一段会话,不管 UI 怎么变化,真正管理“这一轮 Agent 怎么跑”的,就是 QueryEngine。

它管的事情非常多:cwd、tools、commands、mcpClients、agents、read file cache、system prompt、model、budget、partial messages、abortController、usage、discovered skills、nested memory paths……

这不是“发个请求”的类,而是一个会话回合控制器


八、真正的主循环在 query.ts

如果要我选“全仓库最重要的一个逻辑文件”,我大概率会选 query.ts

因为它定义了 Agent 最核心的循环:

  1. 组装系统提示词 + 用户上下文 + 消息历史
  2. 发起模型请求
  3. 流式接收 assistant 输出
  4. 如有 tool_use,则调度工具执行并生成 tool_result
  5. 把结果回填消息流
  6. 必要时做 compact / snip / recovery
  7. 满足终止条件后结束本轮

这就是一个标准但工程细节很重的 Agent loop


九、BashTool 为什么值得单独拿出来讲

核心文件:

  • tools/BashTool/BashTool.tsx

表面上它只是“执行命令”,但你一读就会发现,它实际上做了很多事情:

  • Zod schema 校验
  • 背景任务控制
  • command parsing
  • destructive / read-only / sandbox 规则
  • 结果裁剪与存盘
  • UI 折叠显示
  • 文件历史跟踪
  • git 操作跟踪
  • 输出预览生成
  • image output 适配
  • 任务输出路径管理

也就是说:

BashTool 不是 shell wrapper,而是一个安全可控的命令执行子系统。


十、AgentTool:说明这个项目已经不是单代理玩具了

核心文件:

  • tools/AgentTool/AgentTool.tsx

这个工具的存在本身就说明很多问题。

它意味着:

  • 主代理可以派子代理干活
  • 子代理可以有自己的 prompt、cwd、mode
  • 可以后台运行
  • 可以 worktree 隔离
  • 可以 remote 运行
  • 可以 team / teammate 化

这会带来完全不同层级的系统复杂度:

  • 上下文边界
  • 子任务生命周期
  • 进度上报
  • 远程 / worktree 隔离
  • 结果回传
  • 代理身份与 UI 区分

图 4:AgentTool 多代理结构图

图注: AgentTool 的存在说明 Claude Code 已经不是单代理模型,而是在做子任务拆分、后台执行、worktree 隔离、远程代理和多代理协作。


十一、为什么说它本质上是“消息驱动”

整套系统里,很多看起来复杂的问题,最后都被统一落回到 Message 流:

  • user message
  • assistant message
  • system message
  • attachment message
  • tool_result
  • compact boundary
  • interruption message

这意味着它不是“函数拼接型架构”,而是典型的 消息驱动运行时

这会带来几个很大的好处:

  • 持久化更统一
  • 恢复更容易
  • replay 更自然
  • debug 更容易做 transcript
  • 各种模式下更容易复用

十二、长会话治理:这才是 Agent 工程真正烧人的地方

真正做过 Agent 产品的人都知道:

  • prompt 不难
  • 调工具不难
  • 难的是跑久了别崩

Claude Code 在这块显然下了很多工夫,所以仓库里有大量和以下问题相关的代码:

  • token warning state
  • auto compact
  • reactive compact
  • snip
  • context collapse
  • tool summary
  • transcript 恢复
  • session 持久化

这不是“代码臃肿”,而是产品已经进入真实复杂场景。


十三、这套架构为什么看起来大,但其实不乱?

因为它的复杂性主要来自三种“必要复杂度”:

1)产品形态本身就复杂

它不是单一 CLI,而是同时支持:

  • 本地交互
  • 非交互脚本
  • 远程连接
  • 后台会话
  • 子代理
  • MCP
  • 技能 / 插件
  • 多种 feature gate

2)安全与权限是硬需求,不是点缀

任何能:

  • 读文件
  • 改文件
  • 跑 shell
  • 上网
  • 开子代理
  • 接远程桥接

的系统,安全逻辑都不可能短。

3)长会话恢复和上下文治理非常难

这类代码看起来多,但很多其实都是为了“跑久了仍然可控”。


十四、如果我来给这套设计下定义,我会这么说

claude-code-src 的本质,是一个以消息流为中心、以工具协议为行动接口、以 QueryEngine 为回合控制器的终端 Agent Runtime。

拆开说就是:

  • 不是聊天 UI 项目,而是 Agent Runtime 项目
  • 不是单次 prompt 执行器,而是多回合状态机
  • 不是几个工具拼装,而是结构化工具协议系统
  • 不是单代理助手,而是已经具备多代理 / 远程 / 任务化能力的平台雏形

十五、如果你准备继续深挖源码,建议按这条路线读

不要一上来就试图把 main.tsx 一口气啃完,太痛苦。

建议顺序:

  1. entrypoints/cli.tsx
  2. entrypoints/init.ts
  3. main.tsx(先只看装配主线)
  4. QueryEngine.ts
  5. query.ts
  6. services/tools/toolOrchestration.ts
  7. Tool.ts
  8. tools.ts
  9. tools/BashTool/BashTool.tsx
  10. tools/AgentTool/AgentTool.tsx

结尾

如果只看表面,claude-code-src 像一个“大而杂的终端项目”。

但看清骨架后,你会发现它其实非常统一:

  • 入口层负责分流与装配
  • QueryEngine 负责会话生命周期
  • query loop 负责 Agent 回合推进
  • Tool 层负责行动能力
  • Command 层负责用户控制面
  • services / utils 负责把这一切撑住

所以最准确的理解不是:

“这是一个命令行版 Claude。”

而是:

这是一个终端里的 Agent Runtime,而 Claude 只是它的智能核心之一。