---
title: "两天重建 Claude Code：一万行代码背后的架构设计"
author: deletexiumu
pubDatetime: 2026-03-31T00:30:00+08:00
featured: false
draft: false
tags:
  - Claude Code
  - AI Agent
  - AI 编程
description: "拆解 Claude Code 的 6 大核心机制：Agent Loop、Prompt Caching、工具系统、权限系统、Context 管理、多 Agent 协作。从反混淆源码出发，理解一万行代码中真正值钱的设计决策。"
---

很多人以为 AI Agent 就是调 API 加解析返回。但 Claude Code 超过一万行代码，真正跟 API 交互的可能不到十行。剩下的代码在干什么？

答案是构建一个 **Harness**——一套包裹模型的运行时基础设施，负责上下文管理、工具调度、权限控制等一切"模型之外"的事。Anthropic 的工程博客从 Prompt Engineering 写到 Context Engineering 再到 Harness Engineering，三步走完了这条路。最后一篇直接点出核心难题：Agent 要连续跑几个小时，每次新会话从零开始，就像"轮班制的工程师，新来的人对上一班的工作毫无记忆"。

Claude Code 就是这套 Harness Engineering 理念的产品化实现。

据社区分享，开发者 IceBearMiner 用纯 TypeScript 和极少的外部依赖，在短时间内重建了 Claude Code 的核心机制。这个实验说明了一件事——Claude Code 的架构虽然精巧，但每个机制背后的设计逻辑是可以被理解和复现的。

本文沿着一次用户输入的完整旅程，拆解 Claude Code 的 6 大关键机制。素材来自 npm 包 v2.1.66 的反混淆源码分析，交叉验证了官方文档和社区逆向成果。

![两天重建 Claude Code 架构封面图](/blog/rebuild-claude-code-architecture/01-cover.jpg)

---

## 机制一：Agent Loop——AI 的心跳循环

你在终端里输入一段话，Claude Code 需要持续工作——读文件、改代码、跑测试——直到任务完成。但模型每次 API 调用只能做一件事：输出文本，或者请求使用一个工具。怎么让它"持续干活"？

答案是一个 while 循环。

核心流程：用户输入 → 组装请求 → 调用 API（SSE 流式）→ 解析响应 → 根据 `stop_reason` 决定下一步。如果是 `end_turn`，模型自然结束，输出文本给用户；如果是 `tool_use`，执行工具，把结果追加到消息列表，再回到循环起点。就这么简单。

Tony Bai 的博客里用伪代码总结得很到位：

```python
messages = [...]
while True:
    response = model.generate(messages)
    if not response.tool_calls:
        break
    for tool in response.tool_calls:
        result = execute_tool(tool)
        messages.append(format_result(result))
```

但 Claude Code 的实际实现比这精巧得多。npm 包的源码经过打包混淆，反混淆后可以看到两个关键函数：负责创建隔离执行上下文的 fork agent runner（内部标识 `lR`），和负责实际消息循环的 async generator（内部标识 `JC`）。`lR` 管理 token 统计和 transcript 记录；`JC` 处理 API 调用和工具执行。

**工具执行不是"调一下就完"，而是走六个阶段**：renderToolCall（渲染 UI）→ permissionCheck（权限检查）→ preHook（执行前置钩子）→ checkpoint（创建文件检查点）→ executeTool（执行工具）→ postHook（执行后置钩子）。这六步确保每次工具调用都有权限管控、有回滚能力、有扩展点。

**`maxTurns` 护栏** 防止循环失控。不同场景配置不同上限：compact（上下文压缩）场景 `maxTurns: 1`，只允许一次 API 调用；side question 也是 1 轮；主循环从 agent 定义继承上限。这是成本控制的关键——没有护栏的 Agent 循环就是一张无限额信用卡。

**通用 fork runner 是最值得关注的设计亮点**。`lR` 不仅跑主循环，还复用于 compact（摘要压缩）、side questions（`/btw` 侧问题）、speculation（推测执行）、prompt suggestion（输入建议）等所有子流程。每次 fork 创建独立的 `readFileState` 和 `abortController`。一个 runner 跑所有场景，比每种场景写独立逻辑更可复用。

