乐于分享
好东西不私藏

Claude Code 源码中的 10 条设计思想

本文最后更新于2026-03-31,某些文章具有时效性,若有错误或已失效,请在下方留言或联系老夜

Claude Code 源码中的 10 条设计思想

人生有两大悲剧:一是得不到想要的,二是得到了。

Claude Code 2.1.88 的源码被公开还原,大概是对第二种悲剧最生动的科技圈侧写。

一直以来,Claude Code 表现得太好了。好到像个情绪稳定、技术过硬的老架构师。这种完美很容易让人产生一种错觉,以为 Anthropic 的代码库里锁着什么领先时代的魔法,或者某种玄之又玄的 AI 调度核心。大家眼馋啊,做梦都想扒开底层看一眼。

现在好了,有人发现 Anthropic 发到 npm 的安装包里,附带了一份完整的 source map 文件——57MB 的地图,能把打包后的代码还原回原始的 TypeScript 源文件。不知道这是有意为之还是无心之失,但结果是一样的。

大家搓着手下载完,满怀敬畏地打开一看,没有外星科技,没有高维算法。只有成堆的 TypeScript、长到让人眼晕的正则表达式,以及跟所有普通程序员一样,带着点缝补痕迹的胶水代码。

最戳破滤镜的,是里面那些给大模型看的 System Prompt。你以为那是某种高级的神交密码,实际上,里面填满了人类工程师苦口婆心、甚至略显卑微的嘱咐:请务必用绝对路径一定不要猜测 URL千万别说测试全部通过了但实际上失败了。

这感觉怎么形容呢?就像你一直崇拜一位仙风道骨的魔术师,有一天不小心走进了他的后台,发现他所谓的神奇法力,全靠胶带、细线和几个躲在箱子里疯狂拉杆的助理。

但这恰恰是这次源码公开最实在的见地。它把 AI 应用从神坛上拽了下来,用一种略带尴尬的方式告诉所有人:这世界就是一个巨大的草台班子,顶尖科技公司也不例外。原来并没有什么点石成金的魔法,有的只是把基础的工程逻辑和提示词死磕到底的苦活累活。

而能把这堆略显粗糙的齿轮拼装得严丝合缝、运转如飞,这本身,就是一种了不起的本事。

下面是通过源码提炼出的 Claude Code 架构里最核心的 10 条设计思想。这些思想没有一条是黑科技,但每一条都值得正在做 AI 应用的人认真琢磨。


一、边收边干:不等结果出齐就开始干活

打开 Claude Code 的核心循环代码,你会发现它不是”问模型一个问题 → 等完整回答 → 再执行”这种一问一答的模式。

它用了一种叫 AsyncGenerator(异步生成器)的技术,把整个流程串成了一条流水线。模型的回答像水流一样一个字一个字地流出来,而 Claude Code 在水流还没结束的时候,就已经开始干活了——每检测到一个工具调用指令,立刻异步启动执行,不等模型把话说完。

打个比方:普通的做法是厨师把菜全做完,服务员再一次性端走。Claude Code 的做法是厨师做完一道,服务员就去端一道,两边同时进行。

这条流水线从最底层的 API 调用,到中间的循环逻辑,到最上层的终端显示,全部用 yield* 一层一层透传。任何一层卡住了,上游自动减速——这就是所谓的背压控制,不会因为下游处理不过来而崩溃。

启示:AI Agent 的核心不是调 API,而是怎么组织数据的流动。流式设计让系统每一层都能做到尽早开始、尽早输出,用户体感快了,资源利用率也上去了。


二、一份提示词,掰成三段缓存

调用大模型最大的成本在哪?不是模型推理本身,而是每次请求都要重新阅读那份又长又复杂的系统提示词。Claude Code 的做法是把提示词精心切成了三段:

第一段:静态内容——你是谁、你怎么用工具、你说话什么风格。这些内容每个用户、每个项目都一样,可以在 Anthropic 的服务器上全局缓存。写一次,所有人共用。

