---
title: "从一条推文读懂 Claude Code：工具设计原理与实战指南"
author: deletexiumu
pubDatetime: 2026-03-23T08:00:00+08:00
featured: false
draft: false
tags:
  - Claude Code
  - AI Agent
  - 教程
description: "Claude Code 核心开发者 Thariq 分享了工具设计背后的 6 个关键决策。本文从设计原理反推实操技巧，覆盖 AskUserQuestion 演进、TodoWrite vs TaskCreate 选型、渐进式披露和验证优先等核心主题。"
---


## 一条推文引发的思考

2026 年 3 月，Claude Code 核心开发者 Thariq 发了一篇长文推文 *"Lessons from Building Claude Code: Seeing like an Agent"*。开篇第一句话就点明了核心难题：

> "One of the hardest parts of building an agent harness is constructing its action space."

翻译过来就是：给 AI Agent 设计工具集，是整个系统里最难的部分之一。

这句话让我重新审视了自己使用 Claude Code 的方式。之前我只关心"怎么用"，但理解"为什么这样设计"之后，很多用法上的困惑一下就通了。

本文从 6 个关键设计决策出发，拆解 Claude Code 工具设计背后的逻辑，并给出对应的实操技巧。不是工具全景手册，而是帮你建立一套"知其所以然"的使用框架。

---

## 工具设计的底层逻辑：不是越多越好

### 数学题比喻

Thariq 在推文里用了一个精妙的类比：给学生做数学题，你会根据题目难度给不同的工具——

- **纸笔**：基础运算，靠模型自身能力
- **计算器**：增强工具，扩展特定能力
- **电脑**：完整环境，处理复杂场景

Claude Code 的工具设计遵循同样的逻辑。当前大约 20 个工具，每新增一个工具的门槛都极高——因为每多一个工具，模型就多一个需要判断的选项，认知开销（cognitive overhead）随之增加。

> "Designing the tools for your models is as much an art as it is a science."

### 作者理解框架

下面是我对 Claude Code 核心工具的分层理解（非官方分类，仅覆盖核心工具）：

| 层级 | 工具 | 职责 |
|------|------|------|
| 文件操作层 | Read / Write / Edit | 读取、创建、精确编辑文件 |
| 信息检索层 | Grep / Glob / WebSearch | 在代码库和网络中搜索信息 |
| 任务执行层 | Bash / Agent | 执行命令、委派子任务 |
| 人机交互层 | AskUserQuestion | 结构化向用户提问 |
| 任务编排层 | TaskCreate / TaskUpdate | 任务拆分、依赖管理、进度追踪 |
| 权限安全层 | （贯穿所有工具） | 读操作无需授权，写/执行操作需用户确认 |

这个分层有一个刻意的设计：**读操作低摩擦，写操作需授权**。Read、Grep、Glob 可以自由调用，不会弹出权限确认；但 Bash、Write、Edit 默认会请求你确认。你可以通过 `/permissions` 或 `acceptEdits` 模式预批准常用操作，减少重复确认——但安全边界本身是刻意设计的。

### 实操：在 CLAUDE.md 里引导工具选择

Claude Code 的 system prompt 已经包含了工具选择的指引，但你可以在 CLAUDE.md 里进一步强化。比如：

```markdown
# 工具使用规则
- 搜索文件内容时使用 Grep，不要用 Bash + grep
- 查找文件时使用 Glob，不要用 Bash + find
- 需要探索大量代码时，优先使用 Agent(Explore) 子代理，避免污染主上下文
- 修改现有文件优先用 Edit，只有完全重写时才用 Write
```

这些规则的核心逻辑是：**专用工具比通用工具的输出更结构化，模型更容易理解和处理**。用 Bash 跑 `grep` 返回的是纯文本，模型需要额外解析；用 Grep 工具返回的是结构化结果，直接可用。

---

## AskUserQuestion：从混乱到清晰的提问工具

