# 把这些思想用在你的项目里

本书分析了 Claude Code 里的工程决策。这一章把最有价值的思想提炼成可以直接应用的实践建议。

> ⚠️ **先评估，再采用**：本章不是建议你把六个思想全部实现——它是**菜单**，不是**套餐**。每个思想都有适用条件和过度工程的信号。请先评估你的项目处于什么阶段（原型期、增长期还是规模化期），再选择性地采用。"照搬大厂实践"是工程师最常犯的错误之一。

> 🌍 **行业背景**：Claude Code 的工程思想并非孤立存在——整个 AI 应用工程领域正在快速形成共识。下面介绍的每个思想，我们都会对比主流框架的实现方式，帮你做出知情的选择，而非只提供 Claude Code 一家的做法。
>
> - **LangChain / LlamaIndex** 将"工具调用 + Agent 循环"标准化为通用模式，降低了准入门槛，但在 2024-2025 年因过度抽象和"抽象泄漏"（指框架号称帮你屏蔽了底层细节，但实际使用中底层问题总是"漏"出来，你还是得自己处理——就像买了全自动洗碗机，结果老是要手动预洗才能干净）被大量项目抛弃，不少团队转向了更轻量的 LiteLLM / Instructor 方案。
> - **Vercel AI SDK** 将流式输出和 token 管理封装为前端友好的 API，代表了"token 是一等公民"思想在 Web 端的落地。
> - **CrewAI / AutoGen** 探索了多 Agent 协作的不同编排模式——CrewAI 用 role-based 的面向对象思路，AutoGen 用对话驱动的多轮协商。
> - **Cursor** 的 Agent 模式也采用了 tool-use + permission 循环，其可扩展性体现在 `.cursorrules` 规则文件的分层系统上。
>
> Claude Code 选择**不使用任何框架**而自建 Agent 循环，这个决策本身就值得思考。它的独特价值在于，作为少数在**真正的生产规模**（大规模日活用户、周 token 消耗量达到十亿量级，源码注释 `loadAgentsDir.ts` 提及 "~5-15 Gtok/week across 34M+ Explore spawns"，具体官方数字以 Anthropic 公告为准）下经受过验证的系统，它证明了这些思想不只是理论推演。但这也意味着：如果你的项目规模远小于此，直接照搬可能适得其反。

---

## 思想 1：在等待时间里藏工作

> 💡 **通俗理解**：就像**早上起床时的并行优化**——按下热水壶开关（启动 I/O），趁烧水的时间洗漱穿衣（模块加载），等穿好衣服水也开了。关键是找到那些"反正要等"的时间窗口，把有用的工作塞进去。

**核心公式**：找到"必须等待 X"的地方 → 在等待期间做所有不依赖 X 的工作。

**在你的 AI 应用里**：

当你调用 LLM API 等待响应时（可能 3-30 秒），检查：
- 能不能根据流式输出的前几个 token 判断响应方向，提前加载数据？
- 有没有不依赖 AI 响应的 UI 更新可以先做？
- 有没有后台任务（日志、分析、缓存预热）可以同时运行？

**最小实现（MVP 级）**：

```javascript
// 不好的写法——串行等待
const response = await llm.complete(prompt)
await fetchRelatedData(response)

// 稍好——并行请求，但这只是"并行"，还不是"投机执行"
const [response, sideData] = await Promise.all([
  llm.complete(prompt),
  prefetchLikelyNeededData()
])
```

**核心难点：猜错了怎么办？** 上面的 `Promise.all` 只是简单的并行请求——任何写过异步 JavaScript 的人都会。真正的投机执行之所以叫"投机"，是因为必须处理**预测错误时的回滚与隔离**。`Promise.all` 在任一请求失败时会整体 reject，这意味着投机请求的失败会连带主请求一起崩溃——这在生产环境中不可接受。

**生产级实现**：

