Claude Code 实战——AI 编程工程深度拆解
本文是「AI 大模型工程知识体系」系列的第 06 篇。在前五篇中,我们分别拆解了大模型原理、RAG 检索增强、工具调用机制和 LangChain 框架。本篇将视角从「通用 AI 工程」收束到当下最成功的 AI 编程实践——Claude Code,从基础使用到源码架构,做一次完整的工程深度拆解。
Claude Code 是 Anthropic 推出的命令行 AI 编程工具,它不是一个套壳的聊天机器人,而是一个能自主读代码、写代码、跑测试、做代码审查的智能体(Agent)。在 GitHub 上被无数开发者验证为「最好用的 AI 编程工具之一」。
但本文不只是教你怎么用 Claude Code。我们将从三个层次展开:
- 使用层:基础操作、CLAUDE.md 配置、Skill 机制、SDD 方法论
- 架构层:四层架构、Query Loop 主循环、上下文压缩、记忆系统
- 哲学层:系统提示词设计、代码检索为何不用 RAG、多 Agent 协作、AI 编程的本质
读完本文,你不仅能把 Claude Code 用到极致,还能理解「一个工业级 AI Agent 是怎么设计出来的」。
第一章 基础使用与工作模式
1.1 四个工作模式
启动 Claude Code 只需在终端敲一个 claude。但真正决定它「怎么干活」的,是工作模式。Claude Code 有四个核心模式,用 Shift+Tab 循环切换:
| 模式 | 行为 | 适用场景 |
|---|---|---|
| Normal | 每次修改前征求你同意 | 探索阶段、不熟悉的代码库 |
| Auto-accept edits | 自动接受文件编辑,不再逐个确认 | 信任方向、批量修改 |
| Plan Mode | 只读探索不执行任何修改,先出方案 | 复杂任务、需要先对齐思路 |
| Custom / Full Auto | 自动执行所有操作(含命令) | 高度信任的重复性任务 |
Plan Mode 是最值得单独拎出来讲的。当你面对一个复杂需求——比如「重构整个认证模块」——直接让 AI 开干风险极大。Plan Mode 下,Claude Code 会进入只读探索状态:它可以 grep、可以读文件,但绝不写文件、不跑命令。它先把代码摸清楚,然后给你一份计划:改哪些文件、按什么顺序、预期效果是什么。你确认后,它再退出 Plan Mode 开始执行。
这个设计背后的思想是:工具即能力。Plan Mode 在源码层面是通过 EnterPlanMode / ExitPlanMode 两个工具实现的——模型调用 EnterPlanMode 就进入只读状态,调用 ExitPlanMode 时附带一份计划文本。不是靠什么复杂的状态机,就是把「能做什么」通过工具列表切了一下。
1.2 十个官方进阶技能
Claude Code 提供了一套 /powerup 课程体系,涵盖 10 个进阶能力。这里不逐条复述,而是按理解层次归类:
引用与控制类:
@文件路径引用文件,让模型直接读到内容permissions.deny配置屏蔽特定命令(比如禁止rm -rf)Esc+Esc撤销上一步操作——AI 改错了,一键回退
上下文管理类:
/context查看当前上下文使用情况(用了多少 token)/compact手动压缩对话历史/clear彻底清空对话重新开始claude --resume/--continue恢复历史会话
扩展能力类:
- CLAUDE.md:项目级配置文件,每次启动自动加载(第二章详讲)
- MCP(Model Context Protocol):通过
claude mcp add接入外部工具服务 - Skills:通过
/plugin install安装技能插件(第三章详讲) - Hooks:
PreToolUse/PostToolUse钩子,在工具执行前后插入自定义逻辑 - 子代理(SubAgent):通过
/agents管理独立的子 Agent 实例(第七章详讲)
模型控制类:
/model切换模型(Opus / Sonnet / Haiku)/effort设置推理深度:low→medium→high→xhigh→maxultrathink关键词:临时把推理挡位拉到最高(新版唯一生效的关键词)
1.3 子代理 vs Skills:两套扩展机制的区别
这是初学者最容易混淆的概念。两者都能「扩展 Claude Code 的能力」,但机制完全不同:
┌─────────────────────────────────────────────────┐
│ Skills(技能) │
│ 在主对话上下文中加载一份 .md 指南 │
│ 模型读完指南后,用主对话的工具去执行 │
│ 共享主 agent 的上下文窗口 │
│ 适合:领域知识、操作规范、验证流程 │
├─────────────────────────────────────────────────┤
│ SubAgent(子代理) │
│ 启动一个独立的 agent 实例 │
│ 有自己独立的上下文窗口和工具池 │
│ 跑完只把结论返回给主 agent │
│ 适合:大规模探索、隔离任务、并行调研 │
└─────────────────────────────────────────────────┘
一句话区分:Skill 是给主 agent 看的说明书,SubAgent 是派出去的独立员工。
实践要点
- 新手从 Normal 模式开始,确认 AI 的修改方向正确后再切 Auto-accept
- 复杂任务必走 Plan Mode:先对齐再执行,避免跑偏后回不了头
ultrathink不是万能的:它只在需要深度推理的场景(架构设计、复杂 bug)才有价值,日常写代码用默认挡位即可/context要勤看:上下文用到 70% 以上就该/compact了,别等自动触发- Skills 和 SubAgent 不是二选一:Skill 负责教主 agent「怎么做」,SubAgent 负责「派出去做」,可以组合使用
第二章 CLAUDE.md:给 Agent 的入职手册
2.1 CLAUDE.md 不是 README
很多人把 CLAUDE.md 当成项目 README 来写,这是最大的误解。
README 是给人看的,介绍项目是干什么的、怎么安装。CLAUDE.md 是给 Agent 看的,它每次启动都会自动加载这个文件。所以它的本质是:一份给 AI 的入职手册。
你写给新员工的入职手册会写什么?不会写「我们公司是一家做电商的」(他面试时知道了),而是写「提交代码前必须跑 npm test」「数据库迁移文件放在 db/migrations/ 下」「千万别动 legacy/ 目录的代码」。
CLAUDE.md 同理。它应该写的是:Agent 在这个项目里干活需要遵守的规则。
2.2 200 行黄金线
Anthropic 自己做过统计:
| CLAUDE.md 行数 | 模型遵守率 |
|---|---|
| ≤ 200 行 | 92% |
| 200-400 行 | 70% |
| > 400 行 | 急剧下降 |
为什么?因为 LLM 存在「Lost in the Middle」现象——放在上下文中间位置的信息,模型注意力会下降。CLAUDE.md 越长,真正被模型「看到并遵守」的比例越低。
如果 200 行不够写怎么办?模块化拆分。把规则拆到 .claude/rules/ 目录下,每个文件用 YAML frontmatter 声明它只在特定路径下生效:
---
name: 前端规范
description: React + Tailwind 项目规范
paths: ["**/*.tsx", "**/*.jsx"]
---
# 前端规范
- 组件用函数式,不用 class component
- 状态管理用 Zustand,不用 Redux
- CSS 用 Tailwind utility class,不写自定义 CSS
这样拆分后,遵守率能回升到 96%。因为模型只在编辑 .tsx 文件时才加载前端规范,不会在后端代码里也塞一堆前端规则浪费 token。
2.3 三类反面教材
什么样的 CLAUDE.md 是差的?三类典型反例:
复述型——把项目结构、技术栈复述一遍:
# 本项目是一个 React + Node.js 全栈应用
前端用 React 18,后端用 Express...
模型自己 grep 一下就知道的事,写进 CLAUDE.md 纯属浪费 token。
愿望型——写了但无法验证:
# 代码质量要求
- 写出优雅、高效的代码
- 遵循最佳实践
「优雅」「高效」是主观的,模型无法执行。不如写「函数不超过 50 行」「所有 API 响应时间 < 200ms」。
术语表型——把一堆名词解释塞进去:
# 术语表
- SSR: Server-Side Rendering
- CSR: Client-Side Rendering
- ISR: Incremental Static Regeneration
模型本身就知道这些术语,不需要你教。
2.4 四条写作原则
好的 CLAUDE.md 遵循四条原则:
- 短:200 行以内,超了就拆
- 具体可验证:写「提交前跑
npm test」不写「确保质量」 - 告诉为什么:写「不要用 mock 测试,因为上季度 mock 通过了但 prod 挂了」——有了 Why,模型在边界情况能自己判断该不该破例
- 持续更新:用
/memory命令维护,发现新的坑就加进去
2.5 分层架构
CLAUDE.md 不止项目根目录一个,它有六个层级(按加载优先级从低到高):
┌──────────────────────────────────────────────┐
│ Managed │ 系统级路径,管理员强制策略 │
│ User │ ~/.claude/CLAUDE.md,个人全局 │
│ Project │ 项目根/CLAUDE.md,签入 git 共享 │
│ Local │ CLAUDE.local.md,不签 git 个人用 │
│ Auto │ 自动记忆目录,Agent 自动写入 │
│ Team │ team/ 子目录,团队共享的 AI 偏好 │
└──────────────────────────────────────────────┘
六层叠加(不是覆盖),全部拼进 system prompt。此外还有两个机制:
@include指令:类似 C 语言的#include,在 CLAUDE.md 里写@~/company/security-rules.md,加载时自动把那个文件内容拼进来,支持跨文件引用@AGENTS.md:跨工具兼容,Cursor、Windsurf 等工具也读这个文件,实现一份配置多工具通用
2.6 六段式模板
如果你不知道从哪开始,用 /init 命令让 Claude Code 自己生成一份初稿,然后按六段式模板维护:
1. Overview — 一句话项目简介
2. Commands — 常用命令(build/test/lint/deploy)
3. Architecture — 关键架构决策
4. Conventions — 编码约定(命名、目录、风格)
5. Hard Constraints — 硬约束(绝对不能做的事)
6. Gotchas — 踩过的坑(最容易出错的地方)
其中 Gotchas(坑点清单)是含金量最高的部分。它是你用血泪换来的经验,也是模型最容易忽略的地方。
实践要点
- CLAUDE.md 是写给 Agent 的,不是写给人的——每写一句都问自己「模型能执行吗?」
- 200 行是红线——超了就拆到
.claude/rules/,用paths条件按需加载 - 每条规则带上 Why——让模型在边界情况能自主判断
- Gotchas 优先写——「别用
==比较 null」比「写高质量代码」有用一万倍 - 用
/init起步,用/memory维护——不要手写初稿,让 Agent 先帮你生成
第三章 Skill 机制与渐进式披露
3.1 Skill 是文件夹不是文件
很多人以为 Skill 就是一个 Markdown 文件。不是。Skill 是一个文件夹:
my-skill/
├── SKILL.md ← 核心指南(必需)
├── references/ ← 参考文档(按需读取)
│ ├── api-spec.md
│ └── migration-guide.md
├── scripts/ ← 可执行脚本
│ └── validate.sh
└── assets/ ← 静态资源
└── template.yaml
SKILL.md 是入口,但它不是全部。references 目录存放详细文档,模型只在需要时才读取;scripts 存放可执行脚本,模型可以按需调用。
3.2 渐进式披露(Progressive Disclosure)
这是 Skill 机制最核心的设计理念。它的灵感来自 UI 设计中的 Progressive Disclosure——不要一次性把所有信息塞给用户,而是按需展开。
在 Skill 机制中,它分三个层级:
平时状态:只给 description 清单(占上下文预算的 1%)
│
│ 用户任务匹配到某个 skill
▼
调用状态:加载完整 SKILL.md(几十到几百行)
│
│ SKILL.md 里引用了 references/xxx.md
▼
深入状态:按需读取参考文档(可能几千行)
关键设计在于 description 字段。每个 Skill 的 SKILL.md 开头有一段 YAML frontmatter:
---
name: my-validation-skill
description: "验证 API 响应是否符合 OpenAPI 规范。当用户需要检查
API 接口返回值、验证 Swagger 文档、测试接口合规性时使用。"
---
注意:description 是触发条件,不是给人看的摘要。它写的是「什么情况下应该激活这个 Skill」,用的是模型能匹配的语言。Anthropic 对 description 有一个 250 字符的限制,就是要逼你写精炼的触发条件,而不是写说明书。
3.3 九类 Skill 分类
Skill 不是无差别的「插件」,按用途可以分九类:
| 类别 | 作用 | 价值评级 |
|---|---|---|
| 验证类 | 检查代码/配置是否符合规范 | ⭐⭐⭐⭐⭐ 最高 |
| 工作流类 | 封装多步骤操作流程 | ⭐⭐⭐⭐ |
| 知识类 | 注入领域专业知识 | ⭐⭐⭐⭐ |
| 模板类 | 提供代码/文档模板 | ⭐⭐⭐ |
| 转换类 | 格式转换、代码迁移 | ⭐⭐⭐ |
| 分析类 | 数据/代码分析 | ⭐⭐⭐ |
| 集成类 | 对接外部系统 | ⭐⭐ |
| 导航类 | 帮助理解代码结构 | ⭐⭐ |
| 调试类 | 辅助调试排障 | ⭐⭐ |
验证类 Skill 价值最高,因为它做的是模型最容易偷懒的事——检查自己的输出是否合规。一个「提交前检查是否有 console.log 残留」的 Skill,比十个「帮你写代码」的 Skill 都管用。
3.4 坑点清单(Gotchas):Skill 的灵魂
一个 Skill 的 SKILL.md 里,最值钱的不是使用说明,而是 Gotchas(坑点清单)。
为什么?因为模型读完文档就知道「怎么做」,但不知道「哪里容易出错」。Gotchas 就是把前人踩过的坑列出来,让模型提前避雷:
## Gotchas
- ⚠️ `JSON.parse` 不会报错但会返回 undefined(当输入是 "undefined" 字符串时)
- ⚠️ `Date.getTimezoneOffset()` 的正负号跟直觉相反
- ⚠️ 这里的 `userId` 是字符串不是数字,直接用 === 比较会翻车
- ⚠️ 测试数据库是共享的,跑完测试必须清理数据
这些坑点,模型自己探索要踩好几轮才能发现,但写在 Skill 里它一次就避开了。
3.5 高阶用法
记忆持久化:Skill 可以用 CLAUDE_PLUGIN_DATA 目录存数据,跨会话保持状态。比如一个「代码风格学习」Skill,可以记住你每次纠正的偏好,下次自动应用。
脚本封装:复杂操作封装成 scripts/ 下的脚本,SKILL.md 里告诉模型「跑 scripts/validate.sh」即可,不用让模型自己拼命令。
临时 Hook:有一种叫 careful / freeze 的 Skill,它临时修改工具权限——比如 freeze Skill 激活后,禁止所有写操作,只允许读。适合在审查代码时使用。
3.6 分发与演化
Skill 的分发有两种路径:
- 代码仓库:直接放在项目的
.claude/skills/目录,跟代码一起版本管理 - Plugin Marketplace:通过
/plugin install安装社区 Skill
一个 Skill 的自然演化路径是:沙盒测试 → 口碑传播 → 正式发布。先在自己项目里用,验证有效后分享给团队,最后发布到社区。
实践要点
- description 写触发条件不写摘要——「当用户需要…时使用」比「这是一个…工具」有效得多
- Gotchas 是 Skill 的灵魂——没有坑点清单的 Skill 只是一半的 Skill
- 验证类 Skill 投入产出比最高——优先写「检查类」而非「生成类」
- references/ 目录利用好渐进式披露——详细文档放这里,SKILL.md 里只放入口指引
- 不要一个 Skill 干太多事——一个 Skill 只解决一类问题,description 才能匹配精准
第四章 SDD 规约驱动开发与 grill-me 方法论
4.1 Vibe Coding 的问题
「Vibe Coding」是 Andrej Karpathy 提出的概念——凭感觉跟 AI 对话写代码,想到哪说到哪。这种方式在原型阶段很爽,但项目一旦长大,问题就来了:
越改越乱。今天让 AI 加个功能,明天让它改个 bug,后天让它重构。每次改都对,但改了二十次之后,代码变成了什么样子谁也说不清。AI 没有全局记忆,每次都是「基于当前代码做局部修改」,局部最优的堆叠不等于全局合理。
缺对齐。你脑子里的需求是 A,AI 理解成 B,做出来你发现是 C。你说「不对,重做」,它又从零开始,之前的成果全白费。
SDD(Spec-Driven Development,规约驱动开发)就是来解决这个问题的。
4.2 spec-kit 工具链
Anthropic 开源了一套叫 spec-kit 的工具,把 SDD 落地成五个命令,对应五个阶段:
/speckit-constitution → 定宪法:项目的根本约束和原则
│
/speckit-specify → 谈需求:只说做什么,不说怎么做
│
/speckit-plan → 定技术:选什么技术栈、怎么架构
│
/speckit-tasks → 拆活:把计划拆成可执行的任务清单
│
/speckit-implement → 写代码:按任务清单逐个实现
此外还有两个兜底命令:
/speckit-clarify:当需求含糊时,主动追问澄清/speckit-analyze:一致性检查,看规约、计划、代码三者是否对齐
4.3 SDD 与瀑布流的本质区别
看到「先定需求→再定方案→再拆任务→最后写代码」,很多人会说:这不就是瀑布流吗?
不是。关键区别在于:规约是活的,随时可改。
瀑布流的文档是「签字盖章后不能改」的死文档。SDD 的规约是「随时可以回头改」的活文档。你在 implement 阶段发现 specify 写错了?没问题,回头改 specify,然后 plan 和 tasks 自动跟着调整。
这更接近「设计树」的概念——软件工程泰斗 Fred Brooks 说过,设计是一棵树,上游决策不定死(留有调整空间),下游全是空中楼阁(没有基础)。SDD 的每个阶段就是树的一个层级,你可以回到任意一个节点重新分支。
4.4 适用场景
SDD 不是所有项目都需要。它最适合:
- 长期维护项目:今天写的代码三年后还要改,没有规约就是灾难
- 多人协作项目:规约是团队对齐的工具
- 复杂业务逻辑:需求层次多、边界条件多
不适合:
- 一次性脚本
- 快速原型验证
- 个人玩具项目
4.5 grill-me:往死里盘问
SDD 解决的是「怎么把需求讲清楚」,但在写规约之前,还有一步更前置的:你自己得先想清楚你要什么。
grill-me 是一个社区 Skill,核心理念三句话:
- 往死里盘问:在开始写代码前,把需求里所有模糊的地方全问一遍
- 一次只问一个:不要一次抛十个问题,一次一个,问完再问下一个
- 能翻代码就别问:能在代码里找到答案的,不要问用户
grill-me 最精妙的地方在于它跟 Plan Mode 的配合:
用户提需求
│
▼
grill-me 模式启动:逐个追问模糊点
│ (通常 10-15 个问题,20-30 分钟)
▼
需求完全清晰
│
▼
Plan Mode:基于清晰需求出方案
│
▼
执行
4.6 grill-me vs superpowers:实测对比
社区里另一个流行的 Skill 叫 superpowers,也是做需求澄清的。两者实测对比:
| 维度 | grill-me | superpowers |
|---|---|---|
| 问题数量 | 12 个 | 4 个 |
| 耗时 | 32 分钟 | 2 小时 |
| 覆盖深度 | 片纸不留 | 留完整文档+测试+git |
| 美术风格等主观项 | 问用户拍板 | 用默认值 |
最关键的差异在「美术风格」这类主观决策上。grill-me 会问「你要什么风格?」让你拍板;superpowers 直接用默认值。结果成品差距巨大——一个是你想要的,一个是它猜的。
建议:大多数人先装 grill-me。superpowers 更适合需要自动生成测试和文档的场景,但它耗时长、问题少,容易漏掉关键需求点。
实践要点
- Vibe Coding 适合原型,SDD 适合产品——项目要长期维护就上 SDD
- spec-kit 五步不是瀑布流——规约随时可改,这才是 SDD 和瀑布流的本质区别
- grill-me 在 Plan Mode 之前跑——先想清楚再出方案
- grill-me 的「能翻代码就别问」原则——减少用户负担,能用工具查的就自己查
- 主观决策一定要让用户拍板——AI 的默认审美不一定对
第五章 源码架构:四层架构与 Query Loop
5.1 四层架构鸟瞰
Claude Code 的源码(反编译后的 TypeScript)呈现出清晰的四层架构:
┌─────────────────────────────────────────────────────┐
│ 安全与治理层 (Security & Governance) │
│ 权限控制 · Hook 系统 · Bash 安全沙箱 · 工具三属性 │
├─────────────────────────────────────────────────────┤
│ 服务层 (Services) │
│ API 客户端 · Prompt Cache · MCP Server · 压缩服务 │
├─────────────────────────────────────────────────────┤
│ 工具层 (Tools) │
│ 40+ 工具 · 每个工具强制三安全属性 · 流式执行器 │
├─────────────────────────────────────────────────────┤
│ 引擎层 (Engine) │
│ 协调 · 分发 · 决策(无业务逻辑,纯流程控制) │
└─────────────────────────────────────────────────────┘
引擎层是大脑,负责「下一步干什么」的决策;工具层是手脚,负责「具体怎么干」;服务层是基础设施,负责跟外部世界通信;安全层是免疫系统,贯穿所有层。
5.2 Tool-Use Loop vs ReAct
大多数 Agent 框架用的是 ReAct 模式:Thought(思考)→ Action(行动)→ Observation(观察)→ 循环。每一步都有显式的 Thought 步骤。
Claude Code 不用 ReAct。它用的是 API 原生的 Tool-Use Loop:
# Claude Code 的核心循环(简化版)
while True:
response = await call_llm(messages)
if not response.tool_uses:
break # 模型说"我做完了",循环结束
for tool_use in response.tool_uses:
result = await execute_tool(tool_use)
messages.append(result) # 把工具结果塞回对话历史
没有 Thought 步骤。模型的推理过程通过 Extended Thinking(扩展思考)在 API 内部完成,应用层看不到也不需要看到。应用层就是极简的 while(true)。
这个设计的哲学是:信任模型的推理能力,应用层极简。你不需要在应用层模拟模型的思考过程,模型自己会想。
终止条件是 end_turn——当模型认为任务完成时,它返回的 response 里不带 tool_use,循环自然结束。Claude Code 定义了 17 种退出原因,包括 completed(正常完成)、max_turns(超过最大轮数)、aborted_streaming(流式中断)、prompt_too_long(上下文超限)、max_output_tokens_recovery(输出截断恢复中)等。
5.3 Query Loop 的四层调用链
Claude Code 的主流程叫 Query Loop,它的调用链有四层:
ask() ← 用户入口
└→ QueryEngine.submitMessage() ← 引擎层接收
└→ query() ← 引擎层处理
└→ queryLoop() ← 核心循环(async generator)
全用 async generator(异步生成器)实现,意思是「边干边吐」——模型还没说完,已经把前面的内容流式输出了。用户看到的是打字机效果,但底层是一条消息还没接收完,下一轮工具调用可能已经开始了。
5.4 主循环五步
每一轮循环做五件事:
① 准备消息
│ 检查上下文是否需要被动压缩
▼
② 流式调用模型
│ API 流式返回 response
▼
③ 判断终止条件
│ 有 tool_use → 继续
│ end_turn → 结束
▼
④ 执行工具
│ StreamingToolExecutor 并行启动只读工具
│ 写操作串行执行
▼
⑤ 塞回结果
│ tool_result 注入对话历史
│ 进入下一轮
5.5 StreamingToolExecutor:边输出边执行
这是 Claude Code 架构里最精妙的设计之一。
传统的做法是:等模型把整条 response 输出完 → 解析出所有 tool_use → 逐个执行。这样有延迟——模型说完了才开始干活。
Claude Code 的 StreamingToolExecutor 做的是:模型还在流式输出时,一旦检测到一个完整的 tool_use,立刻启动执行。
但不是所有工具都能并行。规则是:
- 只读工具(Read、Grep、Glob)可以并行——它们不改状态,同时跑不会冲突
- 写操作(Edit、Write、Bash)串行执行——改文件这种事不能并发,否则会互相覆盖
- fail-closed 兜底:如果并行执行出了问题,按最保守的策略处理(失败而不是继续)
5.6 跨轮状态对象
每轮循环之间,Claude Code 维护一个 State 对象:
{
messages: Message[], // 完整对话历史
turnCount: number, // 当前轮数
maxOutputTokensRecoveryCount: number, // 输出截断恢复次数
hasAttemptedReactiveCompact: boolean, // 是否已尝试被动压缩
}
这些状态决定了循环的行为:轮数超限要退出、输出截断要恢复、上下文快满了要压缩。
5.7 工具跑挂了怎么办
Agent 调一个工具,工具抛异常了,怎么办?最直觉的做法是抛给模型一个错误消息。但 Claude Code 的做法更细致:合成一个假的 tool_result(is_error: true)塞回对话历史。
为什么?因为 API 协议要求 tool_use 和 tool_result 必须配对。如果模型发了一个 tool_use 但没收到对应的 tool_result,下一轮 API 调用会直接报错。所以即使工具挂了,也必须塞一个「假」的 result 回去,告诉模型「这个工具失败了」,让模型自己决定下一步。
5.8 输出截断恢复
有时候模型的输出太长,API 会截断。Claude Code 的恢复策略是:静默升档 + nudge 续写。
输出被截断
│
▼
静默升档:max_output_tokens 从 8K → 64K
│
▼
nudge 续写:发一条"请继续"的消息,让模型接着写
│ (最多重试 3 次)
▼
拼接完整输出
「静默」是指用户看不到这个过程,只看到最终结果。升档是调大 API 的 max_output_tokens 参数,nudge 是发一条特殊消息让模型继续未完成的输出。
实践要点
- Tool-Use Loop 的极简设计是核心理念——不要在应用层模拟模型思考,信任模型
- StreamingToolExecutor 的并行/串行规则值得借鉴——只读并行、写操作串行
- 工具失败要合成假 result——API 协议要求 tool_use 和 tool_result 配对
- 17 种退出原因是工业级设计的体现——不是简单的「成功/失败」二元判断
- async generator 让用户体验更流畅——边算边输出,别等算完再吐
第六章 上下文管理:五层压缩金字塔
6.1 为什么上下文管理是 Agent 的灵魂
LLM 是无状态的——每次调用都是从头看一遍所有消息。上下文窗口就是它能「看到」的最大范围。但窗口是有限的,Agent 跑着跑着就会塞满。
大多数 Agent 框架的做法很粗糙:滑动窗口(保留最近 N 轮)、或者定期摘要(把旧消息压缩成一段)。Claude Code 的做法精细得多——它用了一个五层压缩金字塔,每层解决不同的问题。
6.2 五层压缩金字塔
┌───────────┐
第5层 │ Auto-Compact│ 全量摘要重写(最后手段)
└─────┬─────┘
┌─────┴─────┐
第4层 │Context Collapse│ 读时投影(不修改原始消息)
└─────┬─────┘
┌─────┴─────┐
第3层 │Micro-Compact │ 时间衰减清旧工具结果
└─────┬─────┘
┌─────┴─────┐
第2层 │ Snip │ 砍远古消息(模型顺手标记)
└─────┬─────┘
┌─────┴─────┐
第1层 │ Disk Cache │ 大结果存磁盘留预览
└───────────┘
从底到顶,压缩力度递增,但每层都有独立的触发条件和适用场景。
第 1 层:大结果存磁盘。 当工具返回结果超过 50KB 时,不塞进对话历史,而是存到磁盘文件,只在对话里留一个 2KB 的预览。模型需要看完整内容时,可以 Read 那个磁盘文件。
第 2 层:Snip(砍远古消息)。 模型在正常对话过程中,会顺手标记一些「不再需要的旧消息」。这些消息被 Snip 机制移除,零额外 API 调用——因为标记是模型在正常推理时顺手做的。
第 3 层:Micro-Compact(时间衰减)。 距离上次 API 调用超过 60 分钟的旧工具结果,内容被清空,只留元数据占位符。但保留最近 5 个工具结果不动。子 Agent 的输出不受此机制裁剪——因为子 Agent 输出是「结论」不是「过程」。
第 4 层:Context Collapse(读时投影)。 当上下文使用率达到 90% 或 95% 时触发。它的特点是不修改原始消息——只是在「读取」时做投影,把中间的工具调用结果折叠掉,只保留摘要。原始消息还在,只是模型「看到」的是折叠版。
第 5 层:Auto-Compact(全量摘要)。 最后手段。当 token 数超过阈值(有效窗口 - 13K 缓冲)时触发。把所有历史消息送进摘要器,全量重写成一份结构化摘要。
注意:Auto-Compact 和 Context Collapse 互斥,同一时刻只会触发其中一个。
6.3 Auto-Compact 的触发阈值
export const AUTOCOMPACT_BUFFER_TOKENS = 13_000
export function getAutoCompactThreshold(model: string): number {
const effectiveContextWindow = getEffectiveContextWindowSize(model)
return effectiveContextWindow - AUTOCOMPACT_BUFFER_TOKENS
}
这里有个容易被忽略的细节:「有效上下文窗口」本身已经从原始窗口里扣掉了约 20K,专门留给摘要输出用:
// Based on p99.99 of compact summary output being 17,387 tokens.
const MAX_OUTPUT_TOKENS_FOR_SUMMARY = 20_000
Anthropic 跑了大规模数据统计,发现摘要输出的 p99.99 分位是 17,387 token。向上取整加冗余,凑成 20K。所以真正的缓冲是 20K + 13K ≈ 33K,不是表面看到的 13K。
6.4 全量重写:反直觉的核心设计
大多数 Agent 框架做压缩时,保留最近 N 轮,把前面的压成摘要。Claude Code 不这么做——它把所有 200 轮消息全部送进摘要器,重新写一份。
第一眼看这设计很激进:最近的对话也不保留?那 Agent 不是丢了眼前正在做的事?
不会。因为压缩后的消息不是只有一段摘要,而是四段式结构:
export function buildPostCompactMessages(result: CompactionResult): Message[] {
return [
result.boundaryMarker, // ① 压缩边界标记
...result.summaryMessages, // ② 摘要消息
...result.attachments, // ③ 附件(文件、技能、计划等)
...result.hookResults, // ④ hook 执行结果
]
}
- 边界标记:记录压缩是自动还是手动、压缩前 token 数、最后一条消息 ID
- 摘要消息:200 轮全部压缩进这里
- 附件:最近读过的文件、当前计划文件、激活的技能、异步任务状态
- hook 结果:用户配置的 hooks 在压缩时执行的结果
关键在于:不同信息有不同的半衰期,走不同通道恢复。
- 语义信息(「用户想给登录接口加验证码」「技术方案改成了 JWT」)→ 压成摘要
- 状态信息(「a.py 第 42 行有 bug 在改」「文件读到第 100 行」)→ 走附件原样恢复
- 永久指令(CLAUDE.md)→ 不进摘要,清空缓存让下一轮自动重新加载
- 操作配置(system prompt、工具列表)→ 每次重建
6.5 文件恢复策略
压缩后,最多重新加载 5 个文件:
export const POST_COMPACT_MAX_TOKENS_PER_FILE = 5_000 // 每文件最多 5K token
export const POST_COMPACT_TOKEN_BUDGET = 50_000 // 总预算 50K
export const POST_COMPACT_MAX_FILES_TO_RESTORE = 5 // 最多 5 个文件
按「最近活跃度」排,最近被 Read 过的优先。三个参数把「保留最近文件」这个模糊概念工程化定义死了。
6.6 摘要 Prompt:两百多行的精巧设计
Claude Code 的摘要 Prompt 长达两百多行,光「禁止工具调用」这一条就重复了两次:
CRITICAL: Respond with TEXT ONLY. Do NOT call any tools.
- Do NOT use Read, Bash, Grep, Glob, Edit, Write, or ANY other tool.
- Tool calls will be REJECTED and will waste your only turn.
- Your entire response must be plain text.
为什么要夹着喊?因为早期 Sonnet 版本的模型经常无视一次警告,看到对话里提到「这个文件 Read 过」就手痒去 Read 一下。工程师们干脆「前后包夹」——开头喊一遍,结尾再喊一遍。
摘要输出要求用 XML 格式,包含 9 个固定章节:
<analysis>
[模型推理草稿,最终被剥离]
</analysis>
<summary>
1. Primary Request and Intent — 主要请求和意图
2. Key Technical Concepts — 关键技术概念
3. Files and Code Sections — 涉及的文件和代码
4. Errors and fixes — 碰到的错误和修复
5. Problem Solving — 解决的问题
6. All user messages — 所有用户消息(枚举!)
7. Pending Tasks — 待办任务
8. Current Work — 当前进度(最细颗粒度!)
9. Optional Next Step — 下一步建议
</summary>
第 6 项「所有用户消息」和第 8 项「当前进度」是灵魂:
- 第 6 项不是概括,是枚举——用户在第 30 轮改了需求、第 80 轮提了新约束、第 150 轮说「放弃那个方向」,一个不能落
- 第 8 项要精确到文件名和函数名——不是「正在调试」,而是「正在调试 auth.ts 的 refreshToken 函数,发现 cookie 过期判断有 bug」
6.7 熔断与递归守卫
Auto-Compact 有两个安全机制:
Circuit Breaker(断路器):连续失败 3 次就停止重试。源码注释说,曾经有 1000 多个会话因为反复压缩失败、不停重试,把 API 账单当烟花放。这种「带着血味儿的设计」是从生产环境里活下来的。
递归守卫:摘要任务本身也是开了一个子 Agent 调模型,那它会不会因为消耗 token 又触发 auto-compact,进入死循环?
if (querySource === 'session_memory' || querySource === 'compact') {
return false // 不触发压缩
}
三行代码堵死了无限递归。
6.8 压缩后怎么接续
摘要开头会被包装成一句话:「本会话是从之前一次因上下文耗尽而中断的对话延续过来的。以下摘要概述了之前的对话内容。」
这句话很重要——它告诉模型「你是接力,不是从头开始」。模型看到后不会问「请问您想做什么」,而是顺着摘要的 Current Work 往下接。
自动触发时还会打开 suppressFollowUpQuestions 开关,禁止摘要器生成「需要进一步确认」的问题——因为在 agent 正在干活时触发压缩,不能让一个新问题打断节奏。
实践要点
- 五层金字塔是分层防御——不要一上来就全量压缩,先用轻量手段
- 全量重写比保留最近 N 轮更有效——Lost in the Middle 让中间消息本来就看不清
- 不同信息走不同通道——语义进摘要、状态走附件、永久指令靠缓存重载
- 摘要 Prompt 要防呆——「禁止工具调用」要前后包夹,模型真的会手痒
- Circuit Breaker 是生产环境的标配——任何重试机制都要有熔断
- 上下文管理不是省 token,是保信息结构——这是 Claude Code 压缩哲学的核心
第七章 代码检索与记忆机制
7.1 为什么不用 RAG 检索代码
这是面试 Claude Code 时常被问的问题:为什么不用 RAG 检索代码,而是用 grep?
RAG(检索增强生成)是当下 AI 应用的标配——先给文档做 embedding 存进向量数据库,查询时算相似度召回 Top-K。但在代码场景下,RAG 有五个水土不服的痛点:
痛点 1:代码切不动。 文章是流式的,拦腰切一刀损失不大。但代码有严格结构——一个 200 行的函数按 100 行切,上半段是 if 开头、下半段是 else 结尾,两个片段都没法用。函数 A 调用函数 B,但它们在不同片段里,模型只看到 A 不知道 B 是什么。
痛点 2:精确匹配做不了。 向量召回是「找相似的」不是「找对的」。你说「找 getUserById」,向量库返回 getUserByName、getUserByEmail、fetchUserInfo——全是相似的,但没有一个是对的。
痛点 3:索引跟不上代码变化。 开发者一个 commit 改了 20 个文件,索引怎么办?重建成本高、不重建用过期信息、增量更新边界情况多。
痛点 4:冷启动慢。 百万行代码库建索引要十几分钟,用户打开工具等进度条转?直接劝退。
痛点 5:黑盒不可解释。 向量召回回来 5 个片段,为什么是这 5 个?没人答得上。出了 bug 没法 debug。
7.2 Claude Code 的三件套
Claude Code 的检索方案简单到让人意外:让模型像程序员一样,自己去找。
三个工具,对应程序员的三步工作流:
程序员工作流 Claude Code 工具
───────────── ──────────────
find(按文件名找) ←→ Glob(支持 **/*.tsx pattern)
grep -r(按内容搜) ←→ Grep(基于 ripgrep,Rust 写的)
cat(看文件内容) ←→ Read(默认读 2000 行,可分段)
每个工具都有精巧设计:
Grep 底层是 ripgrep(Rust 写的),多线程并行、自动尊重 .gitignore。三种输出模式:content(返回匹配行)、files_with_matches(只返回文件名)、count(只返回数量)。System Prompt 里强硬要求:「ALWAYS use Grep… NEVER invoke grep or rg as a Bash command」——强制走专用工具,不许用 bash 抄近路。
Glob 结果按修改时间倒序排列(最近改过的排前面),100 文件硬上限。设计哲学跟 IDE 的「最近打开文件」一样。
Read 默认只读 2000 行,支持 offset + limit 分段读取。关键:不缓存、不索引——每次直接 stat 磁盘文件读最新内容。这就是实时性的来源:没有索引层,就没有索引滞后。
7.3 三件套的组合用法
一个真实场景:「这个项目登录功能在哪实现?」
步骤1: Glob **/*login*.{ts,tsx,js}
→ 返回 5 个候选文件
步骤2: Grep passport|auth|login (在这5个文件里搜)
→ 定位到具体命中行
步骤3: Read 命中文件的相关行段
→ 看具体实现
每一步基于上一步的结果决定下一步——这是和 RAG 范式最大的不同。RAG 是一次性召回所有材料,模型将错就错;Claude Code 是多轮迭代,搜错了下一轮自己调整。
7.4 派子 Agent 探索:解决上下文污染
简单任务三件套够用。但如果任务是「调研整个项目的认证模块流程」呢?
这种任务需要 grep 几十个关键词、读十几个文件、来回比对。如果让主 Agent 自己干,它的上下文很快被一堆 grep 输出和文件片段塞满——上下文污染。等它想清楚认证流程、要回头写代码时,真正要解决的问题已经被中间结果挤到角落了。
Claude Code 的解决办法:派一个 Explore 子 Agent 去探索。
主 Agent(上下文保持干净)
│
│ 派 Explore 子 Agent
▼
子 Agent(独立上下文)
│ grep 几十次、read 几十次
│ 中间结果全留自己上下文里
▼
返回精简结论给主 Agent
│
▼
主 Agent 上下文只多了一段结论
就像老板做战略决策,不会自己扎进 Excel 翻几小时,而是派秘书去调研,秘书看完资料只把结论给老板。
临界点是「预期超过 3 次查询」——少于 3 次别折腾派 Agent,多于 3 次就别污染主 Agent。
7.5 LLM-driven 多轮迭代:grep 为什么够用
到这里,整个检索系统的层次就清晰了:
底层:Grep / Glob / Read 三件套 → 简单定向检索
中层:Explore 子 Agent → 开放式探索 + 上下文隔离
上层:主 Agent 编排 → 整体任务决策
但还有一层底层的「灵魂」:LLM-driven 的多轮迭代循环。
while True:
response = await call_llm(messages)
if not response.tool_uses:
break
for tool_use in response.tool_uses:
result = await execute_tool(tool_use)
messages.append(result)
grep 本身不强,但「让 LLM 自己决定每一轮 grep 什么」就强了。你看到 Grep 结果是空的?改个关键字再搜。Read 出来的代码不像你以为的?再 Grep 几个相关函数看看。发现这个文件引用了另一个文件?跟过去看一眼。
RAG 是「考试发卷子」——一次性发材料,将错就错。Claude Code 是「现场探案」——边查边推理,走一步看一步。
两种方案代表两种哲学:
- RAG 派:LLM 不够强,工程帮它把材料准备好
- Claude Code 派:LLM 已经够强,工程给它工具,把决策权还给它
Anthropic 押注的是「模型会越来越强」,所以他们选择信任模型。
7.6 记忆机制:不用向量数据库
跟代码检索一样反直觉的,是 Claude Code 的记忆机制——它也不用向量数据库。
业界主流的记忆方案有四类,但都有共同病根:
| 方案 | 原理 | 病根 |
|---|---|---|
| 滑动窗口 | 保留最近 N 轮 | 关键信息和无关信息一起被丢 |
| 对话摘要 | LLM 定期总结旧对话 | 重要细节被压糊 |
| 向量检索 | embedding + top-K 召回 | 相似≠相关、黑盒、维护成本高 |
| 分层存储 | core/recall/archival 三层 | 概念多、检索还是靠 embedding |
四个共同病根:自由文本无约束、不区分类型、没有老化机制、重检索轻写入。
7.7 Claude Code 的两层记忆架构
Claude Code 用了两条独立的线:
静态层:CLAUDE.md 体系(声明式指令)
│ 你写好放那里,Agent 启动时全量加载
│ 解决「怎么协作」「遵守什么规则」
│ = 公司员工手册
│
动态层:自动记忆系统(学习式偏好)
│ Agent 互动中自动写成记忆文件
│ 下次对话按需检索
│ = 你的工作笔记
静态层在第二章已讲,这里展开动态层。
7.8 四种记忆类型
动态层只允许四种类型的记忆,其他一律不许写:
export const MEMORY_TYPES = [
'user', // 用户画像:你是谁
'feedback', // 行为偏好:你不喜欢什么
'project', // 项目动态:项目正在发生什么
'reference', // 外部指针:去哪查什么
] as const
feedback 和 project 类型有强制结构:
---
name: 不要用 mock 数据库
description: 集成测试必须连真实数据库
type: feedback
---
集成测试必须连真实数据库,不要用 mock。
**Why:** 上季度 mock 测试通过了但 prod 迁移挂了
**How to apply:** 所有标了「集成测试」的 case 都适用
为什么这么严?因为只记规则不记原因,遇到边界情况就抓瞎。加上 Why(踩过的坑),Agent 在边界情况能自己判断该不该破例。
7.9 该存什么 vs 不该存什么
跟「该存什么」同样重要的是「不该存什么」:
- 代码模式、架构、文件路径 → grep / CLAUDE.md 就能得到
- Git 历史和最近改动 →
git log/git blame是权威 - 调试方案和修复方法 → fix 已经在代码里
- CLAUDE.md 里已经写过的内容
- 临时任务状态和当前对话上下文
原则是:只记代码推不出来的东西。因为代码是活的,记忆是死的。如果记忆说「AuthService 在第 42 行」但代码已重构,这条记忆就变成了「权威的错误」,比没记忆还糟。
7.10 索引常驻 + 内容按需
100 条记忆全塞进 system prompt 会爆窗口,完全不塞 Agent 又不知道有什么。Claude Code 的方案是:
MEMORY.md 索引 → 始终加载进 system prompt(只含 name + description)
独立记忆文件 → 按需加载(真正需要时才读取完整内容)
就像翻工具书——你不需要把整本书背下来,但至少得知道目录里都有哪几章。
索引有双保险截断:
export const MAX_ENTRYPOINT_LINES = 200 // 最多 200 行
export const MAX_ENTRYPOINT_BYTES = 25_000 // 最多 25KB
两个限制任意一个先触发就截断。防的是「长行索引炸弹」——曾经观察到 200 行不到但加起来 197KB 的情况。
7.11 写入:Extract Memories 代理
记忆怎么写进去?不让主对话自己写(会分心、浪费 token),而是每轮对话结束后,后台单独跑一个 extractMemories 代理。
这个代理不是从零启动新对话,而是完美 fork 主对话,复用主对话的 prompt cache。意味着它不用重新加载几千 token 的 system prompt,只需要看着对话历史决定「有没有值得记的东西」,多花的钱很少。
7.12 检索:用 Sonnet 当选择器
下次对话来了,100 条记忆怎么挑出最相关的 5 条?
不用向量检索,让 Sonnet(小模型)来挑:
第一步:扫描所有记忆文件的前 30 行(只读 frontmatter)
第二步:把所有记忆的「标题清单」拼成文本发给 Sonnet
第三步:Sonnet 用 JSON schema 返回 top-5 文件名
System Prompt 写得很严苛:「Only include memories that you are certain will be helpful. Be selective and discerning.」——不确定就别选,宁可少选不可错选。
为什么用 Sonnet 不用更便宜的 Haiku?因为 Sonnet 比 Haiku 准很多,记忆相关性判错的代价远大于多花的那点钱。
还有两个过滤细节:
alreadySurfaced:上一轮已露过脸的记忆这次排除recentTools:最近用过的工具的「用法文档」不选(正在用就不用看文档),但「警告、坑点」记忆要保留
7.13 老化警告
记忆注入时用 <system-reminder> 标签包裹,并加上时间警告:
<system-reminder>
This memory was saved 5 days ago. Verify it's still accurate before acting on it.
[记忆内容]
</system-reminder>
今天/昨天 → 不警告
2 天以前 → 主动加警告
这解决了向量检索最致命的问题——「权威的错误」。过时记忆不是被闭眼信,而是带着「这是历史,不是现状」的心态去用,发现冲突就更新或忽略。
实践要点
- 代码检索不用 RAG 的六个原因:冷启动慢、实时性差、不精确、token 浪费、黑盒、决策权不在模型
- 三件套 + 子 Agent 分层检索:< 3 次查询直接三件套,> 3 次派 Explore 子 Agent
- 记忆只记代码推不出来的东西——代码是活的,记忆是死的
- 索引常驻 + 内容按需——任何「总量大但少数需要展开」的场景都适用
- 小模型做选择题 > 向量检索——只要候选集不大(几百以内),用模型选更准更便宜
- 记忆要有老化机制——2 天前的记忆加 stale 警告,逼模型主动验证
第八章 多 Agent 协作:SubAgent 实现机制
8.1 为什么一个 Agent 不够用
单个 Agent(一个 LLM + 一堆工具 + 一个循环)在简单任务上挺好用。但真实项目里,一个问题马上就冒出来。
假设你让一个 Agent 做:「调研 React 18 新特性,在项目里实现一个 useTransition 的例子,最后做代码评审。」三个麻烦同时出现:
- 上下文会爆炸:调研阶段要看大量文档,实现阶段要读项目代码,评审阶段又要重新读实现。三个阶段的内容全塞进一个上下文,token 蹭蹭涨
- 职责混乱:一个 Agent 既当研究员又当程序员又当评审员,容易跑偏——调研到一半就开始写代码,代码写到一半又去查文档
- 没法并发:查文档的时候项目代码干等着
Multi-Agent 的思路就是老板带团队:把任务拆给不同「专家」,研究员去调研、工程师去写代码、评审员去挑错。老板只看大方向、收结果。
8.2 三套不同的机制
Claude Code 源码里跟「多 Agent」沾边的代码其实有三套:
┌──────────────────────────────────────────────────────┐
│ 常规 Subagent │
│ 主 Agent 派子 Agent 出去,子跑完把结果交回来 │
│ 对应「父子型」 │
├──────────────────────────────────────────────────────┤
│ Fork Subagent │
│ 派一个「字节级相同」的分身,复用父 Agent 的 Prompt Cache │
│ 省 80-90% 成本 │
├──────────────────────────────────────────────────────┤
│ Coordinator 模式 │
│ 主 Agent 退化成纯协调者,只派 worker、收结果、合成 │
│ 对应「主从型」,最大化并发 │
└──────────────────────────────────────────────────────┘
8.3 隔离机制:两个维度
隔离是多 Agent 设计最关键的一环。如果隔离不好,一个子 Agent 污染了父 Agent 的状态,整个系统就乱套了。
工具隔离——三道准入门:
第一道门:全局黑名单(所有子 Agent 通用)
· 禁止派新 subagent 的工具(防递归嵌套)
· 禁止主动问用户问题的工具(子 Agent 不抢对话权)
· 禁止切换规划模式的工具(规划权归主 Agent)
· 禁止停止其他任务的工具(任务管理是主线程专属)
第二道门:自定义 Agent 加严黑名单
· 用户自己写的 Agent 比内置的再严一道
第三道门:后台异步 Agent 走白名单
· 只准用事先圈定的一小批工具
· 白名单哲学:默认不准用,明确列出的才能用
源码里的过滤函数就一个 filter:
export function filterToolsForAgent({ tools, isBuiltIn, isAsync, permissionMode }): Tools {
return tools.filter(tool => {
if (tool.name.startsWith('mcp__')) return true
if (ALL_AGENT_DISALLOWED_TOOLS.has(tool.name)) return false
if (!isBuiltIn && CUSTOM_AGENT_DISALLOWED_TOOLS.has(tool.name)) return false
if (isAsync && !ASYNC_AGENT_ALLOWED_TOOLS.has(tool.name)) return false
return true
})
}
上下文隔离——按字段粒度决策:
不是一刀切地「全共享」或「全新建」,而是每一项状态单独判断:
| 状态 | 决策 | 原因 |
|---|---|---|
| 读文件缓存 | 克隆一份 | 子 Agent 读了文件,父会误以为自己也读过,跳过不读 |
| 写全局状态 | 直接关闭(设为空操作) | 两边同时改同一份状态,界面会花 |
| 注册后台任务 | 保留通路 | 否则子 Agent 起的后台进程变成孤儿没人回收 |
| Agent ID + 深度 | 新建,深度+1 | 可追踪嵌套层级,超阈值报警防失控 |
export function createSubagentContext(parentContext, overrides): ToolUseContext {
return {
readFileState: cloneFileStateCache(parentContext.readFileState), // 克隆
setAppState: () => {}, // 关闭写权限
setAppStateForTasks: parentContext.setAppStateForTasks ?? parentContext.setAppState, // 保留任务通路
agentId: overrides?.agentId ?? createAgentId(), // 独立 ID
queryTracking: {
chainId: randomUUID(),
depth: (parentContext.queryTracking?.depth ?? -1) + 1, // 深度+1
},
}
}
所谓上下文隔离,不是一刀切地全隔离或不隔离,而是按每个状态的语义单独决策。这个细腻劲儿正是工业级产品稳定跑的根基。
8.4 父子通信:两种形态
默认形态——单向通知:
父 Agent 派子 Agent 出去、等结果,长任务(超 2 分钟)转后台后子 Agent 回头发完成通知。父 Agent 不能中途给在跑的子 Agent 插话,消息基本是子→父 单向。
完成通知被包装成 XML,伪装成一条用户消息塞进父 Agent 的对话历史:
<task-notification>
<task-id>agent-a1b</task-id>
<output-file>/tmp/xxx.txt</output-file>
<status>completed</status>
<summary>Agent "Investigate auth bug" completed</summary>
<result>Found null pointer in src/auth/validate.ts:42...</result>
<usage>
<total_tokens>12345</total_tokens>
<tool_uses>8</tool_uses>
<duration_ms>34567</duration_ms>
</usage>
</task-notification>
为什么用 XML 不用结构化对象?三个原因:LLM 对 XML 天然友好、纯文本可直接塞进对话历史、伪装成用户消息天然复用 agentic loop 处理逻辑。这种「把系统事件伪装成对话」的设计在 LLM 应用里很值得学。
团队模式(agent-teams)——双向对讲:
开启团队模式后,父 Agent 多了一个 SendMessage 工具,可以往子 Agent 的「信箱」(pendingMessages 数组)里扔字条。子 Agent 在每轮循环边界自己捡字条,作为「用户消息」注入对话历史。
如果子 Agent 已经完成停下来了,父 Agent 发 SendMessage 会自动唤醒它——从磁盘 transcript 恢复完整对话历史,拼上新消息重新跑。子 Agent 即使完成了也不是「死了」,随时可被叫醒。
8.5 Fork Subagent:省钱又省延迟的隐藏大招
每派一个常规子 Agent,如果它有独立的 system prompt(上万 token),API 要从头算一遍。两个代价:钱(input token 重新算)和延迟(首 token 等更久)。
Prompt Cache 可以缓解——如果 API 请求前缀跟之前一样,这段前缀走缓存,只要原价 10%。但缓存命中条件极严:字节级别完全相同。一个空格不对都 miss。
Fork Subagent 的思路:派一个子 Agent,它的 system prompt 和工具池跟父 Agent 完全一样,复用父的缓存。
要做到字节级一致,五样必须对齐:系统 prompt 内容、用户上下文、系统上下文、工具池顺序和定义、对话历史前缀。
Fork 的 system prompt 生成函数直接返回空字符串——不是偷懒,而是不重新生成,直接用父 Agent 已渲染好的那份字节。重新调生成函数可能有微小差异(功能开关缓存状态变了),一个字符不同缓存就没了。
export const FORK_AGENT = {
agentType: FORK_SUBAGENT_TYPE,
tools: ['*'], // 用父的完整工具池
maxTurns: 200,
model: 'inherit', // 继承父的模型
permissionMode: 'bubble',
getSystemPrompt: () => '', // 返回空串!直接用父的字节
}
Fork 把子 Agent 成本降到原来的 10% 左右。这意味着原本成本考虑不敢派的活,现在都能派了,Agent 系统的能力边界扩大了。成本优化本身就是能力的一部分。
8.6 Coordinator 模式:真正的多 Agent 并行
Coordinator 模式下,主 Agent 退化成纯协调者——只做三件事:派 worker、收结果、合成答案。不再自己读代码写代码。
System Prompt 强制约束:
You are a coordinator. Your job is to:
- Help the user achieve their goal
- Direct workers to research, implement and verify code changes
- Synthesize results and communicate with the user
- Answer questions directly when possible, don't delegate work
that you can handle without tools
Coordinator 有一句核心 Prompt:「Parallelism is your superpower. Workers are async. Launch independent workers concurrently whenever possible.」
底层支持是:一条 assistant 消息里可以出现多个派 worker 的工具调用,底层并发执行。协调者一口气派三个 worker 分别调研 auth、session、token 三个模块,三个同时干活,谁先完成谁先通知。
典型任务流水线分四个阶段:
| 阶段 | 谁来做 | 目的 |
|---|---|---|
| 调研 | Workers(并行) | 调查代码库、找文件、理解问题 |
| 合成 | 协调者本人 | 读完发现、理解问题、写实现规格 |
| 实现 | Workers | 按规格做具体修改 |
| 验证 | Workers | 测试改动是否真的工作 |
注意中间「合成」阶段是协调者亲自做。Prompt 反复强调:不要偷懒让 worker「based on your findings, implement the fix」,而是自己把 findings 读懂、写成规格再派下去。协调者必须理解而不能转发。
还有一个 Continue vs Spawn 决策:新任务跟 worker 现有上下文高度相关就续命老 worker(省重新加载上下文的开销),不相关或之前走偏了就派新 worker。验证类工作永远派新 worker(避免自己验自己)。
8.7 五条 Multi-Agent 设计原则
从 Claude Code 源码里可以提炼出五条直接可用的原则:
- 上下文隔离要按字段粒度做——不是一刀切全共享或全新建,每个状态单独决策
- 通信走消息不走函数调用——天然异步、天然支持并发、天然兼容 agentic loop
- 工具权限要分级管控——全局黑名单 + 类型黑名单 + 异步白名单
- 缓存友好是一种架构能力——设计 subagent 时考虑 prompt 前缀能否复用,能省 80-90% 成本
- 并行优先 + 协调者合成——能并行的绝不串行,协调者亲自合成不转发
实践要点
- 工具隔离三道门是递归防护的基础——不给子 Agent 派子 Agent 的权力
- 上下文隔离按字段决策——读缓存克隆、写状态关闭、任务通路保留、深度+1
- 完成通知伪装成用户消息——天然复用 agentic loop,不需要额外状态机
- Fork Subagent 在缓存友好场景能省 90% 成本——但与 Coordinator 模式互斥
- Coordinator 模式的协调者必须合成不转发——理解全局做决策,不当传话筒
- 验证类工作永远派新 worker——不能让刚写完代码的 worker 自己验自己
第九章 系统提示词与 AI 编程方法论
9.1 Fable 5 系统提示词泄漏
2025 年,Claude Code(内部代号 Fable 5)的一份约 1600 行的系统提示词被泄漏。这份 Prompt 给了我们一个罕见的窗口——看清一个工业级 AI Agent 是怎么给自己「立规矩」的。
这份 Prompt 大致分两半:前半本定义角色和行为准则,后半本定义工具(17-18 个)。这里挑几个最值得学习的设计。
9.2 ask_user_input:WHEN NOT TO USE 比 WHEN TO USE 详细
ask_user_input 是「向用户提问」的工具。有趣的是,它的 Prompt 里「什么时候不要用」比「什么时候要用」写得还详细:
WHEN NOT TO USE:
- 不要用提问逃避给出你自己的观点
- 不要问用户能从代码里推断出来的事
- 不要问偏好(除非真的影响结果且无法合理默认)
为什么防「用提问逃避给观点」?因为模型有一种倾向——遇到不确定的事就问用户,显得很谨慎。但这其实是在偷懒,把本该自己判断的事推给用户。好 Agent 应该先给出自己的判断和理由,只在真正需要用户决策时才问。
9.3 create_file:参数顺序逼思考
create_file 工具的参数顺序是:description → path → file_text。
注意 description 排在 path 前面。这不是随便排的——它逼模型先想清楚「这个文件是干什么的」(description),再想「放哪里」(path),最后才写内容(file_text)。
如果顺序反过来(先 path 再 description),模型可能先随便定个路径就开始写,写到一半才发现目的不明确。参数顺序是一种隐式的思维引导。
9.4 message_compose:给策略不给语气
message_compose 是「帮用户写消息」的工具。Prompt 要求:给用户策略建议(说什么、怎么说),但不替用户决定语气版本。
为什么不替用户选语气?因为语气是高度个人化的——同样一个「拒绝合作」的消息,有人喜欢委婉、有人喜欢直接。Agent 给策略(「先肯定对方、再说明限制、最后给替代方案」),让用户自己选语气。
9.5 web_search:能稳的别搜,会变的必搜
web_search 工具的指导原则很精炼:
- 能稳的别搜:稳定知识(编程语法、历史事件)不搜,模型自己就知道
- 会变的必搜:会变的信息(最新版本号、当前天气、新闻)必搜
- 训练时见过个大概 ≠ 现在还了解:模型可能「知道」某个 API 的大致用法,但具体参数可能已经变了
9.6 先读 SKILL.md 是 required first step
Prompt 里明确写着:遇到 Skill 时,先读 SKILL.md 是「required first step」。甚至有一个叫 product-self-knowledge 的 Skill,专门让 Agent 查自家产品的知识——连自家产品知识也不信记忆,要现查。
为什么?因为产品知识会更新。你今天记住的 API 文档,明天可能就改了。最稳的办法是每次都查最新的。
9.7 不过度格式化
Prompt 里有几条关于输出风格的约束:
- 拒绝时不用 bullet points——直接说「不行,因为…」,不要列一堆理由显得在推脱
- 不感谢用户 reach out——「感谢您联系我们!」这种话是客服腔,Agent 不需要
- 不过度格式化——简单回答就用纯文本,不要动不动就列表格画图
这些约束的本质是防「AI 上瘾式行为」——模型有时会过度使用格式化来显得「专业」,但其实降低了信息密度。
9.8 安全红线:讲原则藏机制
安全相关的 Prompt 有一个原则:讲原则藏机制。
它告诉模型「不要做什么」,但不告诉它「我们怎么检测你做了没」。因为如果模型知道检测逻辑,它可能会钻空子——「只要不触发检测就行」。不告诉它检测逻辑,它就只能从原则层面遵守。
还有一条很精辟的判断标准:
如果发现自己正在 reframing 一个请求使其 appropriate,那就是 refuse 的 signal。
翻译:如果你发现自己在「重新包装」一个不当请求让它看起来合理,那说明你应该直接拒绝。不要玩文字游戏。
9.9 System Prompt 的动态组装
实际运行中,System Prompt 不是一份静态文本,而是动态组装的:
角色定义
↓
安全红线
↓
行为准则
↓
操作安全
↓
工具使用指南
↓
Git 安全
↓
输出风格(25 词限制)
↓
环境信息
───────────────────
__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__ ← 分割线
───────────────────
动态部分(工具列表、权限、MCP server)
分割线以上是静态的(基本不变),以下是动态的(每次可能变)。这种分割让 Prompt Cache 能命中静态部分——API 只需要重新算动态部分,省 90% 费用。
Claude Code 还有三级缓存:全局缓存(跨会话)、组织缓存(跨用户同组织)、会话缓存(单会话内)。
9.10 Anthropic 40 万次会话研究:用好 AI 编程的关键不是会写代码
2026 年 6 月,Anthropic 发布了一份基于 40 万次真实会话的研究:《Agentic coding and persistent returns to expertise》。
样本量:约 23.5 万用户、40 万次会话,时间跨度半年。这个体量意味着它说的不是某个大佬的个人感受,而是几十万人沉淀出的统计规律。
核心发现 1:人决定「做什么」,Agent 决定「怎么做」。
用户握着约 70% 的规划类决策(功能要解决什么问题、做成什么样),Claude 握着约 80% 的执行类决策(用哪个函数、代码怎么落地)。「怎么写」这部分本是程序员最值钱的硬功夫,现在大头被 Agent 接走了。
核心发现 2:会写代码的护城河正在贬值。
在产出代码的会话里,软件工程师成功率 34%,其他职业 29%——只差 5 个点。管理岗、销售、法务这些跟编程八竿子打不着的人,照样把活干成了。甚至管理类的成功率是所有职业里最高的。
核心发现 3:差距在 prompt 质量。
新手发一条 prompt,平均撬动 Claude 做 5 个动作、600 字产出;专家发一条,撬动 12 个动作、3200 字。动作差 2.4 倍,产出差 5 倍。
差距来自「懂行」。一个没踩过坑的后端说「帮我写个扣库存接口」,Claude 给你一段 update set stock = stock - 1,本地跑没问题。一个被高并发毒打过的老手说「Redis 预扣加 Lua 脚本保证原子性、热点 key 前面挂限流、扣成功异步落库做最终一致」,Claude 噼里啪啦把一整套全搭出来。
两条 prompt 的差别跟会不会写 Java 语法无关,差的是后者懂「高并发下直接 update 会超卖」「热点 key 得防击穿」——这是架构经验,是领域专业度。
核心发现 4:翻车时差距更大。
会话遇到麻烦时,新手 19% 概率放弃,中级和专家只有 5-7%。懂行的人一眼能看出哪儿不对、知道往哪个方向救;不懂的人面对「看着没毛病、一上量就炸」的代码,根本察觉不到。
核心发现 5:及格线特别低。
成功率曲线:新手 15% → 中级 28% → 专家 33%。最大的跳是从新手到中级(+13%),从中级到专家只多了 5 个点。
你不需要是二十年行业老炮。只要对手上的事有个「够用的把握」,知道正常活该长什么样、哪些是关键、做出来对不对,你就跨过了那道收益最大的坎。
9.11 三件该练的功夫
这份研究最终落在三件可操作的功夫上:
第一,把问题说清楚。 不是文采问题,是把「要解决什么、有哪些约束」交代明白。「帮我写扣库存接口」和「Redis 预扣加 Lua 原子扣减、再加限流防超卖」是两个世界。后者多出来的每一句,都是你的架构判断,也是 Claude 多替你干的活。
第二,把活拆好。 大需求别囫囵丢过去。秒杀下单拆成:限流挡请求 → Redis 预扣 → MQ 削峰 → 异步落库。你拆得清楚,它执行起来才不跑偏,哪一环出问题你也立马定位得到。
第三,会验收。 它吐出来的代码对不对,你得有本事判断。「本地全绿、一并发就超卖」的坑,靠的是你心里有杆秤、知道得上压测才拦得下来。AI 说没问题,你不能它说行你就信。
三件事没有一件考编码功底,考的全是对这件事本身理解得够不够透。
9.12 这半年任务在变
研究还发现一个趋势:修 bug 的会话从 33% 降到 19%,运维从 14% 涨到 21%,写作和数据分析翻倍从 10% 涨到 20%,整体任务价值平均涨了 27%。
Agent 正在从「帮你打补丁」的小工,变成能扛更值钱活儿的主力。人也顺势从埋头抠 bug,挪到了更靠近「想清楚要什么」的位置上。
越往后,「懂行」越值钱。
实践要点
- System Prompt 的「WHEN NOT TO USE」比「WHEN TO USE」重要——防的是模型的偷懒倾向
- 参数顺序是隐式思维引导——先想目的再想路径最后写内容
- 讲原则藏机制——不要告诉模型检测逻辑,否则它会钻空子
- Prompt Cache 的静态/动态分割能省 90% 费用——系统提示词要分清不变和会变的部分
- 用好 AI 编程靠的是领域专业度不是编码能力——把问题说清楚、把活拆好、会验收
- 及格线很低——对手上的事有「够用的把握」就能拿走大部分收益
- 这个时代真正稀缺的护城河:不是你会背多少语法、记多少 API(Agent 比你熟),而是你在某个领域里比 AI 更懂这件事本身
总结
从基础使用到源码架构,从上下文压缩到记忆机制,从代码检索到多 Agent 协作,Claude Code 展示了一个工业级 AI Agent 应有的样子。它的设计哲学可以浓缩为一句话:
不堆复杂度,把已经成熟的简单组件(文件系统 + LLM + 工具循环)组合出比花哨方案更好用的东西。
不用向量数据库,用 grep + 小模型选择;不用复杂的 ReAct,用极简的 Tool-Use Loop;不用滑动窗口,用五层压缩金字塔;不用同步函数调用,用消息队列 + XML 通知。
每一块拆开看都不复杂,但组合在一起,就成了一个能支撑 Anthropic 级别产品的工业级系统。
而对于我们每一个使用 AI 编程的人来说,最重要的启示来自那份 40 万次会话的研究:用好 AI 编程的关键不是会写代码,是懂行。把问题说清楚、把活拆好、会验收——这三件事,没有一件考编码功底,考的全是你对这件事本身理解得够不够透。
这个时代真正稀缺的护城河,不是你会背多少语法、记多少 API。那些 Agent 比你熟得多。
稀缺的是,你在某个领域里,比 AI 更懂这件事本身。