这意味着什么？Claude Code 长任务中途"停下来问你问题"或"自动压缩上下文"，背后都是同一个 fork runner 在不同场景下的分支。理解这一点，你就知道为什么有时它会"打断"正在进行的工作——那是优先级更高的子流程抢占了循环。

![Agent Loop 流程图](/blog/rebuild-claude-code-architecture/02-agent-loop.jpg)

---

## 机制二：Prompt Caching——省钱的秘密武器

每轮对话都要发送完整的 system prompt 加历史消息。Claude Code 的 system prompt 很长——身份声明、工具规范、编码风格、安全规则、CLAUDE.md 内容、当前环境信息，加起来轻松上万 token。如果每次都按原价计费，用一天下来费用惊人。

Anthropic 的 Prompt Caching 按前缀匹配工作：系统计算每个缓存断点的累积哈希值，如果与之前的请求匹配，就直接复用缓存。匹配条件严格——需要 100% 完全相同的前缀，在 TTL 内，且在同一工作空间（2026 年 2 月 5 日起从组织级改为工作空间级隔离）。

Claude Code 利用这个机制做了一个巧妙的分段设计。源码中有一个特殊的边界标记：

```
__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__
```

这条分割线把 system prompt 分成两段：

**静态段**（边界之前）：核心身份指令、工具说明、输出风格——这些跨请求不变的内容打上 `cache_control` 标记，长期命中缓存。

**动态段**（边界之后）：当前日期、Git 状态、CLAUDE.md 内容、MCP 指令、环境信息——每轮可能变化，不影响静态段缓存。

实际的排列顺序是：`tools` 数组 → system 静态段 → `__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__` → 动态段 → `messages` 历史。三层各自独立受不同参数变化影响。

省钱效果很直接：缓存写入 1.25 倍基础输入价格，但缓存读取只要 0.1 倍——相当于正常输入价格的十分之一。多轮对话中，大部分 token 走缓存价格，成本差距是数量级的。

有个容易踩的坑：最小可缓存 token 有门槛（Opus 4096、Sonnet 2048），低于阈值的块**静默失败**——不报错，只是 `cache_creation_input_tokens` 返回 0。另外 `tools` 数组排在最前面，改了工具定义会击穿后面所有层的缓存。

**实战启示**：改了 CLAUDE.md 后第一轮对话变慢、费用升高，是因为动态段内容变了需要重新计算。但静态段缓存不受影响。更重要的是：不要频繁改 tools 数组前面的工具定义，那才是真正击穿整个缓存的操作。

![Prompt Caching 动态边界示意图](/blog/rebuild-claude-code-architecture/03-prompt-caching.jpg)

---

## 机制三：工具系统——AI 的手和脚

模型只会输出文本。能"动手"操作文件、搜索代码、执行命令，全靠工具系统。Claude Code 的工具设计有几个值得细看的决策。

**Read 工具加行号前缀**。输出格式是 `cat -n` 风格，每行前面带行号（`行号\t内容`）。这不是为了好看——是让模型能精确引用代码位置。"第 42 行有 bug"这种反馈，只有带行号才说得清。单次读取上限 10000 tokens，超出需要用 `offset` 和 `limit` 分页。

**Edit 工具要求 old_string 唯一**。你给模型一段要替换的文本，如果在文件中出现了多次，直接报错——不会猜你要改哪一处，而是让你提供更多上下文直到唯一匹配。`replace_all` 参数可以批量替换，但默认关闭。这个约束看起来严格，实际上防止了"改错地方"这种灾难性 bug。

**Bash 工具有超时和截断**。默认 120 秒超时，输出上限 20MB。超限后完整输出持久化到临时文件，只返回前 2KB 预览，用 `<persisted-output>` 标签包装。这个设计既防止长时间命令卡死循环，又不丢数据——模型下一轮可以通过 Read 工具读取完整输出。

**Grep 基于 ripgrep**，优先用系统安装的 `rg`，否则走内嵌的 vendor 二进制。选正则搜索而不是向量数据库，是一个刻意的设计决策——无需预建索引，跨平台一致，对代码库搜索来说性能足够。

### Deferred Tools 与衰减算法

Claude Code 不是把所有工具的 schema 都塞进每次请求。低频工具被"延迟加载"——初始 prompt 中不包含它们的 schema，需要时通过 `ToolSearch` 工具动态发现。

