Claude Code 源码揭秘:拆完 320+ 文件后,我总结的 11条Agent 设计心法
💡 阅读前记得关注+星标,及时获取更新推送
「Claude Code 源码揭秘」系列的最后一篇,上一篇《Claude Code 源码揭秘:用 React 写终端界面,Ink 框架到底有多香》。这 18 篇拆解,前面 17 篇是”术”,今天这篇收尾,聊聊”道”。从 48 个子目录、320 多个源文件里,我提炼出这些核心心法。不是什么理论框架,就是从源码里读出来的实战经验。

Claude Code 是我用得最多的编程智能体。但说”编程智能体”其实已经不太准确了。现在它帮我写文章、做数据分析、整理资料、搞技术调研、甚至管理我的知识库——对我来说,它早就不是一个编程工具,而是我的个人 AI 助手。每天的工作流里,大大小小的事情,第一反应都是”让 Claude 来”。
正因为重度依赖它,我才更想搞明白它到底是怎么工作的。
学习 Claude Code 源码的意义不在于炫技,不是说”看,我把它的代码都读懂了”。真正的意义在于:当你理解了一个工具的底层逻辑,你才能真正驾驭它,把它的潜能发挥出来。就像你开车,知道发动机的基本原理,遇到异响你就能判断是不是大问题,而不是每次都得去4S店。
还有一层意义更长远——当你想开发自己的 Agent 的时候,Claude Code 就是目前市面上最好的学习材料。没有之一。这不是一个实验室里的 demo,是年化营收 25 亿美金、发布不到一年就成为 Anthropic 核心收入引擎的生产系统。之前我翻译过一篇 Pragmatic Engineer 的文章《Claude Code 是如何构建的》,里面提到 Claude Code 90% 的代码是它自己写的,团队每个工程师每天发布约 5 个版本——这种工程效率本身就说明了这套架构设计的合理性。
简单循环可能就够用了
这是我拆源码时最大的意外发现。
Claude Code 的核心架构就是一个 while 循环。没有 DAG 编排,没有状态机,没有复杂的工作流引擎。模型决定调什么工具,执行,拿结果,再决定。循环往复,直到任务完成。
while (turns < maxTurns) { response = await this.client.createMessage(messages, tools, systemPrompt); for (const block of response.content) { if (block.type === 'tool_use') { const result = await toolRegistry.execute(block.name, block.input); messages.push(createToolResultMessage(block.id, result)); } } if (response.stop_reason === 'end_turn') break;}
就这?就这。
所有复杂行为——多步骤任务、错误恢复、反复迭代——都是从这个循环里”涌现”出来的。循环一直跑,模型一直想,工具一直执行,直到模型自己说”行了,够了”。
我之前也做过 Agent 相关的项目,当时团队花了两周时间设计状态机,各种状态转移图画得漂漂亮亮。结果上线后,80% 的任务走的都是最简单的那条路径,复杂的分支几乎没触发过。
Claude Code 的设计思路是反过来的:先用最简单的方案,等真正遇到问题再加复杂度。现代大模型的推理能力够强,你给它一个工具列表,它自己知道该调什么、调几次、什么时候停。你费劲设计的状态机,可能还不如模型自己的判断靠谱。
不是说所有 Agent 都该用 while 循环。但在动手造复杂架构之前,先问问自己:简单方案真的不够用吗?
模型无状态,但你得有状态
Claude 在每次 API 调用之间是没有记忆的——它收到消息,生成回复,然后就”忘”了。你感受到的连续性,完全是客户端维护的 messages 数组撑起来的幻觉。
模型专注推理,客户端负责状态、持久化和编排。各管各的,互不干涉。
这意味着:你的 Agent 的记忆,完全是你的责任。工具结果、文件内容、之前的决策——都得你显式塞进去。没有隐式,只有显式。
工具本质上就是结构化输出
当 Claude 说”我要读一个文件”,它并没有真的去读文件。它只是生成了一段 JSON:
{ "type": "tool_use", "name": "Read", "input": { "file_path": "/src/main.ts" }}
客户端拿到这段 JSON,执行实际的文件读取,把结果作为消息发回去。模型从头到尾没碰过文件系统,它只是通过结构化数据”描述”了一个意图。
这个设计天然就带来了安全性。客户端可以检查、验证、拦截每一个操作。模型提议,客户端裁决。
之前在那篇《Claude Code 的设计哲学:为什么它选择回归 Unix 之道?》里我聊过,Claude Code 的整体理念就是 Unix 哲学——每个组件做好一件事,通过标准接口组合。工具系统就是这个哲学的典型体现:模型和执行环境之间的边界清晰得像管道一样。
流式传输不是锦上添花,是改变体验的关键
批量 API——等模型把整个回复生成完再一次性处理——技术上完全可行。但用户体验天差地别。
流式传输让文字随着生成实时出现,UI 始终保持响应。一个长任务,流式下是”它在一步步干活”的感觉,批量下是”它卡死了吧?”的焦虑。更重要的是,流式让中断成为可能——用户可以随时取消,出了问题可以立刻终止。
代价是实现复杂度上来了。SSE 解析、不完整 JSON 的累积拼装、并发状态更新… 我在第五篇《SSE 与状态机》里详细拆过这块。但这些复杂度换来的体验提升是质变级别的。
我之前做过一个 AI 对话产品的 POC,第一版批量返回,老板体验了一把说”这也太慢了”。其实不是慢,是没有流式,用户感知到的等待时间被放大了好几倍。后来加了流式,同样的响应速度,老板说”这个流畅”。
上下文是最宝贵的资源
Claude Code 在上下文管理上下的功夫,可能比其他任何模块都多。
200K 的上下文窗口,听起来很大,读几个大文件、跑几次搜索、积累一些工具输出,就快满了。消耗速度永远比你预期的快。
几个关键设计:
70% 就开始压缩——不是等到快满了才处理,而是留足余量。压缩本身也需要空间,等到 90% 再压,可能连压缩的空间都没有了。
提示词缓存是经济学问题——缓存命中 90% 意味着成本降低 90%,从根本上改变了 Agent 的运营经济模型。所以 Claude Code 把 system prompt 设计成尽可能稳定的,就是为了让缓存生效。
子代理隔离上下文——主代理当包工头,子代理干活,干完把摘要带回来。有些任务需要读二十个文件,让主对话做,这些内容永远留在上下文里;让子代理做,回来的只是精炼的结果。委派优于堆积。
持久化输出的清理——工具输出超过 400KB 只保留 2KB 预览,每次 API 调用前清理旧输出,只保留最近 3 个。正是这些细节让系统能跑几十轮都不爆。
对 Agent 来说,上下文窗口就是它的”内存”。每个 token 都是金子。
提示词约束比你想的靠谱
Claude Code 大量使用”提示词约束”来控制模型行为。比如文件编辑的”先读后改”规则,核心就是靠 system prompt 里的一段话:
“You must always read a file before editing it. Never edit files that have not been read in the current conversation.”
没有强制的代码校验,就是告诉模型”你必须这么做”。换作几年前我肯定会说这不靠谱,但实际跑下来,违规率极低。
背后的逻辑是:现代大模型的指令遵循能力已经够强了。只要规则写得清楚、没有歧义,模型基本都能遵守。
当然,提示词约束不能替代硬防护——沙箱、命令注入检测、权限系统这些还是得有。但在硬防护之上,它是一道成本极低的”软防线”。
Bash 很强,但要防护得当
Bash 是”万能工具”,你不需要造一堆专用工具,一个 Bash 就能执行任何命令。但它也是最危险的工具。给 AI 一个能执行任意命令的接口,就像给一个新手一把枪。
Claude Code 的安全设计,有一半是围绕 Bash 展开的:
15 种危险模式检测——递归删除、磁盘擦除、远程脚本执行、命令替换… 每种攻击模式都有对应的正则规则。
沙箱隔离——Linux 用 Bubblewrap,macOS 用 Seatbelt,Docker 环境用容器隔离。就算出问题也不会影响宿主系统。
审计日志——每条命令的输入、输出、时间戳、执行结果都有记录,出了问题可以追溯。
这套组合拳的思路是:先假设模型会出错,然后层层设防。永远不要相信任何输入,模型生成的命令也是”输入”。
扩展性靠的是边界,不是开放
MCP 服务器扩展能力,不需要动核心代码。Hooks 自定义行为,不需要 fork 项目。靠的是清晰的边界。
MCP 通过 JSON-RPC 通信,对 Claude Code 内部一无所知。Hooks 通过环境变量接收上下文,通过 stdout 返回结果。这些边界让演化成为可能——内部随便改不会破坏扩展,扩展随便创新不用等核心更新。
大部分人做扩展性,一上来就想”开放一切”,结果内部和外部绞在一起。Claude Code 的做法反过来:先画好边界,边界内随便玩。
终端 UI 不是事后补丁
在终端里用 React,听起来离谱。但 Claude Code 的 UI 要同时处理流式文本、权限弹窗、进度指示、键盘输入、多工具并发显示… 用裸的 ANSI 转义码去搞,代码会脆弱到不行。
React + Ink 把 Web 开发里那套组件化、声明式渲染搬到了 CLI 里。好的工具选型能省掉大量维护成本。与其跟终端底层搏斗,不如站在成熟框架上面。自己造轮子的快感很短暂,维护的痛苦很漫长。
可观测性是一切优化的前提
你没法改进你看不见的东西。Claude Code 在监控上下了不少功夫:API 调用耗时、工具执行时间、token 消耗量、缓存命中率、错误频率… 这些数据驱动了状态栏显示、上下文压缩的阈值判断、各种优化决策。
值得一提的是遥测对隐私的尊重——只收集使用元数据,不收集工作内容。
做了十几年后端的人都知道,线上系统没有监控,出了问题就是两眼一黑。Agent 也一样——你得知道每次循环花了多少 token、哪个工具最耗时、缓存有没有生效。没有这些数据,连优化方向都找不到。
质疑每个组件的必要性
最后这一条,也是我觉得最重要的。
为什么 Claude Code 的代码量这么少? 320 个文件,考虑到它的功能覆盖面——文件操作、命令执行、安全防护、权限管理、上下文压缩、子代理调度、多云适配、IDE 集成、浏览器控制、终端 UI… 这点代码量真的很克制。
没有单独的 GitTool,因为 Bash 能干。没有复杂的状态机,因为 while 循环够用。没有 ORM,因为直接操作文件够用。
这种”减法思维”贯穿整个项目。最好的代码是不存在的代码。每多一行,就多一点维护成本,多一个出 bug 的可能。
写在最后
这 17 篇拆解,花了我大概两周的业余时间。最大的收获不是某个具体的技术点,而是看到了一个成熟工程团队的思维方式——不追求”最先进”,追求”刚刚好”;不追求”什么都能干”,追求”该干的干好”。这也是我做架构设计始终遵循的原则:简单、合适、演进。
你理解了循环机制,就知道为什么它有时会反复调用工具而不停下来;你理解了上下文管理,就知道为什么长对话后期它会”变笨”;你理解了权限系统,就知道那些配置项到底在控制什么。了解原理,不是为了当考官,是为了当更好的驾驶员。
如果你也在做 AI Agent 相关的项目,建议把 Claude Code 的源码翻一翻。不是让你抄代码,而是学习这种工程思维。毕竟,能打的产品,靠的从来不是花架子。
夜雨聆风
