拆解 Claude Code:一个 AI Agent 的架构设计哲学

张开发
2026/4/6 21:24:53 15 分钟阅读

分享文章

拆解 Claude Code:一个 AI Agent 的架构设计哲学
源码版本claude-code 2.1.88本文基于对 Claude Code 源码的深度阅读分析其 Agent 架构中最值得借鉴的设计理念。一、Agent 的心脏异步生成器循环它是什么Claude Code 的核心是一个async function* query()—— 一个异步生成器函数。整个 Agent 的工作方式就像一条永不停止的传送带用户输入 → 模型思考 → 输出文本/工具调用 → 执行工具 → 结果回传 → 继续思考 → …这条传送带用while(true)驱动每轮迭代就是一次模型响应 工具执行的完整周期。外部通过for await (const message of query(...))消费产出来一条处理一条。为什么巧妙想象你在餐厅点了一份套餐。传统做法是前菜、主菜、甜点全部做好了一起上Promise 模式。而异步生成器更像是一道菜做好了就端上来——前菜上桌的同时主菜已经在做了。这意味着渐进式渲染用户能立即看到模型的第一个字不用等整轮结束自然背压如果消费者UI处理不过来生产者模型流自动减速组合性子流程通过yield*委托像俄罗斯套娃一样嵌套源码位置query.ts:219二、流式工具执行不等齐就开跑它是什么大多数 Agent 系统的工作方式是等模型输出完所有 tool_use → 一次性执行所有工具 → 全部结果回传Claude Code 的StreamingToolExecutor打破了这个等待模型输出 tool_use A → 立即执行 A 模型输出 tool_use B → 立即执行 B如果安全跟 A 并行 模型输出 tool_use C → 等前面执行完如果是写操作为什么巧妙想象你在超市结账。传统模式是等所有商品扫完码再一起付钱、装袋。而 Claude Code 更像是边扫码边装袋——扫一件装一件有冲突的商品比如冷冻食品和热食才需要等一等。具体实现上只读工具Read、Grep、Glob可以安全并行——它们不会改任何东西写操作Edit、Write、Bash必须串行——避免冲突工具有一个isConcurrencySafe()方法来声明自己的安全性这个区分看似简单实则关键。在代码分析场景中Agent 经常一次发起 5-10 个搜索请求并行执行可以将延迟从10x 单次延迟压到1x 单次延迟。源码位置StreamingToolExecutor.ts三、Fork 子 Agent字节级 Prompt Cache 共享它是什么Claude Code 的子 Agent如后台记忆提取、Session Memory启动时不是从零开始的对话而是完整继承父 Agent 的对话历史和系统提示词。为什么巧妙这涉及到大模型 API 的一个底层优化Prompt Cache提示词缓存。API 端会缓存已处理过的 prompt 前缀下次请求如果前缀相同就能跳过重复计算直接命中缓存。问题是缓存是字节级精确匹配的。哪怕差一个空格整段缓存就失效了。所以 Claude Code 做了这些事CacheSafeParams类型显式声明了哪些参数必须一致system prompt、tools、model、messages、thinking configFORK_PLACEHOLDER_RESULT—— 所有 fork 子进程的工具结果占位符必须完全相同确保字节对齐子进程的系统提示词不是重新生成而是直接引用父进程的同一份想象你在写一份合同模板给律师审阅。如果 10 个子公司都要用同一份模板聪明的做法是让律师审一次缓存后面 9 个只看修改的部分增量。但如果每个子公司都改了一个字律师就要从头看 10 遍缓存全部失效。Claude Code 的 fork 设计就是确保前 N 页一个字都不改。源码位置forkSubagent.ts:60-72,forkedAgent.ts:57-68四、YOLO 分类器用 AI 审 AI它是什么在 Auto 模式下Claude Code 不会对每个工具调用都弹权限确认框。它用了一个YOLO 分类器来判断命令是否安全——安全就自动放行危险才问用户。为什么巧妙这个分类器本身就是一个 Claude API 调用不是一个独立训练的小模型。它通过巧妙的 prompt 设计让 Claude 自己充当安全审核员两阶段审核流程 Stage 1快审max_tokens64要求模型立即输出 blockallow/block 或 blockdeny/block ↓ 如果 allow → 直接放行快路径延迟极低 ↓ 如果 deny Stage 2深思放宽 token 限制允许 chain-of-thought 推理减少误杀这就像机场安检的快速通道大多数旅客安全命令走快速通道扫一眼就过——对应 Stage 1可疑行李触发开箱检查——对应 Stage 2两个阶段共享同一套安检标准system prompt且共享 prompt cache细节上还有个讲究关掉 thinkingtemperature: 0thinking: false。因为 extended thinking 产出的 token 会被解析器忽略白白浪费。只有内部特殊模型强制 thinking才保留并额外加 2048 token 余量防止 thinking 吃光预算。还有一个防死循环设计拒绝追踪自动降级。如果 auto 模式连续被拒 3 次自动降回每次都问用户的 prompt 模式。这防止 Agent 在无人值守时陷入申请→被拒→再申请→再被拒的死循环。源码位置yoloClassifier.ts,denialTracking.ts五、任务完成的判定不是你想停就能停它是什么直觉上判断 Agent 任务是否完成很简单——模型没有调用工具就说明它认为任务做完了。Claude Code 的实现确实以此为基础但在上面叠加了多层拦截器。为什么巧妙想象一个法官判案。表面上是法槌一敲就结案但实际流程是法槌落下模型没有输出 tool_use → 这是程序性错误吗413/Media/MaxTokens → 不算重新审理 → 这是 API 故障吗错误消息 → 特殊处理直接结束 → 陪审团有异议吗Stop Hooks → 可能把案子打回重审 → 预算用完了吗Token Budget → 可能强制续写 → 全部通过 → 真正结案 ✅其中最有趣的是Stop Hook。它是一个用户可配置的外部脚本在 Agent 认为自己完成之后运行。Stop Hook 可以打回重做blockingError注入一条消息告诉模型你漏了什么然后continue回主循环强制终止preventContinuation不等模型同意直接结束静默通过确认任务确实完成了这是一个**“默认结束但多方可否决”**的设计。比起简单的有 tool_use 就继续没有就停它给了用户、Hook 系统、预算系统各一个否决权。另一个细节stop_reason tool_use在 API 中不可靠不总是被正确设置。所以 Claude Code 不依赖它而是自己在流式接收时维护一个needsFollowUp标志位。这体现了工程实践中一个重要原则不信任外部系统的隐式状态自己维护自己需要的真相。源码位置query.ts:554-558needsFollowUp 定义,query.ts:1062-1357完成分支,stopHooks.tsStop Hook 处理六、上下文管理投影而非快照它是什么Agent 在长对话中会逐渐耗尽上下文窗口。常见的做法是上下文太长时做一次摘要替换。Claude Code 用了一种更精细的方式——上下文坍缩Context Collapse。为什么巧妙想象你在读一本 1000 页的小说。传统摘要方式是读到第 500 页时让人帮你写个 50 页的摘要扔掉前 500 页原文然后带着摘要继续读。问题是你丢掉了所有细节。Claude Code 的做法更像折叠地图原始对话历史不删除完整保留维护一个坍缩日志——记录哪些部分被折叠了发送给 API 时动态投影出折叠后的视图需要细节时可以展开某个特定部分系统注释写道“Nothing is yielded — the collapsed view is a read-time projection over the REPL’s full history.”翻译过来就是什么都不丢弃只是在读取时投影出一个更短的版本。此外还有多种互补策略Auto-compaction自动在 token 使用超标时触发摘要Micro-compaction更细粒度通过 cache editing 保留工具结构Snip compaction只裁剪冗长的工具输出保留关键消息Reactive compaction在 prompt-too-long 错误发生后的紧急抢救这五套策略层层递进从预防到急救形成完整的上下文管理防线。源码位置query.ts:440-447context collapse,autoCompact.ts自动摘要七、工具结果持久化避免无限递归它是什么每个工具有一个maxResultSizeChars属性。当工具输出超过这个阈值时结果会被持久化到磁盘消息中只保留一个文件路径引用。为什么巧妙考虑这个场景Read 工具读取了一个大文件输出超过阈值被持久化到了/tmp/result.txt。模型看到消息里说结果已保存到 /tmp/result.txt于是发起第二次 Read 去读那个文件。那个文件的内容又超过阈值又被持久化……无限递归。Claude Code 的解决方案极其简洁Read 工具的maxResultSizeChars设为Infinity。这意味着 Read 的输出永远不会被持久化无论多大都留在消息中。从根源上消除了递归的可能。这体现了安全设计中的一个重要原则不要用复杂的逻辑来防止循环而是从结构上让循环不可能发生。源码位置Tool.ts:466-468八、错误恢复五层防线它是什么Agent 在运行中会碰到各种错误模型挂了、输出截断了、上下文太长了、工具执行失败了……Claude Code 为每种情况都准备了恢复策略形成五层防线层级错误类型恢复策略源码位置1模型故障切换到备用模型清理残留状态重试query.ts:893-9512输出截断注入续写指令让模型从断点继续最多 3 次query.ts:1222-12573上下文过长先尝试释放已缓存的坍缩再做紧急摘要query.ts:1085-11834工具结果缺失为未匹配的 tool_use 生成合成错误消息query.ts:123-1495流式中途故障发射墓碑消息清理无效的 thinking blockquery.ts:713-728为什么巧妙想象一个经验丰富的登山向导路有点滑 → 换条路走模型 fallback天色晚了 → 找个地方过夜明天继续输出截断续写雪崩封路 → 先清理积雪collapse drain清理不了就改道reactive compact装备掉了 → 用替代品补上合成错误消息队友走散了 → 留个标记等他们跟上墓碑消息关键是每一层恢复都不是简单的重试而是针对特定错误类型的专门策略。而且它们之间可以串联——第 3 层尝试失败后可能退到第 5 层做最后的清理。九、动态工具加载按需取用它是什么Claude Code 支持大量的工具——内置的 Read/Write/Edit/Bash/Grep/Glob加上用户通过 MCP 接入的外部工具。如果把所有工具的 JSON Schema 都放在初始 prompt 里会消耗大量 token。解决方案工具可以标记为defer_loading: true不在初始 prompt 中发送。模型通过一个特殊的ToolSearchTool来搜索和加载需要的工具。为什么巧妙想象你去图书馆借书。一种做法是把图书馆所有书都搬回家全部工具 schema 都放 prompt显然不现实。另一种是先带一个目录索引工具名称和一句话描述需要哪本再去书架上取。这就是ToolSearchTool的作用。它像一个图书管理员模型说我需要一个操作文件的工具ToolSearch 返回相关工具的完整 schema模型获得足够信息后正常调用这对 MCP 生态尤其重要——用户可能接入了 20 个 MCP 服务每个服务有 5-10 个工具总共 100 个工具。全部放 prompt 里根本放不下。源码位置Tool.ts:439-450十、Hook 系统可编程的生命周期它是什么Claude Code 在几乎所有关键生命周期节点都暴露了 Hook 接口。用户可以注册 Shell 脚本在特定事件触发时执行并且可以修改行为。支持的 Hook 事件包括类别事件工具执行PreToolUse, PostToolUse, PostToolUseFailure会话生命周期SessionStart, SessionEnd, Stop, StopFailureAgent 生命周期SubagentStart, SubagentStop上下文管理PreCompact, PostCompact用户交互UserPromptSubmit, PermissionRequest任务管理TaskCreated, TaskCompleted状态变更FileChanged, CwdChanged, ConfigChange为什么巧妙这就像编程语言中的面向切面编程AOP。你不需要修改 Agent 的核心逻辑就能在任意切面插入自定义行为。而且 Hook 不仅是监听器它有修改能力PreToolUse Hook可以修改工具的输入参数Stop Hook可以阻止 Agent 停下来让它继续干活Permission Hook可以覆盖权限决策但有一个安全不变量Hook 的 allow不能覆盖用户的显式 deny最后一个点特别重要。这体现了安全设计的最小权限原则——即使 Hook 被恶意配置也无法绕过用户明确设定的安全规则。源码位置hooksConfigManager.ts,toolHooks.ts总结设计的核心张力纵观 Claude Code 的架构几乎所有巧妙的设计都在解决同一对矛盾低延迟 vs 安全控制流式工具执行 → 降低延迟YOLO 分类器 → 自动审批降低延迟同时保持安全Prompt Cache 共享 → 降低子 Agent 启动延迟动态工具加载 → 减少初始 prompt 大小上下文坍缩 → 延长可用对话轮次而在安全这一侧分层权限模型规则 Hook 分类器Hook 的 allow 不能覆盖用户 denyRead 的 Infinity 预算从结构上防递归拒绝追踪自动降级防死循环这种**“默认快速层层安全兜底”**的设计哲学使得 Claude Code 在保持响应速度的同时不牺牲用户对 Agent 行为的控制权。这对任何构建 AI Agent 系统的人来说都是值得借鉴的思路。本文基于 claude-code 2.1.88 源码分析源码目录结构可能随版本迭代发生变化。

更多文章