Published on

Claude Code 源码深度拆解:工具系统、多Agent编排与上下文压缩的设计哲学

Authors

为什么要研究 Claude Code 的源码

2025 年 2 月,Anthropic 以开源形式发布了 Claude Code,一个终端原生的 AI 编程助手。作为一个日常重度使用 AI Coding 工具的开发者,我在体验了数百次协作编程后产生了一个问题:一个生产级的 AI Agent 系统,到底应该如何工程化地解决工具安全、上下文管理和多 Agent 协作这三大难题?

与其猜测,不如直接看代码。

经过对 Claude Code 源码的系统性阅读,我提取了三个核心子系统进行深度分析。本文不是 API 文档的翻译,而是一个工程视角的设计拆解——重点关注"为什么这样设计"而非"它做了什么"。


一、工具系统:36 个工具的统一契约与安全防御

Claude Code 内置了 35+ 个工具,涵盖文件操作、命令执行、代码搜索、Agent 管理、网络访问、MCP 协议等类别。但真正值得研究的是工具的注册框架和安全体系

1.1 buildTool():统一工具契约

所有工具通过 buildTool() 工厂函数构建,位于 src/Tool.ts。这个函数定义了一个完整的工具生命周期契约:

export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D>

每个工具定义包含:inputSchema(Zod Schema 验证)、validateInput(同步验证)、checkPermissions(权限检查)、call(执行)、prompt(注入 system prompt 的使用说明)、description(动态描述)等。

这个设计的精妙之处在于验证与权限的分离validateInput 失败直接返回错误,而 checkPermissions 可以返回 allow/ask/deny/passthrough,其中 ask 会弹窗等待用户决策。这种分离让"格式错误"和"权限不足"的处理路径完全独立。

另一个亮点是延迟加载。当工具数量膨胀时,不可能全部注入 system prompt(会浪费 token)。Claude Code 的方案是:工具可标记 shouldDefer: true,不注入 prompt;模型通过 ToolSearch 工具按需搜索和激活,支持精确选择和模糊匹配。

1.2 BashTool:7000 行代码构建的 Shell 安全堡垒

BashTool 是整个系统中架构最复杂的工具,拆分为 13 个子模块。它的安全体系是四层纵深防御

命令输入 → AST 解析(Tree-sitter)→ 语义检查 → 权限分类器 → 沙箱执行

AST 级安全检查是最令人印象深刻的一层。它使用 Tree-sitter 解析 Shell AST,在语法树层面而非字符串层面检测注入攻击。共定义了 23 种安全检查 ID,覆盖命令替换注入($()、反引号)、进程替换(<()>())、Zsh 模块加载攻击(zmodloadzptyztcp)、IFS 注入、花括号展开逃逸等攻击向量。

权限分类器自动将命令分为 allow/ask/deny 三类,支持通配符规则(如 Bash(git commit*))。还有并发限制——复合命令的子命令数量上限为 50,防止 ReDoS 导致事件循环饥饿。

沙箱系统提供文件系统白名单/黑名单、网络限制和 Unix Socket 控制。甚至 sed -i 都被特殊处理——Claude Code 会引导模型使用 FileEditTool 而非 sed 进行文件编辑。

1.3 FileEditTool:精确字符串替换的工程哲学

FileEditTool 的核心设计决策是:使用精确字符串匹配替换,而非 diff/patch 格式

{
  file_path, old_string, new_string, replace_all
}

这个选择对 LLM 非常友好——模型不需要理解 diff 格式的 +/- 行号语义,只需要提供"要替换的原文"和"替换后的文本"。同时它内置了多重安全检查:

  • 读取前置检查:必须先通过 Read 工具读取过文件,readFileState 跟踪读取状态和时间戳,防止盲目编辑未读文件
  • 文件变更检测:比较 lastWriteTime,防止覆盖外部修改
  • 唯一性检查replace_all=false 时,old_string 必须在文件中唯一
  • 引号风格保留preserveQuoteStyle() 在替换时保持文件原有引号风格,避免引入不必要的 diff
  • 1GiB 文件大小限制:防止 OOM

1.4 MCPTool:薄代理层设计

MCP 工具的实现极其简洁——一个 passthrough() 的 open schema,名称、描述和 schema 都在连接 MCP 服务器后动态注入。这种"薄代理层"设计将复杂性推到了 MCP 协议层,Claude Code 自身只需处理权限透传和进度报告。


二、多 Agent 编排:Coordinator 的 Prompt 工程与任务生命周期

2.1 Coordinator:用 Prompt 而非代码做编排

这是 Claude Code 多 Agent 系统最反直觉的设计——Coordinator 不是代码层面的编排框架,而是一段精心设计的系统 prompt