```javascript
// 真正的投机执行需要：独立错误隔离 + 可取消 + 结果验证
const controller = new AbortController()

// ① 先 kick off 投机任务，但不 await（让它在后台跑）
// ② 独立 catch 保证它的失败不会冒泡影响主流程
const specPromise = prefetchLikelyNeededData({ signal: controller.signal })
  .catch(err => ({ __failed: true, err }))  // 错误隔离：投机失败静默转成一个标记对象

// ③ 主请求单独 await——主流程的节奏由主请求决定
const mainResult = await llm.complete(prompt)

// ④ 主请求已完成，此时根据主结果决定是否还需要投机结果
//    如果不再需要，立刻 abort 投机任务（通过 AbortController 取消已发出的请求/fetch）
if (stillNeedsPreloadedData(mainResult)) {
  const specResult = await specPromise  // 快速路径：投机已经跑完则零等待；否则等它完成
  if (!specResult.__failed && isStillRelevant(specResult, mainResult)) {
    usePreloadedData(specResult)
  } else {
    controller.abort()
    usePreloadedData(await fetchRelatedData(mainResult))
  }
} else {
  // 主结果已经不再需要预取数据——立刻取消，不等待
  controller.abort()
}
```

> ⚠️ **不要用 `Promise.allSettled([main, spec])`**：`allSettled` 会等待**两个** Promise 都进入终态（fulfilled/rejected）才 resolve——这意味着投机任务变成了关键路径，失去了"猜错立即丢弃"的特性。投机执行的本质是"主请求跑自己的节奏，投机任务**尽力而为**"——主请求完成后想用就用、不想用就 `abort()` 丢掉；绝不能让主请求被投机任务拖慢。

Claude Code 源码中的投机执行正是这个思路：使用 `AbortController` 实现可取消性，配合独立的错误边界保证投机失败不影响主流程。复杂度比简单的 `Promise.all` 高一个数量级。

> 📚 **课程关联**：CPU 分支预测是个好类比，但别忘了类比的另一半——CPU 投机执行之所以能工作，靠的是**重排序缓冲区**（ROB）保存预执行结果和**精确异常处理**实现干净回滚。对应到你的代码里，`AbortController` 就是你的 ROB（取消未完成的预取），独立的 `.catch()` 错误隔离 + 主请求单独 await 的结构就是"精确异常处理"——猜错了能立刻丢弃，不拖慢主流程。没有这套机制的"投机执行"只是并行请求，名不副实。

> 🚫 **过度工程信号**：如果你的 AI 应用是低频使用的内部工具（比如日均请求 < 1000 次），投机执行带来的延迟优化（省掉几百毫秒）几乎感知不到，但代码复杂度和调试难度会显著上升。在这种场景下，简单的串行调用反而是最佳选择。**经验法则：当 API 调用延迟占总交互时间 > 30% 且用户量足以让你关心 P95 延迟时，再考虑投机执行。**

---

## 思想 2：token 是一等公民

**核心问题**：你的系统里哪些操作消耗 token？每次消耗多少？有没有不必要的消耗？

**实践清单**：

- [ ] System prompt 里有没有可以省略的内容？（对于只读操作，可以省去写作规范类的指令）
- [ ] 是否在每次 API 调用前都检查 prompt cache 的兼容性？
- [ ] 是否对不同子任务使用了不同大小的模型？（摘要用 Haiku，主任务用 Sonnet/Opus）
- [ ] 工具描述是否有长度限制？（防止某个工具描述占满整个 context）
- [ ] 是否有阈值控制"后台 AI 任务"的触发频率？（防止每次响应都触发记忆提取）

**缓存参数的注意事项**：

如果你的系统有多个 AI 实例共享同一个 prompt cache（比如主请求和后台监控），确保所有实例使用**完全相同**的 API 参数（model、system、tools、messages、thinking config）。任何参数差异都会破坏缓存共享，可能造成数倍的成本上涨。

> ⚠️ **高频踩坑：参数顺序也敏感**。Anthropic 的 prompt caching 不仅检查参数值，还检查**参数顺序**——同样的 tools 列表如果顺序不同，缓存就会 miss。这对使用**动态工具集**的系统来说尤其危险：如果你根据上下文动态选择 tools 子集，每次传入 API 的 tools 数组顺序不同，缓存命中率会断崖式下降。**解决方案：在传入 API 前，始终按固定规则（如工具名的字母序）对参数数组排序。**