![沟通方式对比：从混乱到清晰](/blog/claude-code-tool-design-philosophy/02-communication-compare.jpg)

### 早期的失败尝试

Thariq 在推文中提到，AskUserQuestion 并不是一开始就存在的。早期团队尝试过两种方案：

**尝试一：把提问功能附着在其他工具的参数上。** 比如让 Claude 在调用某个工具时顺便带上一个"问题"字段。结果是职责混乱——模型分不清什么时候该执行工具、什么时候该先问用户。

**尝试二：通过 markdown 格式标记来传递问题。** 让 Claude 用特定格式输出问题，前端解析后展示给用户。结果是格式漂移——Claude 会"append extra sentences, omit options, or use a different format altogether"。

### 当前方案：独立工具 + 模态弹窗 + 阻塞等待

最终的方案是把提问做成一个独立工具。这个决策背后的逻辑很简单：**工具边界清晰 = 模型调用更准确**。当"提问"是一个明确的工具时，模型知道调用它就是在问问题，不会和其他操作混淆。

来看它的参数结构：

```json
{
  "questions": [
    {
      "question": "你希望认证模块支持哪些登录方式？",
      "header": "登录方式",
      "multiSelect": true,
      "options": [
        {
          "label": "邮箱密码 (Recommended)",
          "description": "最基础的认证方式，实现简单"
        },
        {
          "label": "OAuth 第三方登录",
          "description": "GitHub/Google 等，需要配置 OAuth 应用"
        },
        {
          "label": "手机验证码",
          "description": "需要接入短信服务商"
        }
      ]
    }
  ]
}
```

几个关键细节：

- **1-4 个问题**：每次调用可以问 1 到 4 个问题，默认建议一次聚焦一个问题，强相关的才合并
- **2-4 个选项**：每个问题 2 到 4 个选项，推荐选项放第一个并标记 `(Recommended)`
- **系统自动添加 Other**：不要手动加"自定义"或"其他"选项，系统会自动提供兜底
- **preview 字段**（v2.1.47+）：需要用户对比选择时，可以用 preview 展示 HTML mockup 或代码片段

### 实操技巧

**Interview-first 工作流：** Anthropic 官方推荐的提示词模板：

```
I want to build [brief description]. Interview me in detail
using the AskUserQuestion tool. Ask about technical implementation,
UI/UX, edge cases, concerns, and tradeoffs. Don't ask obvious
questions, dig into the hard parts I might not have considered.
Keep interviewing until we've covered everything, then write a
complete spec to SPEC.md.
```

完成 spec 后，用 `/clear` 开启新会话执行。新会话有干净的上下文，只关注实现——这是"上下文分离"的最佳实践。

**封装为可复用 Skill：** 把 Interview 模式做成 Skill，就不用每次都手写这段提示词：

```yaml
---
name: feature-interview
description: "Interview user about feature requirements before coding"
user-invocable: true
disable-model-invocation: true
---
Interview the user about their feature requirements using AskUserQuestion.
Ask 5-10 rounds of questions covering:
- Technical implementation details
- Edge cases and error handling
- UI/UX preferences
- Performance and security concerns

Don't ask obvious questions. Focus on the hard parts.
After the interview, write a complete spec to SPEC.md.
```

### 已知边界与坑

- **子代理中不可用**：当前版本中，子代理调用 AskUserQuestion 会静默失败（注意：官方文档对此限制的表述存在冲突，未来可能调整）
- **Skill 中紧接工具调用后触发可能导致 UI 不渲染**：AskUserQuestion 直接返回空答案，拿不到用户输入
- **不是万能交互手段**：复杂的多轮对话不适合用这个工具，直接在对话中沟通更自然

---

## 从 TodoWrite 到 TaskCreate：两代任务管理的演进

### 不是轻重之分，而是代际差异

很多人把 TodoWrite 和 TaskCreate 理解为"简单版 vs 复杂版"，这不准确。它们是两代产品，服务于不同的场景。