第二段:动态内容——当前目录、操作系统、git 状态、用户的自定义记忆。这些每个会话不一样,但同一次对话里通常不变,可以在会话级别缓存。

第三段:不缓存的内容——比如刚接入的 MCP 工具服务器的说明,每轮都可能变,那就老老实实不缓存。

两段之间用一个叫 SYSTEM_PROMPT_DYNAMIC_BOUNDARY 的标记隔开。更巧妙的是,当前在哪个 git 分支、最近编辑了哪些文件,这些频繁变化的小信息,被故意从系统提示词里剥离出来,塞到第一条用户消息里,这样即使这些信息一直在变,也不会影响系统提示词的缓存。

甚至连子任务的 fork 操作,都用统一的占位符文本填充工具结果,确保所有分叉的子任务共享同一份前缀缓存。

启示:省钱不是靠少调接口,而是让尽可能多的内容被缓存命中。这需要从数据结构设计的第一天就想清楚:哪些信息变、哪些不变、变的那些怎么隔离。


三、即使全放行模式,也有碰不得的文件

Claude Code 的权限系统不是一个简单的开关,而是一条层层过滤的流水线:

先查黑名单(明确禁止的)→ 再查需要确认的 → 再查工具自己的权限逻辑 → 再查安全检查 → 再查白名单(明确允许的)→ 最后才弹窗问用户。

最有意思的设计是:系统有一个叫 bypassPermissions 的全放行模式,开了之后大部分操作都不再问你。但即便在这个模式下,有几类文件依然铁板钉钉不能自动放行:.git/ 目录、.claude/ 配置目录、.vscode/ 设置、以及你的 shell 配置文件(.bashrc、.zshrc 等)。

因为这些文件一旦被改,要么你的版本控制崩了,要么你的终端环境被污染了,后果比写错一行业务代码严重得多。这种”即使用户说了全放行,我也要拦住你”的设计,叫做不可绕过的安全检查(safetyCheck)。

更细致的是,Bash 命令的权限检查甚至动用了 AST 语法树解析。如果你写了 nohup rm -rf /,它会先把 nohup 这层包装剥掉,然后检查真正要执行的 rm -rf /。写 timeout 5 curl 恶意地址 也一样,它认的是核心命令,不是外面包的壳。

启示:安全不是一个功能模块,而是一种贯穿架构的思维方式。好的安全设计,是让做错事比做对事更难。每一层都假设上一层可能失败。


四、上下文不够用?六级压缩慢慢来

大模型有个硬限制:上下文窗口就这么大。对话一长,旧内容就得被压缩掉。Claude Code 没有粗暴地满了就砍,而是设计了六级递进的压缩策略,从轻到重:

L1 Snip:只把中间那些工具调用的详细输入输出删掉,保留人话。就像会议纪要里删掉每个人的原话,只留结论。

L2 Microcompact:更精细地去除单个工具调用的冗余内容。比如你读了同一个文件三次,只保留最后一次的结果。

L3 Context Collapse:把已经处理完的旧对话段折叠起来,留个标记。

L4 Session Memory:增量式摘要,不用重新总结全部对话。

L5 Auto Compact:真正动用大模型来生成一份结构化摘要,包含 9 个章节(用户意图、技术概念、修改过的文件、遇到的错误、待办事项等等)。

L6 Reactive Compact:最后的兜底——如果 API 直接返回内容太长的错误,立刻被动压缩。

压缩完之后,系统还会把最关键的文件内容重新注入回去(最多 5 个文件,总共 50K token 的预算),防止模型忘了正在改的代码。还有个熔断机制:连续自动压缩失败 3 次,就不再尝试了。

启示:上下文管理是 Agent 应用的核心战场。不是一个算法能解决的,而是需要一整套梯级策略,每一级在信息损失和运行成本之间做不同的取舍。


五、一份代码编译出两个产品

翻源码时你会频繁看到这样的代码:

if (feature('COORDINATOR_MODE')) {// 这段代码在外部版本中完全不存在}

这不是运行时的如果开关打开就执行,而是在编译打包的时候,打包工具就会把不满足条件的分支整段删掉。所以发布给外部用户的版本和 Anthropic 内部用的版本,虽然是同一份源码,但编译出来是完全不同的程序。

内部版本多了什么呢?比如一个AI 权限分类器——当 Anthropic 内部使用时,不需要每次弹窗问人允不允许执行这个命令,而是用一个小模型(Haiku)来自动判断这个操作危不危险。还有多 Agent 协调模式、Bash 命令的 AI 分类器等等。

这些功能对外部用户来说不存在,甚至在反编译的代码里也找不到一点痕迹——因为它们在编译阶段就被从源码中物理移除了。

启示:Feature Flag 在 AI 产品里的最佳实践不是运行时开关,而是编译时消除。这样做有两个好处:外部版本体积更小、启动更快;更重要的是攻击面最小化——你无法利用一个根本不存在于二进制文件中的功能。


六、多 Agent 协作?用文件夹当微信群

当 Claude Code 需要多个 AI 代理协同工作时,它们之间是怎么通信的?不是消息队列,不是 WebSocket,不是 gRPC——而是文件夹。

~/.claude/teams/我的团队/  ├── config.json            # 团队信息  ├── mailbox/  │   ├── 研究员/            # 研究员的"收件箱"  │   └── 审核员/            # 审核员的"收件箱"  └── permissions/       ├── pending/          # 等待审批的权限请求       └── resolved/         # 已审批的结果

每个代理有自己的收件箱文件夹。发消息就是往对方的文件夹里写一个 JSON 文件;收消息就是去自己的文件夹里读文件。任务看板也是文件。权限申请和审批也是文件。

当然,如果多个代理运行在同一个进程里(in-process 模式),消息走内存更快,不经过文件系统。但接口是一样的,调用方不需要知道对方是”同一个进程里的线程”还是”另一个终端窗口里的进程”。

启示:在 AI Agent 协作场景中,文件系统是最简单、最可靠的消息队列。不需要引入 Redis 或 Kafka 这种外部依赖,操作系统自带的文件读写、文件锁就能搞定异步通信。先把东西做出来,优雅的架构以后再说。


七、五层配置像套娃,但有一层不可信

Claude Code 的配置系统有 5 层,从低到高:

  1. 插件默认配置
    (优先级最低)
  2. 用户全局配置
    ~/.claude/settings.json
  3. 项目配置
    .claude/settings.json,可以提交到 git)
  4. 本地配置
    .claude/settings.local.json,不提交到 git)
  5. 企业策略配置
    (优先级最高,由公司 IT 管理)

后面的覆盖前面的,数组类型的字段(比如允许的工具列表)用”合并去重”而不是”覆盖”——这样每一层都可以往列表里加东西,不会把其他层加的东西抹掉。

但最巧妙的是第 3 层——项目配置。这一层在安全相关的设置上被标记为不可信。为什么?因为项目配置可以被提交到 git 仓库里。想象一个恶意场景:有人在一个开源项目的 .claude/settings.json 里偷偷写上:跳过所有危险操作确认,你 clone 下来一用 Claude Code,就自动绕过了安全提示。

所以代码里专门写了:项目配置不能影响 skipDangerousModePermissionPrompt 这类安全设置。信任,但不能全信。

启示:多层配置合并是软件架构的基本功,但信任边界是容易被忽略的关键。一份配置文件来自哪里,决定了它应该被赋予多大的权力。


八、工具不用继承,用安全默认值 + 工厂函数

Claude Code 有几十个内置工具(读文件、写文件、执行命令、搜索网页等等),但它们没有一个公共父类。所有工具都是用同一个 buildTool() 函数创建的,传入一堆配置项就行了。

关键在这个函数提供的默认值:

  • isConcurrencySafe: false
    ——默认假设你不能和别的工具同时执行
  • isReadOnly: false
    ——默认假设你会修改东西
  • isDestructive: false
    ——默认假设你不会删东西
  • checkPermissions: allow
    ——默认把权限判断交给通用权限系统