Coordinator 是一个不直接执行工具操作的"管理者" Agent,它的职责是:分解任务为研究/实现/验证阶段、编排并行 Worker、综合结果并与用户沟通。它可用的工具仅有 AgentTool(生成 Worker)、SendMessage(发送后续指令)、TaskStopTool(停止 Worker)和团队管理工具。

并发策略在 prompt 中明确定义:

阶段执行者并发策略
ResearchWorkers完全并行
SynthesisCoordinator串行(自身执行)
ImplementationWorkers按文件集串行
VerificationWorkers可与实现并行

这种设计的优势是灵活性极高——编排逻辑随 prompt 可调,无需代码变更。它充分利用了 LLM 的理解能力进行任务分解,而非硬编码的状态机。

2.2 七种 Task 类型与统一接口

所有任务通过联合类型统一管理:

type TaskState =
  | LocalShellTaskState // Shell 命令执行
  | LocalAgentTaskState // 本地 Agent 任务
  | RemoteAgentTaskState // 远程 Agent 任务
  | InProcessTeammateTaskState // 进程内队友(Swarm 模式)
  | LocalWorkflowTaskState // 工作流任务
  | MonitorMcpTaskState // MCP 监控任务
  | DreamTaskState // 自动规划(记忆整合)

每种任务都实现相同的 Task 接口(nametypekill),UI 层完全不需要关心具体类型。

2.3 XML 通知协议与前台/后台双模式

任务完成时,结果以 XML 格式注入主会话消息队列:

<task-notification>
  <task-id>agent-a1b2</task-id>
  <status>completed</status>
  <summary>Agent "xxx" completed</summary>
  <usage><total_tokens>N</total_tokens></usage>
</task-notification>

这使 Coordinator 能像处理用户消息一样处理任务结果,保持了消息流的统一性。使用原子性 notified 标志防止重复通知。

任务支持前台/后台无缝切换。长时间运行的任务在超过 120 秒后自动转后台,用户也可通过 Ctrl+B 一键后台化。Shell 任务还有Stall Watchdog——每 5 秒检查输出增长,45 秒无增长且匹配交互提示模式(如 (y/n))时通知用户。

2.4 Swarm 模式的隔离机制

进程内队友通过 agentName@teamName 格式标识身份,采用双重隔离:

  • AsyncLocalStorage 上下文隔离:每个 Agent 有独立的异步上下文,互不干扰
  • 消息历史隔离:独立 transcript 文件,UI 只保留最近 50 条消息(全量在磁盘上)
  • AbortController 级联:父 Agent 取消时自动取消所有子 Agent,无孤儿进程

Worker 也有工具白名单限制——不能创建/删除团队、不能发送消息、不能使用内部合成输出工具。


三、上下文压缩:三层渐进策略与 Cache Editing API

这是整个系统中最精巧的子系统。Claude Code 采用三层渐进式压缩,从零成本到高成本逐级升级:

层级机制Token 节省
Microcompact缓存编辑/内容清除~10-40K
Session Memory Compaction剪裁旧消息 + 注入笔记~60-80K
Full CompactLLM 摘要~90%+

3.1 Microcompact:不破坏缓存的增量清理

Microcompact 有两种触发模式。缓存编辑模式利用 Anthropic API 的 cache_edits 功能,删除指定 tool_use_id 的缓存内容——关键是不修改本地消息,仅通过 API 层操作,因此不会破坏已缓存的 prompt 前缀。按先进先出策略删除最旧的工具结果,仅限主线程防止子 agent 污染全局状态。

时间触发模式更巧妙:当距上次 assistant 消息超过 10 分钟时,服务端缓存已过期,此时直接修改本地消息,将旧工具结果替换为 [Old tool result content cleared]

3.2 Session Memory 作为压缩替代

当 token 接近 autocompact 阈值时,Claude Code 优先尝试 Session Memory 压缩而非完整 LLM 摘要。它检查是否有实质性的会话笔记,如果有,剪裁旧消息并将笔记内容作为摘要注入。

这个设计非常聪明——用已有的笔记替代一次 LLM 调用,几乎零延迟,省时省钱。

3.3 Full Compact 的容错设计

完整压缩使用 Forked Agent(共享 prompt cache)生成结构化摘要,包含 9 个 section:用户请求、技术概念、文件和代码、错误修复、问题解决、所有用户消息、待办任务、当前工作和下一步。

两个工程细节值得关注:

熔断器机制:连续 3 次自动压缩失败后停止尝试。这源于一个真实的生产事故——曾有会话连续失败 3272 次,造成大量 API 浪费。

PTL 重试:当压缩请求本身超过上下文窗口时,按 API 轮次分组,从最旧组开始丢弃,最多重试 3 次。根据 tokenGap(超限量)智能计算需丢弃的组数。

3.4 API 级别上下文管理

