Claude Code实战——AI编程工程深度拆解

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 安装技能插件(第三章详讲)
  • HooksPreToolUse / PostToolUse 钩子,在工具执行前后插入自定义逻辑
  • 子代理(SubAgent):通过 /agents 管理独立的子 Agent 实例(第七章详讲)

模型控制类:

  • /model 切换模型(Opus / Sonnet / Haiku)
  • /effort 设置推理深度:lowmediumhighxhighmax
  • ultrathink 关键词:临时把推理挡位拉到最高(新版唯一生效的关键词)

1.3 子代理 vs Skills:两套扩展机制的区别

这是初学者最容易混淆的概念。两者都能「扩展 Claude Code 的能力」,但机制完全不同:

┌─────────────────────────────────────────────────┐
│              Skills(技能)                       │
│  在主对话上下文中加载一份 .md 指南                 │
│  模型读完指南后,用主对话的工具去执行               │
│  共享主 agent 的上下文窗口                         │
│  适合:领域知识、操作规范、验证流程                 │
├─────────────────────────────────────────────────┤
│            SubAgent(子代理)                      │
│  启动一个独立的 agent 实例                         │
│  有自己独立的上下文窗口和工具池                     │
│  跑完只把结论返回给主 agent                        │
│  适合:大规模探索、隔离任务、并行调研               │
└─────────────────────────────────────────────────┘

一句话区分:Skill 是给主 agent 看的说明书,SubAgent 是派出去的独立员工

实践要点

  1. 新手从 Normal 模式开始,确认 AI 的修改方向正确后再切 Auto-accept
  2. 复杂任务必走 Plan Mode:先对齐再执行,避免跑偏后回不了头
  3. ultrathink 不是万能的:它只在需要深度推理的场景(架构设计、复杂 bug)才有价值,日常写代码用默认挡位即可
  4. /context 要勤看:上下文用到 70% 以上就该 /compact 了,别等自动触发
  5. 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 遵循四条原则:

  1. :200 行以内,超了就拆
  2. 具体可验证:写「提交前跑 npm test」不写「确保质量」
  3. 告诉为什么:写「不要用 mock 测试,因为上季度 mock 通过了但 prod 挂了」——有了 Why,模型在边界情况能自己判断该不该破例
  4. 持续更新:用 /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(坑点清单)是含金量最高的部分。它是你用血泪换来的经验,也是模型最容易忽略的地方。

实践要点

  1. CLAUDE.md 是写给 Agent 的,不是写给人的——每写一句都问自己「模型能执行吗?」
  2. 200 行是红线——超了就拆到 .claude/rules/,用 paths 条件按需加载
  3. 每条规则带上 Why——让模型在边界情况能自主判断
  4. Gotchas 优先写——「别用 == 比较 null」比「写高质量代码」有用一万倍
  5. /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 的自然演化路径是:沙盒测试 → 口碑传播 → 正式发布。先在自己项目里用,验证有效后分享给团队,最后发布到社区。

实践要点

  1. description 写触发条件不写摘要——「当用户需要…时使用」比「这是一个…工具」有效得多
  2. Gotchas 是 Skill 的灵魂——没有坑点清单的 Skill 只是一半的 Skill
  3. 验证类 Skill 投入产出比最高——优先写「检查类」而非「生成类」
  4. references/ 目录利用好渐进式披露——详细文档放这里,SKILL.md 里只放入口指引
  5. 不要一个 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,核心理念三句话:

  1. 往死里盘问:在开始写代码前,把需求里所有模糊的地方全问一遍
  2. 一次只问一个:不要一次抛十个问题,一次一个,问完再问下一个
  3. 能翻代码就别问:能在代码里找到答案的,不要问用户

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 更适合需要自动生成测试和文档的场景,但它耗时长、问题少,容易漏掉关键需求点。

实践要点

  1. Vibe Coding 适合原型,SDD 适合产品——项目要长期维护就上 SDD
  2. spec-kit 五步不是瀑布流——规约随时可改,这才是 SDD 和瀑布流的本质区别
  3. grill-me 在 Plan Mode 之前跑——先想清楚再出方案
  4. grill-me 的「能翻代码就别问」原则——减少用户负担,能用工具查的就自己查
  5. 主观决策一定要让用户拍板——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 是发一条特殊消息让模型继续未完成的输出。

实践要点

  1. Tool-Use Loop 的极简设计是核心理念——不要在应用层模拟模型思考,信任模型
  2. StreamingToolExecutor 的并行/串行规则值得借鉴——只读并行、写操作串行
  3. 工具失败要合成假 result——API 协议要求 tool_use 和 tool_result 配对
  4. 17 种退出原因是工业级设计的体现——不是简单的「成功/失败」二元判断
  5. 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 正在干活时触发压缩,不能让一个新问题打断节奏。