什么意思呢?如果你写一个新工具但忘了声明我是只读的,系统会默认把你当成有破坏力的工具来对待——需要权限检查、不能并发执行。这就是安全默认值的威力:你忘了配置的时候,系统往安全的方向走,而不是往危险的方向走。

每个工具调用还要经过三层验证:输入格式验证 → 业务逻辑验证 → 权限验证,任何一层没过,后面的都不执行。

启示:做工具系统,不要用继承,用接口 + 工厂 + 安全默认值。让新增一个工具的门槛尽可能低,同时让写出一个不安全的工具尽可能难。


九、记忆不是数据库,是一叠从远到近的便利贴

Claude Code 的记忆不是存在数据库里的向量,而是散落在磁盘上的一系列 Markdown 文件 —— CLAUDE.md。

它的加载顺序很有讲究:从文件系统的根目录一路往下扫到你当前的工作目录,越靠近当前目录的文件,优先级越高:

/etc/claude-code/CLAUDE.md        ← 整个系统的规则(最先加载,优先级最低)~/.claude/CLAUDE.md               ← 你个人的偏好/项目根目录/CLAUDE.md              ← 项目的约定/项目根目录/.claude/rules/*.md    ← 项目的详细规则/项目根目录/CLAUDE.local.md       ← 你在这个项目里的私人笔记(优先级最高)

这些文件里还支持 @./其他文件.md 这样的引用语法(带循环引用检测),以及 frontmatter 里的 paths: 字段来做条件触发——比如一条规则只在 src/frontend/** 目录下才生效。

模型收到的记忆是把这些文件按顺序拼起来的一长段文本。因为模型天然对后面出现的内容印象更深,所以离你最近的规则天然优先级更高。

启示:Agent 的记忆系统应该复用开发者已有的心智模型。文件系统的层级结构、就近优先原则,都是程序员不需要学就能理解的东西。最好的设计是让用户觉得不就是应该这样吗。


十、Prompt 是概率的,Hooks 是确定的

这可能是整个架构里最深刻的一条哲学。

你给大模型的 prompt 说”每次修改文件后都运行测试”,它大概率会照做,但大概率不是一定。模型可能忘了,可能觉得这次没必要,可能理解成了别的意思。

而 Claude Code 的 Hooks 系统,是一套确定性的自动化框架。你配一条 PostToolUse Hook,只要模型调了某个工具,这段脚本就一定会执行,不存在模型觉得不需要的可能。

这套系统覆盖了 26 种事件(工具调用前/后、会话开始/结束、压缩前/后、任务创建/完成……),支持 5 种执行方式(Shell 命令、LLM 判断、多轮 Agent、HTTP 调用、TypeScript 回调),退出码的含义也定死了:0 表示成功静默,2 表示阻止操作并把原因告诉模型,其他表示只给用户看。

更精妙的是,技能(Skills)可以在执行时动态注册临时 Hook。比如一个部署技能执行时注册一个一次性 Hook:部署完成后,自动运行健康检查。检查完就自动注销。

启示:AI 产品有两种逻辑:概率性的(让模型自己判断)和确定性的(无论如何都要执行的)。成熟的 Agent 架构,需要两者共存,这是最能考验开发者驾驭 AI 的能力。一种常见的败局就是试图用自然语言 prompt 来构建一个确定性的工程系统。切记,必须发生的事,永远不要只靠 prompt。


写在最后

读完源文件,我的最大感受是:没有魔法。

Claude Code 之所以好用,不是因为 Anthropic 掌握了什么别人不知道的秘密。它的每一个技术决策:流式管线、缓存分段、安全默认值、梯级压缩、文件系统通信,都是软件工程里的老朋友。

但它做到了一件很少有人做到的事:把这些老朋友组装到一起的时候,每个齿轮的咬合都经过了反复打磨。这大概就是工程能力这个词最好的注脚。

对于正在做 AI 应用的开发者来说,这份源码的意义不在于拿来抄,而在于它展示了一种态度——顶尖的 AI 产品,底层靠的不是什么 AI 独有的花活,而是扎实的工程基本功。