Thariq 在推文中提到了演进的驱动力：随着模型能力提升（特别是 Opus 4.5 的出现），TodoWrite 从"有用的脚手架"变成了"约束"。简单任务模型自己就能记住，复杂任务又需要 TodoWrite 不具备的依赖管理和跨会话持久化能力。

### 对比表

| 特性 | TodoWrite | TaskCreate / Task* |
|------|-----------|-------------------|
| **引入版本** | v0.2.93 | v2.1.16 |
| **持久化** | 仅在当前对话上下文中 | 存储在 `~/.claude/tasks`，跨会话可恢复 |
| **依赖管理** | 不支持 | 支持 blockedBy/blocks |
| **Hook 触发** | 支持 PreToolUse/PostToolUse | 不触发（已知回归问题） |
| **VSCode 可用** | 可用 | 不可用（仅 CLI v2.1.19+） |
| **Token 占用** | 2,161 tokens | 528 tokens |
| **子代理可见性** | 同会话可见 | 跨代理协调 |
| **Hydration 恢复** | 不支持 | 支持（通过环境变量恢复） |

![TodoWrite vs TaskCreate 对比](/blog/claude-code-tool-design-philosophy/04-todo-vs-task.jpg)

### 实操：怎么选

**交互式 CLI 会话** → 优先用 TaskCreate。Token 占用更小（528 vs 2161），支持依赖管理，跨会话可恢复。

**Agent SDK / 非交互模式** → TodoWrite 仍然适用。它在非交互场景下作为自我检查清单依然有效。

**Agent Team 中跨代理协调** → TaskCreate + SendMessage。一个代理创建的任务，其他代理可以通过 TaskList 查看并领取。

**跨会话恢复任务列表：**

```bash
# 启动时指定任务列表 ID，新会话可恢复之前的任务状态
CLAUDE_CODE_TASK_LIST_ID=my-project claude
```

### Hook 绕过问题

这是一个需要注意的坑：Task* 系列工具完全绕过 PreToolUse/PostToolUse hook 系统。如果你的工作流依赖 hook 来监控任务行为（比如在任务完成时触发通知），用 TaskCreate 时这些 hook 不会被触发。

不过 v2.1.33 引入了专门的 `TeammateIdle` 和 `TaskCompleted` hook 事件来补偿这个缺口。

---

## 渐进式披露：别把整本说明书塞进 system prompt

![信息分层：从过载到按需加载](/blog/claude-code-tool-design-philosophy/03-info-layers.jpg)

### 设计原理

Claude Code 官方文档中有一句话道出了所有工具设计决策的核心约束：

> "LLM performance degrades as context fills."

上下文越满，模型表现越差。所以 Claude Code 的信息架构遵循 Progressive Disclosure（渐进式披露）——这个概念最早由 Jakob Nielsen 在 1995 年提出，核心思想是：先给目录，按需读详情。

Claude Code 早期曾尝试用 RAG 向量数据库来检索代码上下文，后来换成了让模型用 Grep 自主搜索。为什么？因为 Grep 搜索更灵活，模型可以根据当前任务动态决定搜索什么，而不是依赖预先构建的索引。

### 三个层面的渐进式披露

**层面一：CLAUDE.md 层级**

```
my-project/
├── CLAUDE.md                 # 根文件：< 100 行，全局规则 + 目录指引
├── .claude/
│   ├── rules/                # 启动时自动加载（无 paths 前置元数据时）
│   │   ├── code-style.md     # 代码风格规则
│   │   ├── testing.md        # 测试规则
│   │   └── security.md       # 安全规则
│   └── skills/               # 按需加载
│       └── deployer/
│           ├── SKILL.md      # 入口，< 500 行
│           └── docs/         # 详细参考文档
├── src/
│   └── api/
│       └── CLAUDE.md         # 目录级规则，仅当 Claude 读取该目录文件时加载
```