实践要点

  1. 五层金字塔是分层防御——不要一上来就全量压缩,先用轻量手段
  2. 全量重写比保留最近 N 轮更有效——Lost in the Middle 让中间消息本来就看不清
  3. 不同信息走不同通道——语义进摘要、状态走附件、永久指令靠缓存重载
  4. 摘要 Prompt 要防呆——「禁止工具调用」要前后包夹,模型真的会手痒
  5. Circuit Breaker 是生产环境的标配——任何重试机制都要有熔断
  6. 上下文管理不是省 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」,向量库返回 getUserByNamegetUserByEmailfetchUserInfo——全是相似的,但没有一个是对的。

痛点 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

feedbackproject 类型有强制结构:

---
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 天以前 → 主动加警告

这解决了向量检索最致命的问题——「权威的错误」。过时记忆不是被闭眼信,而是带着「这是历史,不是现状」的心态去用,发现冲突就更新或忽略。

实践要点

  1. 代码检索不用 RAG 的六个原因:冷启动慢、实时性差、不精确、token 浪费、黑盒、决策权不在模型
  2. 三件套 + 子 Agent 分层检索:< 3 次查询直接三件套,> 3 次派 Explore 子 Agent
  3. 记忆只记代码推不出来的东西——代码是活的,记忆是死的
  4. 索引常驻 + 内容按需——任何「总量大但少数需要展开」的场景都适用
  5. 小模型做选择题 > 向量检索——只要候选集不大(几百以内),用模型选更准更便宜
  6. 记忆要有老化机制——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 源码里可以提炼出五条直接可用的原则:

  1. 上下文隔离要按字段粒度做——不是一刀切全共享或全新建,每个状态单独决策
  2. 通信走消息不走函数调用——天然异步、天然支持并发、天然兼容 agentic loop
  3. 工具权限要分级管控——全局黑名单 + 类型黑名单 + 异步白名单
  4. 缓存友好是一种架构能力——设计 subagent 时考虑 prompt 前缀能否复用,能省 80-90% 成本
  5. 并行优先 + 协调者合成——能并行的绝不串行,协调者亲自合成不转发

实践要点

  1. 工具隔离三道门是递归防护的基础——不给子 Agent 派子 Agent 的权力
  2. 上下文隔离按字段决策——读缓存克隆、写状态关闭、任务通路保留、深度+1
  3. 完成通知伪装成用户消息——天然复用 agentic loop,不需要额外状态机
  4. Fork Subagent 在缓存友好场景能省 90% 成本——但与 Coordinator 模式互斥
  5. Coordinator 模式的协调者必须合成不转发——理解全局做决策,不当传话筒
  6. 验证类工作永远派新 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 工具的参数顺序是:descriptionpathfile_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,挪到了更靠近「想清楚要什么」的位置上。

越往后,「懂行」越值钱。

实践要点

  1. System Prompt 的「WHEN NOT TO USE」比「WHEN TO USE」重要——防的是模型的偷懒倾向
  2. 参数顺序是隐式思维引导——先想目的再想路径最后写内容
  3. 讲原则藏机制——不要告诉模型检测逻辑,否则它会钻空子
  4. Prompt Cache 的静态/动态分割能省 90% 费用——系统提示词要分清不变和会变的部分
  5. 用好 AI 编程靠的是领域专业度不是编码能力——把问题说清楚、把活拆好、会验收
  6. 及格线很低——对手上的事有「够用的把握」就能拿走大部分收益
  7. 这个时代真正稀缺的护城河:不是你会背多少语法、记多少 API(Agent 比你熟),而是你在某个领域里比 AI 更懂这件事本身

总结

从基础使用到源码架构,从上下文压缩到记忆机制,从代码检索到多 Agent 协作,Claude Code 展示了一个工业级 AI Agent 应有的样子。它的设计哲学可以浓缩为一句话:

不堆复杂度,把已经成熟的简单组件(文件系统 + LLM + 工具循环)组合出比花哨方案更好用的东西。

不用向量数据库,用 grep + 小模型选择;不用复杂的 ReAct,用极简的 Tool-Use Loop;不用滑动窗口,用五层压缩金字塔;不用同步函数调用,用消息队列 + XML 通知。

每一块拆开看都不复杂,但组合在一起,就成了一个能支撑 Anthropic 级别产品的工业级系统。

而对于我们每一个使用 AI 编程的人来说,最重要的启示来自那份 40 万次会话的研究:用好 AI 编程的关键不是会写代码,是懂行。把问题说清楚、把活拆好、会验收——这三件事,没有一件考编码功底,考的全是你对这件事本身理解得够不够透。

这个时代真正稀缺的护城河,不是你会背多少语法、记多少 API。那些 Agent 比你熟得多。

稀缺的是,你在某个领域里,比 AI 更懂这件事本身。