> 🚫 **过度工程信号**：如果你的 API 月账单 < $100，花时间优化 token 消耗的 ROI 很低。把精力放在产品功能上更值得。**经验法则：当 token 成本占总运营成本 > 15% 时，再系统性地做 token 优化。**

---

## 思想 3：把 AI 设计成可组合的模块

**核心接口**：

```typescript
type AIQuery = {
  messages: Message[]
  systemPrompt: string
  querySource: string     // 区分调用者，用于日志和权限
  canUseTool: (tool, input) => Permission  // 每个调用者定义权限边界
  setAppState?: (updater) => void  // 是否允许修改共享状态（子 AI 通常不能）
  tools?: Tool[]          // 可以限制工具集
}
```

注意 `canUseTool: (tool, input) => Permission` 这个签名——权限检查不仅考虑"**哪个**工具"，还考虑"传了**什么参数**"。这意味着同一个 `BashTool`，在 `input = "ls"`（列出文件目录——无害操作）时可以自动批准，在 `input = "rm -rf /"`（强制删除整台电脑的所有文件——极度危险）时需要人工确认。这种**参数级别**的权限控制是 Claude Code 权限系统的真正精髓。

不要为每种 AI 角色创建独立的执行代码。统一的执行引擎 + 不同的参数配置 = 可维护性大幅提升。

**与框架的对比——两种组合哲学**：

这里存在两条截然不同的路线：

| | Claude Code 路线 | CrewAI / AutoGen 路线 |
|---|---|---|
| **思路** | 统一执行引擎 + 不同参数配置 | 每个 Agent 是独立类/角色 |
| **编程范式** | 函数式：一个函数，不同配置 | 面向对象：不同类，不同行为 |
| **优点** | 可维护性高，行为一致 | 灵活性高，Agent 可有完全不同的执行流程 |
| **缺点** | 所有 Agent 的能力被限制在同一接口内 | Agent 数量增长后维护成本急剧上升 |

**什么时候应该打破统一性？** 当某个子 Agent 需要完全不同的执行流程时（比如主 Agent 只需单轮 tool-use，而子 Agent 需要多轮自主循环），统一引擎就成了束缚。此时可以考虑为该 Agent 单独建立执行路径，但保持 `querySource` 和权限接口的统一。

**子 AI 的边界**：

子 AI 实例应该：
- ✅ 有独立的 AbortController
- ✅ 有限制的工具集（只用它需要的工具）
- ✅ 有明确的 `querySource` 标识（在多 Agent 系统中，`querySource` 解决的是一个根本性问题：当出了问题时，你能追溯到是**哪个 Agent** 发起的**哪个操作链路**——这对 AI 系统的可审计性至关重要）
- ❌ 不能修改父 AI 的全局状态（`setAppState` 通常应该是 no-op，但注意：某些特定状态更新如工具结果缓存可能需要例外——Claude Code 中子 Agent 通过 `querySource` 区分权限后，部分状态更新是被允许的）

> 🚫 **过度工程信号**：如果你的项目只有一种 AI 调用模式（比如用户提问→AI 回答），不需要可组合架构。**经验法则：当你发现自己在复制粘贴 AI 调用代码并修改参数时，才是引入统一引擎的时机。**

---

## 思想 4：多层防线（fail-closed）

**设计清单**：

1. **什么操作是 bypass-immune 的？** 硬编码不可绕过的限制，不受配置控制
2. **当判断不确定时，选择什么？** 默认应该是更保守的行为
3. **连续失败时怎么处理？** 自动降级（如 iron gate——一种"连续违规即强制回到严格模式"的硬性闸门——回退到人工审批）
4. **怎么区分"拒绝执行"和"拒绝但告知模型"？** （exit code 语义：2 = 给模型看，其他非零 = 给用户看）

**最小实现**：

```python
def check_permission(operation, context):
    # 层 1：硬编码的绝对禁止（bypass-immune）
    if operation.path in NEVER_TOUCH_PATHS:
        return Deny("System file - never allowed")
    
    # 层 2：配置的拒绝规则
    if matches_deny_rules(operation, context.settings):
        return Deny("Policy")
    
    # 层 3：独立分类器检查（注意：不是让 operation 自检！）
    if not classifier.is_permitted(operation, context):
        return Deny("Classifier rejected")
    
    # 层 4：配置的允许规则
    if matches_allow_rules(operation, context.settings):
        return Allow()
    
    # 层 5：默认（保守）
    return Ask("Unknown operation - need approval")
```