Claude 从工作目录向上遍历目录树，加载所有祖先 CLAUDE.md。子目录的 CLAUDE.md 只在 Claude 读取该目录的文件时才加载——这就是"按需"的意思。

**层面二：Skill 的入口与详情分离**

SKILL.md 是入口（目录），references/ 或 docs/ 是详情。Skill 描述在 context window 中只占约 2% 的空间（约 16K 字符），保持精简的代价很低。

```yaml
---
name: pr-summary
description: "Summarize PR changes and generate review notes"
context: fork
agent: Explore
allowed-tools: Bash(gh *)
---
# PR Summary Skill

Analyze the current PR and generate a summary.

## Additional resources
- For PR review checklist, see [review-checklist.md](docs/review-checklist.md)
- For commit message conventions, see [commit-style.md](docs/commit-style.md)
```

`context: fork` 让这个 Skill 在隔离的子代理中执行，执行完毕后只有摘要返回主上下文——又一次渐进式披露。

**层面三：ToolSearch 延迟加载**

从 v2.1.7 开始，MCP 工具描述如果超过 context 的 10%，会被延迟加载。模型需要时通过 ToolSearch 按需搜索和加载工具的完整 schema。这避免了启动时就把几十个 MCP 工具的描述全塞进 system prompt。

### 实操：CLAUDE.md 瘦身检查

对 CLAUDE.md 里的每一行问一个问题：**"删掉这行，Claude 会犯错吗？"** 如果不会，删掉。

**好的写法：**

```markdown
- Use 2-space indentation in all TypeScript files
- Run `npm test` before committing
- API endpoints must validate input with zod schemas
```

**差的写法：**

```markdown
- Format code properly
- Test your changes
- Make sure the API works correctly
```

官方建议：根文件控制在 100 行以内，单个文件不超过 200 行。添加 "IMPORTANT" 或 "YOU MUST" 可以提高遵从率，但要节制——标记太多等于没标记。

**关键警告**：臃肿的 CLAUDE.md 会导致 Claude 忽略你的实际指令。写得多不等于管得住，写得精准才是关键。

---

## 验证优先：好工具必须让模型能自证正确

![自检流程](/blog/claude-code-tool-design-philosophy/05-self-check-flow.png)

### 设计原理

Claude Code 官方 Best Practices 文档中有一句被反复引用的话：

> "Claude performs dramatically better when it can verify its own work."

这不是一条使用建议，而是一个设计原则。Claude Code 的工具体系围绕"可验证性"做了大量设计：Bash 能跑测试、LSP 能检查类型错误、Read 能检查文件是否正确写入。每一步修改都有对应的验证手段。

为什么验证如此重要？因为 LLM 生成代码时会犯错——语法正确但逻辑不对、类型匹配但边界遗漏。如果模型改完代码就报告"完成了"，你只能靠人工审查来发现问题。但如果模型能在提交前自己跑一轮测试，大部分低级错误就被拦截了。

### Claude Code 的验证工具链

**Bash 跑测试：** 最直接的验证方式。Claude 改完代码后调用 Bash 执行测试命令，根据测试结果判断是否需要继续修复。

```bash
# Claude 会在修改代码后自动执行类似的验证
npm test                    # 单元测试
npm run lint                # 代码风格检查
npm run typecheck           # TypeScript 类型检查
```

**LSP 类型检查：** Claude Code 集成了语言服务器协议（LSP），能在不运行代码的情况下检测类型错误、未使用的变量、缺失的导入。这比跑完整测试套件更快，适合在每次编辑后立即检查。

**Read 验证写入结果：** Edit 工具修改文件后，Claude 会用 Read 重新读取文件，确认修改确实生效且没有破坏文件结构。这看起来是多余的一步，但对于大文件的精确编辑，这个验证能避免"改错位置"的问题。

### 实操：在 CLAUDE.md 中内嵌验证步骤