怎么判断"低频"？源码中用了一个 7 天半衰期的衰减算法：

```javascript
// 半衰期衰减：7 天半衰期
let daysSinceLastUse = (Date.now() - lastUsedAt) / 86400000;
let decayFactor = Math.pow(0.5, daysSinceLastUse / 7);
let score = totalCount * Math.max(decayFactor, 0.1);
// 得分 < 500 则 defer
```

用过的工具累积使用次数，但随时间衰减。7 天没用的工具分数减半，累计分数低于 500 就被 defer。MCP 工具默认都是 deferred 的（它们天然是外部扩展，不应占初始 prompt 空间）。

效果：显著降低初始 `tools` 数组的 token 开销——这直接影响 Prompt Caching 的效率，因为 tools 排在缓存层级的最前面。高频工具始终可用，低频工具按需一次 ToolSearch 查询就能加载。

日常使用中，看到 Bash 输出 "output persisted to file" 不要慌，完整输出在临时文件里，模型会自动读取。ToolSearch 偶尔慢一拍，是因为你要用的工具被 defer 了，需要额外一次查询——这是省 token 的代价。

---

## 机制四：权限系统——安全与自主的平衡

AI 能执行命令、写文件、发网络请求。怎么防止它删库跑路？Claude Code 的权限系统在"安全"和"别老打断我"之间做了精细的分级。

### 四种用户可见模式

| 模式 | 行为 | 适用场景 |
|------|------|---------|
| `default` | 安全工具自动执行，危险操作需确认 | 日常开发 |
| `plan` | 只读为主，适合探索和规划 | 代码库探索、规划 |
| `acceptEdits` | 文件编辑自动批准，Bash 等仍需确认 | 代码迭代 |
| `dontAsk` | 不弹提示，只允许预批准工具 | 锁定环境 |

内部还有一个 `bypassPermissions` 模式，完全跳过权限检查，仅用于受信任的内部流程（如容器环境）。

### Auto Mode 的三层决策

Auto Mode 是最值得深入理解的。它不是简单的"全部放行"，而是三层过滤：

**第一层：规则表直接匹配**。用户在 `.claude/settings.json` 里配置的 allow/deny 规则，零延迟匹配。格式如 `Bash(npm test)` 表示只允许运行 `npm test`，`Read(*)` 表示允许读取任何文件。进入 Auto Mode 时，已知可授予任意代码执行的宽泛规则（如 `Bash(*)`、`Bash(python*)`）会被自动丢弃。

**第二层：自动批准只读和项目内编辑**。读文件、搜索代码不需要过分类器。项目目录内的文件编辑也自动放行——反正有版本控制可以回退。

**第三层：分类器**。其余操作交给一个两阶段分类器管道。第一阶段是单 token 快速过滤（yes/no），误拦率（FPR）8.5%，倾向于"宁可多拦"；第二阶段用链式推理精确判断，把误拦率降到 0.4%。两阶段共享几乎相同的 prompt，区别只在最后的指令，这样第一阶段的 prompt 缓存可以被第二阶段复用。代价是完整管道的漏放率（FNR）为 17%——Anthropic 自己承认这是当前的局限，分类器通常能识别危险操作，但有时误判用户同意覆盖了全部影响范围。

**关键设计：分类器看不到模型的推理过程**。它只接收用户消息和工具调用参数，助手的文本、推理链、工具输出全部剥离。为什么这样做？防止 prompt 注入。设想一个场景：恶意文件内容被读入工具输出，模型在推理中"合理化"了这段内容，如果分类器能看到推理过程，就可能被骗过。剥离推理后，攻击者必须同时绕过输入探测和让输出分类器独立批准工具调用，难度大幅提高。

### Speculation 的独立权限

推测执行（Speculation）是另一个有趣的场景：在用户还没输入时，Claude Code 预测下一步操作并提前执行。这里写操作不落盘到真实文件系统，而是重定向到 overlay 目录（`/tmp/speculation/{pid}/{id}`）。用户确认后才从 overlay 复制回来。Bash 只允许只读命令。

用起来的感受是：Auto Mode 下有些操作秒过（规则表直接 allow），有些等半秒（进了分类器）。如果某个操作老是需要确认，可以在 `.claude/settings.json` 里加 allow 规则让它走第一层直接通过。但别加 `Bash(*)`——进 Auto Mode 时会被自动清理掉。

