这是什么:2026-03-31,Claude Code 的 npm 包不小心带了 sourcemap(还原源码的"地图"),被人扒出完整 TypeScript 源码(约 1884 文件 / 51 万行,版本 v2.1.88)。本篇是对那批源码架构层的拆解 —— 它内部怎么搭起来的。
注意:① 泄露/反编译源码,法律灰色,仅供理解,别商用 ② 部分细节是第三方从代码推断,非官方文档,可能有误 ③ 本机已存完整源码全家桶:
~/projects/开源项目/collection-claude-code-source-code,要核对实现直接去翻。内幕类发现(它收你哪些数据 / 模型代号 / 远程开关 / 路线图)见 Claude Code 内幕发现(遥测/代号/卧底/远程开关/路线图)。怎么用 CC 的工作流见 Claude Code 工作流(跨作者主题综合)。
一句话骨架:一个循环 + 11 层加固
整个 Claude Code 最核心的东西,小到可以画在餐巾纸上:
用户 → messages[] → 调 Claude API → 看返回
│
返回里有 "要用工具" 吗?
╱ 有 ╲ 没有
执行工具 返回文本,结束
把结果塞回 messages[]
↑——————— 回到"调 API"循环 ———————┘
类比:这就像一个厨房学徒。你给他菜谱(prompt),他看一眼,发现"需要先切洋葱"(要用工具),就去切,切完把洋葱端回来摆桌上(tool_result),再看菜谱下一步……直到不需要任何动作了,端出成品(文本)。这个"看一眼→动手→回看"的圈,就是所有 AI agent 的最小内核。代码在
query.ts(785KB,全仓最大单文件)。
关键认知:这个循环本身 50 行就能写完。Claude Code 51 万行里,99% 不是这个循环,而是围着它裹的"生产加固" —— 怎么防它跑飞、怎么省 token、怎么并行、怎么不丢进度。下面这 12 层就是把"玩具循环"变成"能交付的产品"的全部秘密。
12 层渐进式加固(Harness Mechanisms)
这是整批源码里最值钱的一张表 —— 它等于回答了"做一个生产级 agent,除了基础循环还缺哪些零件":
| # | 机制 | 一句话心法 | 它解决什么 |
|---|---|---|---|
| s01 | 核心循环 | "一个循环 + Bash 就够了" | agent 的起点:调 API、看 stop_reason、执行工具、塞回结果 |
| s02 | 工具调度 | "加一个工具 = 加一个处理器" | 循环不动,新增能力只是往调度表注册一个 handler |
| s03 | 计划 | "没计划的 agent 会迷路" | 先列步骤(TodoWrite / Plan Mode)再执行,完成率翻倍 |
| s04 | 子代理 | "拆大任务,各自清空上下文" | 派分身去干活,每个分身全新对话,主线不被噪音污染 |
| s05 | 按需知识 | "要用时才加载" | Skill / CLAUDE.md 通过工具结果注入,不塞爆系统提示 |
| s06 | 上下文压缩 | "装满了,腾空间" | 三套策略把旧对话压缩,见下文 |
| s07 | 持久化任务 | "大目标→小任务→落盘" | 任务图存磁盘,带状态/依赖,崩了能续 |
| s08 | 后台任务 | "慢活扔后台,agent 继续想" | 守护线程跑命令,完成后回头通知 |
| s09 | 代理团队 | "一个人扛不动→派队友" | 带异步收件箱的常驻队友 |
| s10 | 团队协议 | "共享沟通规则" | 用一套请求-响应协议(SendMessage)驱动 agent 间协商 |
| s11 | 自主代理 | "队友自己扫任务自己认领" | 空闲循环 + 自动认领,不用 leader 派每个活 |
| s12 | 工作树隔离 | "每人在自己目录里干" | 每个任务一个独立 git worktree,并行改文件不打架 |
Why 这张表重要:用户作为 PM,如果哪天要评估"我们自己要不要做 agent / 做到哪一层",这 12 层就是功能路线图的天花板参照 —— 从 s01 能跑,到 s06 能省钱,到 s09-s12 能多 agent 协作。难度和价值都是阶梯式往上爬的。
工具系统:每个工具是一个"标准插槽"
CC 有 40+ 内置工具(读写文件、Bash、搜索、网络、子代理、MCP、技能……)。它们不是各写各的,而是全部塞进一个统一模板(buildTool() 工厂),每个工具必须填好这几格:
一个工具 = 填满下面这张表:
├─ 生命周期
│ ├ validateInput() → 参数不对?最早一关就拒,别浪费后面
│ ├ checkPermissions() → 这个操作准不准做?(工具自己的授权逻辑)
│ └ call() → 真正干活,返回结果
├─ 能力标签(给调度器看的"说明书")
│ ├ isConcurrencySafe() → 能不能和别的工具同时跑?
│ ├ isReadOnly() → 有没有副作用?(只读的可放心并行)
│ └ isDestructive() → 是不是不可逆操作?(删文件等,要慎重)
├─ 渲染(终端 UI 怎么显示这个工具的输入/输出/进度)
└─ 面向 AI
└ prompt() / description() → 给模型看的"这个工具是干嘛的"
Why 这么设计:这就是 s02 那句"加工具 = 加 handler"的落地。循环代码永远不用改,想给 CC 加个新本事,只要按这张表填一个新工具注册进去。
isConcurrencySafe/isReadOnly这些标签是关键 —— 调度器靠它们决定"哪些工具能一口气并行跑,哪些必须排队",这是 CC 感觉"快"的底层原因。
工具大致分类:文件操作(Read/Edit/Write/Notebook)、搜索(Glob/Grep/ToolSearch)、执行(Bash/PowerShell)、网络(WebFetch/WebSearch)、代理与任务(Agent/SendMessage/Team/Task)、技能扩展(Skill/LSP)、计划工作流(EnterPlanMode/Worktree/TodoWrite)、交互(AskUserQuestion)、系统(Config/Schedule/Sleep)。
权限系统:一个"层层安检"的漏斗
模型想用某个工具时,不是直接放行,而是过一道安检漏斗,任何一层都能拦下:
工具调用请求
↓
① validateInput() 参数非法?直接退回
↓
② PreToolUse Hooks 你在 settings.json 写的自定义 shell 钩子
│ 能:批准 / 拒绝 / 改写输入
↓
③ 权限规则 alwaysAllow / alwaysDeny / alwaysAsk 三张名单匹配
│ (来自设置、CLI 参数、本次会话的决定)
↓ 没有规则命中?
④ 交互式弹窗 给你看工具名+参数,选 允许一次 / 总是允许 / 拒绝
↓
⑤ checkPermissions() 工具自己的逻辑(如路径沙盒检查)
↓
通过 → 真正执行 tool.call()
Why 重要:这解释了你平时为什么有时被弹窗问、有时自动放行 —— 取决于第③层那三张名单。也解释了 hooks(第②层)为什么这么强:它在规则之前,能直接改写或否决任何工具调用。拒绝 = 往对话里塞一条 "操作被拒" 的结果,循环继续(模型会知道这条路走不通,换招),而不是程序崩溃。
子代理与多代理:4 种"派分身"的方式
主 agent 可以派出分身干活,有 4 种隔离强度:
| 模式 | 怎么隔离 | 类比 |
|---|---|---|
| default | 同进程、共享对话 | 你自己换个话题接着想 |
| fork | 子进程、全新 messages[]、共享文件缓存 | 派个助理,给他干净的脑子但能看同一批文件 |
| worktree | 独立 git 工作目录 + fork | 助理在自己的房间改同一个项目的副本,改完再合 |
| remote | 通过桥接连到远程容器,完全隔离 | 派去外地办事处,彻底独立 |
多个 agent 之间靠 SendMessage(互发消息)、TaskCreate/Update(共享任务看板)、TeamCreate/Delete(团队生命周期)协作。集群模式(Swarm):一个 leader + 多个队友,共享任务看板和收件箱,但各自的对话、文件缓存、工作目录是隔离的 —— 队友自己去看板认领任务(对应 s11 自主代理)。
上下文压缩:对话装满了怎么办
模型的"记忆"(上下文窗口)有上限。长对话迟早装满,CC 有三套策略腾空间:
| 策略 | 做什么 | 类比 |
|---|---|---|
| autoCompact | token 超阈值时,把旧消息总结成一段摘要(单独调一次 API) | 把前半本会议记录浓缩成"前情提要" |
| snipCompact | 删掉"僵尸消息"和过时标记 | 撕掉草稿纸上划掉作废的部分 |
| contextCollapse | 重构上下文结构提升效率 | 把散乱笔记重新归类 |
压缩后的结构:[旧消息的压缩摘要] + [compact_boundary 分界标记] + [最近消息(完整保真)]。
Why:这就是你用 CC 久了会看到 "compacting conversation" 的时刻。理解这个能帮你判断何时该主动 /clear —— 摘要再好也会丢细节,关键任务最好别让它跨好几次自动压缩。
会话持久化:为什么能 --resume
每次对话都只追加写进一个 JSONL 日志(~/.claude/projects/<hash>/sessions/<id>.jsonl),一行一条消息。
--continue → 当前目录的上一次会话
--resume <id> → 指定某次会话
--fork-session → 新 ID,复制历史(从某个节点分叉)
写盘策略很讲究:用户消息阻塞等写完(防崩溃丢你的输入),助手消息即发即弃(保序队列,不卡流式)。
其它关键子系统
▸ MCP 集成 —— MCP 是"给 agent 插外部工具"的标准协议。CC 支持 5 种连接方式(stdio 子进程 / SSE / 流式 HTTP / WebSocket / 进程内),工具命名约定 mcp__<服务器>__<工具>,支持 OAuth 2.0 授权。
▸ 桥接层(Bridge) —— 让 Claude Desktop / Web 端能遥控你本机的 CLI 跑活。靠 JWT 认证 + 工作密钥交换,会话生命周期 create/run/stop,带退避重连。(对应你 memory 里那套 lark-channel-bridge / multi-window-bridge 的官方原型。)
▸ Feature Flag + 死代码消除(DCE) —— 编译时,feature('X') 为 false 的功能直接从包里删掉(不是运行时关,是物理不存在)。这就是为什么泄露源码里能看到一堆普通用户版本里根本没有的功能(语音、浏览器工具、协调器模式等)。具体有哪些藏起来的功能见 Claude Code 内幕发现(遥测/代号/卧底/远程开关/路线图)。
关键设计模式速查
工程上值得记的几个套路(PM 视角:这些是"为什么 CC 又快又稳"的工程答案):
| 模式 | 干什么 |
|---|---|
| AsyncGenerator 全链路流式 | 从 API 到屏幕一路流式吐字,不等全部生成完 |
| 构建器 + 工厂(buildTool) | 工具定义给安全默认值,新增工具不易出错 |
| Feature Flag + DCE | 编译时删掉关闭的功能,包更小、机密不外泄 |
| 即发即弃写入 | 保序队列非阻塞落盘,流式不卡顿 |
| 快照状态(FileHistory) | 文件操作可撤销/重做 |
| 上下文隔离(AsyncLocalStorage) | 同一进程里每个 agent 有独立上下文 |
| 环形缓冲区 | 错误日志有限内存,超长会话不爆 |
目录结构(要去翻源码时的地图)
本机源码在 ~/projects/开源项目/collection-claude-code-source-code。核心入口:
query.ts—— 主代理循环(785KB,最大文件,从这里看核心逻辑)Tool.ts+tools.ts—— 工具接口与注册QueryEngine.ts—— SDK/headless 查询生命周期tools/—— 40+ 工具各自一个目录services/—— API 客户端 / 遥测 / 压缩 / MCP / 工具执行器bridge/—— 桌面/远程桥接tasks/—— LocalShell / LocalAgent / RemoteAgent / Dream 等任务类型utils/permissions/—— 权限规则引擎utils/swarm/—— 多代理集群
运行时:Bun 编译成 Node.js ≥18 bundle。约 192 个 npm 依赖。