> ⚠️ **安全要点：被检查者不应该是检查者**。第 3 层使用**独立的 `classifier`**（分类器）来判断操作是否允许，而不是让 `operation` 对象自己调用 `self.is_permitted()`。为什么？如果 `operation` 对象被 prompt injection 污染了，它的自检方法可能返回恶意结果——这就像让嫌疑犯自己写无罪判决书。Claude Code 的实际实现正是由独立的 classifier 做判断，而非让工具自检。

**生产级补充：权限缓存**。在 Agent 循环中，每秒可能发起多次 tool call，5 层权限检查的延迟会累积。Claude Code 使用 permissions cache 来缓解——对已判定过的相同操作跳过重复检查。你的"最小实现"一旦投入生产，**缓存层几乎是必须的**。

> 📚 **课程关联**：多层防线的 fail-closed 设计是**信息安全**课程中"纵深防御"（Defense in Depth）原则的直接应用。上面的 5 层权限检查可以类比为网络安全中的防火墙规则评估：按优先级逐层匹配，最后一条是默认拒绝（default deny）。这也对应**形式化方法**课程中的安全性质验证——fail-closed 保证了"没有被显式允许的操作一定被拒绝"，这是一个可以被形式化证明的安全不变量。

> 🚫 **过度工程信号**：在单用户桌面应用或内部原型中，5 层权限检查是不必要的复杂性。**经验法则：当你的系统需要对外暴露（用户可以通过自然语言触发文件操作、网络请求等敏感行为）且用户数 > 1 时，多层防线才有意义。单用户内部工具用一层硬编码禁止列表 + 默认询问就够了。**

---

## 思想 5：可观测性是产品的一部分

**在设计每个功能时，同时设计其可观测性**：

- 这个功能执行时，用户能看到它在做什么吗？
- 当它出错时，有足够的信息来诊断原因吗？
- 有指标可以知道这个功能是否真的有效（而不是主观认为有效）？

**实践**：

```python
# 不好的写法（可观测性后置）
result = await ai_classify(tool_call)
# ... 后来发现分类器行为异常，加了日志

# 好的写法（可观测性内置）
start_time = time.now()
result = await ai_classify(tool_call)
log_event('classifier_result', {
    'tool': tool_call.name,
    'decision': result.decision,
    'duration_ms': time.now() - start_time,
    'reason': result.reason,
})
```

把日志事件视为和功能代码同等重要的代码，而不是"调试用的 print"。

**AI 应用的特有可观测性需求**：上面的 `log_event` + `duration_ms` 是 2015 年微服务时代就已标准化的实践（OpenTelemetry / Zipkin / Jaeger 都支持）。AI 应用的真正挑战在于需要追踪**AI 特有的指标**：

- **Token 消耗**：每次调用用了多少 input/output tokens，缓存命中率多少
- **分类器决策链路**：权限分类器为什么做了这个判断，依据是什么
- **Tool call 链路**：一次用户请求触发了几次 tool call，每次的结果如何
- **Agent 循环深度**：Agent 进入了几轮循环才完成任务，是否有无限循环的风险

LangChain 的 `CallbackHandler` 和 Vercel AI SDK 的 `onToken` / `onFinish` 钩子都提供了部分能力，但如果你自建 Agent 循环，这些指标需要自己埋点。

> 🚫 **过度工程信号**：如果你的 AI 应用还在验证产品假设阶段，console.log 足矣。**经验法则：当你第一次遇到"AI 行为异常但不知道为什么"的调试困境时，就是引入结构化可观测性的时机。**

---

## 思想 6：Hooks 系统是扩展性的标准模板

Hooks/Plugin 系统是软件工程中最古老的模式之一——从 Emacs 的 hooks 到 Webpack 的 tapable，从 Git hooks 到 WordPress 的 action/filter，这个模式至少有 40 年历史。Claude Code 没有发明 hooks，但它展示了如何将这个经典模式适配到 **AI Agent** 场景。