最有效的做法是把验证命令写进 CLAUDE.md，让 Claude 每次修改后自动执行：

```markdown
# 验证规则
- 修改 TypeScript 文件后，运行 `npx tsc --noEmit` 检查类型
- 修改测试文件后，运行对应的测试用例（不要跑全量测试）
- 修改 API 接口后，运行 `npm run test:api` 验证接口契约
- 提交前运行 `npm run lint && npm test`
```

关键是要把验证命令写得**具体且可执行**。"测试你的修改"是无效指令；"`npm run test:api`"是有效指令。

另一个技巧是用 Hook 实现自动验证。比如在每次 Edit 操作后自动跑 lint：

```json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "cd $CLAUDE_PROJECT_DIR && npx eslint --fix . 2>/dev/null || true"
          }
        ]
      }
    ]
  }
}
```

这样 Claude 不需要"记住"跑 lint——Hook 确保每次文件修改后自动执行，确定性比指令高得多。

---

## 最小权限子代理：Claude Code Guide 的设计启示

### 设计原理

当你问"Claude Code 怎么配置 MCP"时，Claude Code 不会在主上下文里搜索所有功能文档。它会把这个问题委派给一个叫 Claude Code Guide 的子代理：

| 属性 | 值 |
|------|-----|
| 模型 | Haiku（快且便宜） |
| 可用工具 | Read-only + WebFetch + WebSearch |
| 不可用工具 | Write、Edit、Bash、Agent 等所有修改工具 |

这个设计体现了两个价值：

1. **主上下文不膨胀**：功能咨询的搜索过程和结果留在子代理的独立 context window 里，只有最终答案返回主上下文
2. **最小权限降低风险**：Guide 只有只读能力，不可能误改你的代码

### 子代理体系一览

Claude Code 内置了多种专用子代理，每种都有明确的职责边界：

| 子代理 | 模型 | 工具权限 | 用途 |
|--------|------|----------|------|
| Explore | Haiku | 只读（Glob, Grep, Read, Bash） | 代码库搜索和分析 |
| Plan | 继承父级 | 只读 | 计划模式的调研 |
| General-purpose | 继承父级 | 全部 | 需要探索+修改的复杂任务 |
| Claude Code Guide | Haiku | 只读+联网 | Claude Code 功能咨询 |

### 实操：写你自己的最小权限子代理

这个模式可以直接搬到你的项目中。比如你需要一个只做代码审查、不能修改代码的子代理：

```yaml
# .claude/agents/code-reviewer.md
---
name: code-reviewer
description: "Review code changes for bugs, security issues, and style violations"
model: haiku
tools: Read, Grep, Glob
disallowedTools: Write, Edit, Bash
permissionMode: plan    # plan 模式 = 只读探索，确保子代理无法执行写操作
---
You are a code reviewer. Analyze the given code changes and report:
1. Potential bugs or logic errors
2. Security vulnerabilities
3. Style violations against project conventions

You can only READ code. You cannot modify any files.
Report your findings as a structured summary.
```

关键点不在于记住什么"触发话术"来激活某个子代理，而在于理解这个设计模式本身：**专业问题 → 最小权限子代理**。

---

## 贯穿案例：从一个模糊需求到完整工作流

假设你拿到一个需求："重构用户认证模块"。看看 Claude Code 的工具设计如何在实际工作流中串联起来：

**第一步：澄清需求（人机交互层）**

Claude 不确定"重构"的范围，调用 AskUserQuestion：

```
问题：重构认证模块的目标是什么？
选项：
  1. 迁移到 OAuth 2.0（Recommended）- 当前的 session 方案不满足合规要求
  2. 性能优化 - 登录接口 P99 延迟过高
  3. 代码整理 - 逻辑分散在多个文件中，需要统一
```

你选了选项 1。范围明确了。

**第二步：拆分任务（任务编排层）**

