2026 年 3 月 31 日,Anthropic 给 npm 包 @anthropic-ai/claude-code 打包时,不小心把一个 60MB 的 cli.js.map 源映射文件塞了进去。这个文件里直接写着 Cloudflare R2 存储桶的公开地址,任何人下载 npm 包后顺着地址都能拿到 1906 个未混淆的 TypeScript 源文件,约 51.2 万行。
对你这种准备做 Agent 工程的人来说,这件事最值钱的不是八卦,而是——一份白捡的顶级工程教材。你不用再去翻官方那些只讲 API 的文档,直接看 Claude Code 的内部实现就行:核心引擎长什么样、工具怎么抽象、权限怎么分级、Hook 怎么挂、上下文怎么压。
本文讲清楚 4 件事:
骨架:四层架构 + QueryEngine这个单文件"心脏"一次 turn 怎么"转":代理循环 + 流式工具调用 + 权限门禁 工具与权限: Tool.ts把"能干什么"写成了契约扩展与安全:hooks、技能、多 Agent、上下文压缩
1,整体骨架:四层架构,引擎是"心脏",Bash 是"万能适配器"
先说结论:Claude Code 不是"LLM + Shell 聊天机器人",而是一个把权限、工具、Hook、插件、多 Agent 全部装进终端的"Agent 操作系统"。真正的循环不在入口,而在 4.6 万行的 QueryEngine.ts——这也是它和很多"100 行写个 agent"玩具最大的区别。

四层从顶到底依次是:
• UI 层 · React + Ink: main.tsx入口,负责终端渲染、流式输出;启动时还会并行预取 MDM 设置、钥匙串、API 预连接。• 交互层 · 命令解析: commands.ts注册 40+ 个斜杠命令(commit、review、config…),并对用户输入做预处理。• 核心层 · 代理循环:本图正中那个大虚线框,里面才是 Agent 真正"转"起来的地方。 • 服务层 · 外部集成:Anthropic API、MCP 服务器、Git、ripgrep,全部外挂。
核心层里有 6 个关键模块,可以分成上下两排:
QueryEngine.ts | hooks |
Tool.ts | skills / plugins |
coordinator · 多 Agent |
每一层只跟相邻层交互。main.tsx 不直接调 API,它只负责把用户输入和流式结果在终端里渲染出来;commands.ts 不执行工具,它只把命令翻译成一次对话的开始;QueryEngine.ts 不直接写文件,它把"写文件"这件事委托给 Tool.ts 注册好的 Write 工具。
这里容易产生一个误解:以为主循环写在入口 main.tsx 里。不是。main.tsx 在启动后只做两件事——并行预取 MDM/钥匙串/GrowthBook(顺便 lazy load OpenTelemetry 和 gRPC 两个大模块),然后初始化 React/Ink 渲染器。真正"转"起来的是 QueryEngine.ts 的 4.6 万行代码。这也解释了为什么改一个终端 UI 不会让 agent 行为变化——两层是物理隔离的。
2,一次 turn 是怎么"转"起来的:代理循环 + 流式工具调用
先说结论:Claude Code 的 agent 循环是一个"请求 → 流式响应 → 发现 tool_use → 权限检查 → 执行工具 → 结果回填 → 再请求"的闭环,直到模型不再调用工具才输出最终文本。整张循环图画成下面这样:

伪代码(按真实结构还原):
while (true) { response = await streamClaudeAPI(systemPrompt, context) # ① 流式 assistantMsg = 聚合流式片段(text / thinking / tool_use) # ② 聚合 if (assistantMsg.tool_use.length === 0) { return assistantMsg.text # ③ 收尾 } for (tool_use of assistantMsg.tool_use) { permission = 检查权限(tool_use) # ④ 门禁 if (permission.denied) { 记录 permission_denials; continue; } result = await executeTool(tool_use) # ⑤ 执行 context.push(tool_result) # ⑥ 回填 }}几个关键点:
• 流式聚合:响应不是一次吐完,是把 text / thinking / tool_use三种片段聚合成一条AssistantMessage。• 循环推进:只要响应里还有 tool_use,循环就不停;全没了才把最后那条text当作 turn 输出。• 权限先行:工具执行前先过权限检查, denied会被记入permission_denials,不会静默执行。• 结果回填: tool_result作为新消息塞回上下文,下一轮模型就能"看到"执行结果。• 触发压缩:上下文用到 75%–92% 容量时,触发 PreCompacthook + 信息密度评分压缩。
这里容易产生一个误解:以为工具调用是一锤子买卖——模型说"用 Bash",跑一次就完事。实际是循环:Bash 跑完,输出以 tool_result 回填,模型继续判断"下一步要不要再跑一个命令",直到它认为任务完成才会停止产出 tool_use。
3,工具与权限:Tool.ts 把"能干什么"写成了契约
先说结论:Claude Code 内置 30+ 工具,但形成能力基石的只有 8 个;它们全部实现同一个 Tool 接口——输入 Schema(Zod v4 校验)、权限模型、进度状态是公共三件套。
八大核心工具:
Bash | git:*、npm install:* 等) | |
Read | ||
Write | ||
Edit | ||
Grep | ||
Glob | ||
Task | ||
TodoWrite |
Bash 的"万能适配器"值得多说一句:早期版本试过用 Voyage 嵌入做语义代码搜索(RAG),但内部基准测试显示 ripgrep 表现更优,最终回到了"搜索而非索引"的策略。所以你看到的 Grep 工具背后不是向量数据库,而是一个 ripgrep 子进程——这是 Anthropic 用真金白银的 benchmark 投出来的设计选择。
权限系统是安全的核心,四档模式 + 白名单/黑名单 + 可编程回调:
• default:每个工具都问用户(最严格)。• acceptEdits:文件编辑类(Read / Write / Edit)自动放行,其余仍问。• plan:先出计划,审批后才执行(适合重构前对齐)。• bypassPermissions:全部放行,仅限可信环境(需allowDangerouslySkipPermissions)。
除了模式,SDK 还给了三个精细控制旋钮:allowedTools(自动放行白名单)、disallowedTools(直接禁掉)、canUseTool(toolName, input, ctx)(自定义回调,可以改写输入甚至拒绝):
options: {allowedTools: ["Read", "Bash"], // 这些工具自动放行disallowedTools: ["WebFetch"], // 这些工具彻底不可见permissionMode: "default",canUseTool: async (toolName, input, { signal, suggestions }) => {if (toolName === "Bash" && input.command?.includes("rm -rf")) {return { behavior: "deny", message: "危险命令已被拦截", interrupt: true }; }return { behavior: "allow", updatedInput: input }; },}这里容易产生一个误解:以为 allowedTools: ["Read","Write","Bash"] 是"把其他工具全禁了"。不是。它只是自动审批白名单——没列出的工具照样存在,只是每次都要过权限决策(默认弹窗,或走 canUseTool 回调)。真正"消失"是 disallowedTools 的语义。
4,扩展与安全:hooks、技能、多 Agent、上下文压缩
先说结论:Claude Code 的能力边界不是写死在代码里的——hooks 让宿主应用拦截循环、skills/plugins 让用户往里塞能力、coordinator 让多个 Agent 协作、上下文压缩让 200K 窗口经久耐用。
4.1 hooks:挂在循环上的"摄像头"和"闸门"
hooks 是 Claude Code 应用层(不是 Claude 模型本身)在循环特定节点调用的回调。完整事件包括:
• PreToolUse / PostToolUse / PostToolUseFailure:工具执行前后拦截,可改参数、可中止。 • UserPromptSubmit / Stop:用户输入提交时、本轮结束时触发。 • SessionStart / SessionEnd:会话启停。 • SubagentStart / SubagentStop:子代理启停。 • PreCompact / PermissionRequest:上下文压缩前 / 权限请求时。
4.2 多 Agent:AgentTool 与 coordinator
• 子 Agent: Task/AgentTool生成子代理,深度受限、上下文隔离,各干各的互不污染。• 团队协作: TeamCreateTool支持团队级并行,coordinator/目录专门做编排。
4.3 扩展:skills 与 plugins
• 技能系统: skills/目录定义可复用工作流,SkillTool执行;你可以加自定义技能。• 插件架构:内置 + 第三方插件都走 plugins/子系统统一加载。
4.4 上下文压缩:75%–92% 就要动手
• 触发时机:上下文用到 75%–92% 容量时触发。 • 信息密度评分:压缩不是盲删,先给消息算"信息密度"(代码占比越高密度越大),密度低的优先压缩。 • 关键消息保留:用户指令、工具结果、最终输出这些"关键消息"永远留下。
这里容易产生一个误解:以为 hooks 是给模型看的提示词。不是。hooks 是宿主进程的 JavaScript/脚本回调——PreToolUse 拦截的是"工具要不要执行",这跟模型读到的 system prompt 完全是两回事:一个是运行时控制面,一个是上下文内容。一个是门,一个是灯,别搞混。
彩蛋:源码里还藏着若干 feature flag,包括 KAIROS(自主守护模式)、PROACTIVE(主动建议)、VOICE_MODE(语音交互)、DAEMON(后台常驻)、BUDDY(一个带 18 种物种的虚拟宠物系统)。这些都没正式发布,但代码已经在了——是 Anthropic 内部对未来 Agent 形态的押注。
总结
一句话收口——Claude Code 把"agent"做成了操作系统:引擎驱动循环,工具统一契约,权限分级把关,钩子插件扩展开挂。
十六字口诀:引擎驱动循环,工具契约安全,权限分级把关,钩子扩展开挂。
QueryEngine.ts | ||
Tool.ts | ||
canUseTool | ||
底层逻辑不变——任何能"自己跑命令"的 agent,终将长出操作系统的样子:循环引擎、权限门禁、扩展生态,一个都不会少。
夜雨聆风