如果你的 AI 系统需要支持用户自定义行为，hooks 系统是一个成熟的模式。以下五个设计要点中，前三个是所有 hooks 系统的基本要素，后两个是 AI 应用场景的**特殊需求**：

1. **定义事件节点**：在系统的每个有意义的节点上发出事件
2. **定义退出码语义**：约定不同退出码的不同含义（阻断/非阻断/给 AI 看/给用户看）
3. **安全默认值**：需要显式的信任建立，不是默认开放
4. **来源标记**（AI 特有）：每个 hook 标记其来源，用于审计和优先级——在多 Agent 系统中，你需要知道是哪个 Agent 触发了哪个 hook
5. **异步支持**（AI 特有）：AI Agent 的操作往往是长时间运行的，某些 hook 需要异步运行并通过通知机制唤醒系统

> ⚠️ **退出码语义需要极其谨慎**。如果用户写的 hook 脚本返回了意料之外的退出码怎么办？这是一个 fail-open vs fail-closed 的设计决策。Claude Code 选择了 **fail-closed**——未知退出码当作阻断。这牺牲了一些用户体验（用户的 hook 可能因为无关 bug 导致操作被意外阻断），但换来了安全保证。你的项目需要根据自己的安全需求做选择。

Cursor 的 `.cursorrules` 分层系统提供了另一种思路：不通过退出码控制行为，而是通过规则文件的优先级层次（项目级 > 用户级 > 全局级）来实现可扩展性。如果你的需求更偏向"配置"而非"脚本"，这种声明式方案可能更简洁。

> 🚫 **过度工程信号**：如果你的 AI 应用用户 < 100 且没有第三方集成需求，hooks 系统是纯粹的过度工程。**经验法则：当你收到第三个"能不能在 X 之前/之后自动做 Y"的功能请求时，就是引入 hooks 的时机。**

---

## 最后

这些思想不是 Claude Code 发明的——等待时间复用来自 CPU 投机执行，token 管理类比内存管理，多层防线来自安全工程，hooks 来自 Unix 的扩展点设计。LangChain、Vercel AI SDK、CrewAI、AutoGen、Cursor 等框架和产品也在各自的领域实践着类似的理念，且各有优劣——我们在每个思想中都做了对比。

Claude Code 的贡献不在于"首创"，而在于**把这些已有的工程思想系统性地组合到一个产品中**，并在真实生产规模（大规模用户、周 token 消耗十亿量级，具体数字以官方公告为准）下验证了它们协同工作的有效性。单独看每个思想，你可能在教科书或其他项目里见过；但把它们组合在一起形成一个连贯的工程体系，这是 Claude Code 作为"活案例"的独特价值。

这种"组合价值"可以用**系统性效应**来概括：Dream 系统让 KAIROS 更聪明 → Bash AST 解析器使权限系统成为可能 → 权限使自主运行安全 → 自主运行使 KAIROS 可行 → **三层压缩**[^compact-layers] 使长会话成为可能 → 长会话使多 Agent 协调成为现实。移除任何一层，其余层的效果都会下降。这就是精良架构的本质——整体大于部分之和。

[^compact-layers]: 这里的"三层压缩"是高度抽象的口径——指**轻量裁剪 / 中度压缩 / 重度全文压缩**三大类。完整技术细节在 **Q02 上下文压缩为什么需要六套机制** 中描述为"六套压缩机制"，**Part2 第四章 查询循环** 描述为"五层 in-call 压缩 + 第六道防线 reactiveCompact"。三种说法对应同一系统的不同抽象粒度——本节用"三层"是为批判性分析的简化，不是和其他章节矛盾。

这是这个代码库最大的价值：它不只是 Claude Code，它是一个规模化 AI 应用工程的活案例。

---

## 代码落点

- `src/main.tsx`：启动序列——展示"在等待时间里藏工作"的完整实例
- `src/services/api/claude.ts`：参数注入模式和 `querySource` 路由逻辑——"把 AI 当可组合模块"的核心实现（重点关注参数如何按 `querySource` 动态组装，而非 `query()` 函数本身）
- `src/utils/permissions/permissions.ts`：10 步权限状态机——多层防线思想的集大成者