Anthropic API 原生支持 cache_edits,Claude Code 配置了两层策略:

  • 清除工具结果:input tokens 达 180K 时触发,目标保留最近 40K
  • 清除 thinking 块:超过 1 小时不活跃只保留最后一轮

四、记忆系统:四类分类法与跨会话持久化

4.1 四类记忆分类

Claude Code 将跨会话记忆严格分为四类:

  1. user — 用户偏好、角色、工作风格
  2. feedback — 用户对输出的反馈(正面/负面)
  3. project — 项目架构、决策、约定
  4. reference — 重要的外部参考信息

关键设计决策是明确排除可从代码/文件系统推导的信息(代码模式、架构、git 历史),避免记忆膨胀。

记忆文件格式使用 YAML frontmatter:

---
name: user_prefers_verbose_logging
description: User wants detailed logging in development
type: user
created: 2026-04-01
---

具体记忆内容...

存储在 ~/.claude/projects/<path-slug>/memory/ 下,按项目隔离MEMORY.md 作为索引文件,在每次对话启动时注入 system prompt,同时有 200 行和 25KB 的双重截断限制。

4.2 SessionMemory:会话级持久化笔记

SessionMemory 是当前会话的"实时笔记本",在后台周期性更新,不阻塞主对话。它通过 Forked Agent(共享 prompt cache)运行,工具限制为只能对 memory 文件使用 Edit 工具。

触发条件是双重阈值:上下文增长量超过 minimumTokensBetweenUpdate 工具调用次数 >= toolCallsBetweenUpdates。记忆模板包含 10 个 section(标题、当前状态、任务规格、文件和函数、工作流、错误修复、文档、经验、关键结果、工作日志),每个 section 有 2000 token 上限,总计 12000 token。

4.3 ExtractMemories:跨会话记忆提取

在每个完整查询循环结束时(模型产生无工具调用的最终响应后),Claude Code 通过 Forked Agent 从当前会话中提取可跨会话复用的记忆。

互斥检测是亮点:当主 Agent 自己已经写了记忆文件时,跳过 forked 提取,避免重复工作。还有Trailing Run机制——运行中的提取完成后,处理积压的最新消息上下文。

4.4 AutoDream:跨会话记忆整合

AutoDream 定期(默认 24 小时)回顾多个会话的 transcript,整合和优化记忆文件。三重门控:时间门(>=24h)、会话门(>=5 个会话更新)、锁门(无其他进程正在整合)。通过文件 mtime 实现跨进程锁,失败时回滚 lock 的 mtime。


五、设计亮点总结

值得借鉴的模式

1. 精确字符串替换优于 diff/patch FileEditTool 证明了对 LLM 友好的设计应该以模型的理解方式为准,而非人类传统工具的习惯。

2. Prompt 驱动编排 Coordinator 用一段精心设计的 prompt 实现了复杂的多 Agent 编排,比硬编码状态机更灵活、更易迭代。

3. 三层渐进压缩 从缓存编辑到笔记注入到 LLM 摘要,每一层的成本递增但效果也递增。先尝试最便宜的方案,只在必要时升级。

4. Forked Agent 共享 Prompt Cache 子任务继承主对话上下文以共享缓存前缀,大幅降低 token 成本。这是 Anthropic API 特性的工程化利用。

5. 熔断器模式 任何可能陷入循环的自动重试系统都应该有熔断器。3272 次连续失败的教训值得铭记。

6. 读取前置检查 "你读过才能改"的安全模式简单但有效,防止了 LLM 盲目编辑文件的常见问题。

7. 统一 Task 接口 7 种任务类型实现相同接口,UI 层完全解耦。这是经典的策略模式在 Agent 系统中的应用。

8. 记忆分类的排除原则 明确排除可推导信息,避免记忆膨胀。这个原则适用于任何需要持久化"知识"的系统。

工程启示

Claude Code 的源码揭示了一个重要趋势:AI Agent 系统的工程质量,很大程度上取决于非 AI 部分。7000 行的 Shell 安全检查、精确的文件编辑策略、三层渐进压缩的工程实现——这些都不是 LLM 能力本身,而是传统软件工程的深度应用。

换句话说,构建一个生产级 AI Agent 系统,80% 的工作是做好"AI 之外的工程"。


结语

Claude Code 是目前公开的最完整的 AI Agent 工程实现之一。它的工具安全体系、多 Agent 编排机制和上下文管理策略,为整个行业提供了一个可参考的工程范式。

当然,它并非完美。Coordinator 纯 prompt 编排在极端场景下的可靠性仍有待验证;记忆系统的四类分类法是否足以覆盖所有场景也值得探讨。但这些都不影响它作为"AI Agent 工程化教科书"的价值。

如果你正在构建自己的 AI Agent 系统,强烈建议花时间阅读 Claude Code 的源码。不是因为它完美,而是因为它是目前我们能看到的、最诚实的工程实践。