Claude 调用 TaskCreate，把"迁移到 OAuth 2.0"拆成有依赖关系的子任务：

- Task A：调研现有认证逻辑（无依赖）
- Task B：设计 OAuth 2.0 接入方案（依赖 A）
- Task C：实现 token 刷新机制（依赖 B）
- Task D：迁移现有用户 session（依赖 C）
- Task E：编写集成测试（依赖 C）

**第三步：按需加载文档（渐进式披露）**

执行 Task B 时，Claude 需要查项目的安全规范。它不是从 system prompt 里找——而是从 CLAUDE.md 的 `@import` 引用中发现 `rules/security.md`，Read 加载后获取必要的上下文。

**第四步：委派安全审查（最小权限子代理）**

OAuth 配置涉及密钥管理，Claude 把安全审查委派给一个只读子代理，确保审查过程不会误改配置文件。

**第五步：自我验证（验证优先）**

每步修改完成后，Claude 自己跑测试验证。CLAUDE.md 中配置的验证命令会在这里发挥作用——Claude 不需要你提醒"跑个测试"，它知道改完代码就该验证。如果测试失败，它会自动修复并重新验证，直到通过。

### 五个设计原则

从上面的工作流和前文的分析中，可以提炼出 Claude Code 工具设计的五个核心原则：

1. **工具边界清晰**：每个工具有明确的职责范围。AskUserQuestion 只负责提问，不附着在其他工具上；Read 只读文件，Write 只写文件。
2. **信息按需加载**：不把所有东西塞进 system prompt。CLAUDE.md 分层、Skill 入口与详情分离、ToolSearch 延迟加载——都是同一个思路。
3. **验证优先**：好工具必须让模型能自证正确。测试、lint、Hook 自动检查——验证手段内嵌到工作流中，而非留给人工审查。
4. **安全边界分明**：读操作低摩擦，写/执行需授权，子代理最小权限。这是刻意的设计，不是多余的麻烦。
5. **最小权限委派**：专业问题委派给权限受限的子代理，主上下文不膨胀，风险不扩散。

这些原则不只适用于理解 Claude Code。当你写 CLAUDE.md、设计 Skill、组织项目结构、甚至构建自己的 Agent 系统时，都可以用这套框架来指导决策。

### 现在就做一件事

打开你项目的 CLAUDE.md（没有就创建一个），用这个标准过一遍每一行：**"删掉这行，Claude 会犯错吗？"** 把它精简到 100 行以内。然后把详细规则拆到 `.claude/rules/` 目录下的独立文件里，用 `@import` 引入。

如果你还没写过 Skill，试着把本文第三节的 `feature-interview` 模板复制到 `.claude/skills/feature-interview/SKILL.md`，下次开新功能时用 `/feature-interview` 启动——你会发现需求澄清的效率完全不一样。

---

*原文参考：Thariq (@trq212) 在 X 平台发布的长文推文 "Lessons from Building Claude Code: Seeing like an Agent"（2026-03-22）*

*原文参考：Claude Code 官方文档 — Tools Reference 页面*

*原文参考：Claude Code 官方文档 — Best Practices 页面*

*原文参考：Claude Code 官方文档 — Sub-agents 页面*

*原文参考：Claude Code 官方文档 — Memory 页面*

*原文参考：Nielsen Norman Group — Progressive Disclosure*

---

## 相关阅读

- [同样的话跟 Claude Code 说了八遍，是时候让它自己记住了](/posts/claude-code-memory-rules/) — CLAUDE.md 和记忆系统的详细配置指南，与本文"渐进式披露"一节互补
- [从需求到自动化：Claude Code Skill 工具链完整实战](/posts/claude-code-skill-toolchain/) — 如何设计和编写 Skill，包含 references/ 拆分的实战案例
- [我如何使用 Claude Code：先计划，再编码](/posts/claude-code-plan-before-code/) — 工作流方法论，与本文"贯穿案例"一节的理念一脉相承