---

## 机制五：Context 管理——长对话不翻车的秘密

对话越来越长，token 逼近上下文窗口上限，怎么办？Claude Code 的 auto-compact 机制比"做个摘要"复杂得多。

**触发条件**不是一个固定百分比。源码中的追踪策略是：至少 10000 tokens 才开始追踪，两次更新间至少 5000 tokens 或 3 次工具调用。当 token 估算达到阈值时触发压缩。

**Compact 的三步走**：

第一步，生成对话摘要。独立的 API 调用，`maxTurns: 1`，禁止使用任何工具（防止摘要过程中产生副作用），专门的输出 token 上限。

第二步，主动恢复关键上下文。这是核心亮点——不是只丢一段摘要就完事。压缩后自动恢复：

- 最近读取的 5 个文件（每个最多 5000 tokens，总量上限 50000 tokens）
- 当前 plan 文件引用
- 已调用的 skills
- 子 agent 任务状态

第三步，替换消息数组。旧消息被摘要替代，恢复的附件注入新的消息序列。

这背后的设计哲学是 **状态外部化优于全塞上下文**。重要信息存在文件里（CLAUDE.md、plan、memory），compact 后通过恢复机制重新注入。这比试图把所有东西都塞进 messages 可靠得多——因为你无法控制摘要会丢掉什么，但你可以控制哪些文件一定被恢复。

所以长任务中 Claude Code "忘记"之前讨论的内容，不是 bug，是 compact 的信息损失。应对策略很明确：把关键决策写进 plan 文件或 CLAUDE.md，compact 后会自动恢复。不要只在对话里说"记住这个"——让它写到文件里。

---

## 机制六：多 Agent 协作——从单兵到团队

一个 Agent 搞不定复杂任务时，需要分工。Claude Code 的多 Agent 系统在隔离性和协作效率之间做了权衡。

**Agent ID 格式是 `name@team`**，如 `researcher@my-team`。带时间戳的变体确保唯一性。每个 Agent 有独立的上下文和压缩逻辑——子 Agent 的上下文膨胀不会污染主 Agent。

**Worktree 隔离是可选的**。通过 `EnterWorktree` 工具创建 Git worktree，子 Agent 在独立分支操作。worktree 自动清理——如果子 Agent 没做任何改动，worktree 直接删除；有改动的话，返回 worktree 路径和分支名给主 Agent。

**后台 Agent 有独立的生命周期管理**。每 30 秒为长运行的 Agent 生成摘要（至少 3 条消息才触发）。完成时以 `tool_result` 通知主 Agent。这个摘要机制确保主 Agent 不需要轮询子 Agent 状态——异步通知，需要时查看摘要。

**嵌套防护**：子 Agent 有深度限制，不能再生成子 Agent。`maxTurns` 按场景配置，子 Agent 有独立上限。

UI 层面，每个子 Agent 分配不同颜色以直观区分输出来源。

Agent Team 有时比单 Agent 慢，原因不只是协调开销——每个子 Agent 有独立的上下文和压缩，启动本身就需要构建完整的 system prompt。对于小任务，直接在主 Agent 里做更高效。Worktree 模式的真正价值在并行开发不同功能时：多个 Agent 各自在独立分支操作，互不干扰，完成后各自提交。

---

## 设计启示：从 Claude Code 架构中提炼的工程思路

回看这六个机制，有几个贯穿始终的设计决策值得单独拎出来。

**一个 runner 跑所有场景**。Claude Code 没有为主循环、compact、speculation 各写一套执行逻辑，而是用同一个 fork runner 通过参数区分。这让新增子流程的成本很低——不用改核心循环，fork 一个新场景就行。如果你在构建自己的 Agent，考虑把"执行一段对话"抽象为可复用的原语，而不是每种场景写 if-else。

**给工具加约束，而不是给模型加提示**。Edit 的唯一性检查、Bash 的 120 秒超时和 20MB 截断、权限的三层分级——这些都是硬约束，不是"请你不要做危险的事"这种软提示。硬约束不依赖模型的"理解"，在工程层面就能兜底。

**重要信息存文件，别只放上下文里**。compact 的文件恢复机制证明了这一点：对话历史会被压缩、会丢信息，但 plan 文件、CLAUDE.md、memory 文件不会。Prompt Caching 的分段设计也是同一个思路——把不变的东西固化下来，让变化的部分不影响稳定的部分。

---

## 从用户到共建者

理解 Claude Code 的架构不是为了自己造轮子——虽然 IceBearMiner 证明了这是可行的。更实际的价值在于：你能更好地使用和扩展它。

CLAUDE.md、Hooks、Skills、MCP——这些扩展点都建立在上述架构之上。当你理解了 `maxTurns` 护栏，你就知道什么时候该把大任务拆成子任务。当你理解了 Prompt Caching 的前缀匹配和动态边界，你就知道 CLAUDE.md 在动态边界之后，改它不会击穿 tools 和 system 静态段的缓存，但会导致该位置之后的缓存需要重建。当你理解了 compact 的恢复机制，你就知道关键信息要写进文件而不是只在对话里说。

SWE-bench Pro 的数据印证了这一点：同一个 Claude Opus 模型，在 Augment Code 的 scaffold 下比在 SWE-Agent 下多解了约 17 题（731 题总量），差距完全来自 Agent 架构而非模型本身。架构决定上限，模型只是其中一个变量。

如果你想进一步探索，Claude Code 的 npm 包源码（`@anthropic-ai/claude-code`）虽然是打包混淆的，但核心逻辑可以反混淆阅读。GitHub 上的 `anthropics/claude-code` 仓库包含 plugins、examples 和 workflows。社区项目 `shareAI-lab/learn-claude-code` 提供了从零构建类似 Agent 的教学代码。

---

## 参考来源

- [Anthropic - Building Effective Agents (2024.12)](https://www.anthropic.com/research/building-effective-agents)

- [Anthropic - Effective Context Engineering for AI Agents (2025.09)](https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents)

- [Anthropic - Effective Harnesses for Long-Running Agents (2025.11)](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents)

- [Anthropic - Auto Mode for Claude Code (2026.03)](https://www.anthropic.com/engineering/claude-code-auto-mode)

- [Anthropic - Prompt Caching 官方文档](https://platform.claude.com/docs/en/build-with-claude/prompt-caching)

- [Anthropic - Permission Modes](https://code.claude.com/docs/en/permission-modes)

- [Tony Bai - 拆解 Claude Code：Coding Agent 背后的架构真相 (2026.01)](https://tonybai.com/2026/01/08/how-claude-code-works/)

- [PromptLayer - Claude Code: Behind the Scenes of the Master Agent Loop](https://blog.promptlayer.com/claude-code-behind-the-scenes-of-the-master-agent-loop/)

- [Jannes Klaas - Agent Design Lessons from Claude Code](https://jannesklaas.github.io/ai/2025/07/20/claude-code-agent-design.html)

- [vrungta - Claude Code Architecture (Reverse Engineered)](https://vrungta.substack.com/p/claude-code-architecture-reverse)

- [Augment Code - Auggie tops SWE-Bench Pro](https://www.augmentcode.com/blog/auggie-tops-swe-bench-pro)

- [Pragmatic Engineer - AI Tooling for Software Engineers in 2026](https://newsletter.pragmaticengineer.com/p/ai-tooling-2026)

- [harness-engineering.ai - The Complete Guide to Agent Harness](https://harness-engineering.ai/blog/agent-harness-complete-guide/)

- [Epsilla - The Third Evolution: Harness Engineering](https://www.epsilla.com/blogs/harness-engineering-evolution-prompt-context-autonomous-agents)

---

## 相关阅读

- [从一条推文读懂 Claude Code：工具设计原理与实战指南](/posts/claude-code-tool-design-philosophy/) — 本文拆解了工具系统的源码实现，那篇从使用者角度讲工具设计哲学

- [拆解 Anthropic 工程博客：怎么让 Claude 连续跑 6 小时还不翻车](/posts/anthropic-multi-agent-harness-design/) — Harness Engineering 的官方博客解读，本文是源码级验证

- [Claude Code 安全三道防线：从权限模式到 Hook 兜底的纵深防护实战](/posts/claude-code-safety-three-defenses/) — 权限系统的实战配置指南，本文深入到分类器内部机制
