这篇文章是Claud Code的核心开发人员Thariq在X发表的一篇文章,也是目前 agent engineering 最重要的一篇工程文章之一。
构建 Agent 时最困难的问题之一
构建一个 agent 框架时,最困难的事情之一是 设计它的动作空间(action space) 。
Claude 通过 Tool Calling(工具调用) 来执行操作。而在 Claude API 中,工具可以通过多种方式构建,例如:
- bash
- skills
- 最近新增的 code execution
面对这些选项,你应该如何设计 agent 的工具?
只需要一个工具(例如 bash 或 code execution)就够了吗?
还是需要 50 个工具,每种场景一个?
一个简单的类比:解一道数学题
为了站在模型的角度思考问题,我常常会做这样一个想象:
假设你被给了一道非常困难的数学题。
你会希望手边有哪些工具?
答案取决于 你的能力。
- 纸和笔:最低限度可以解决问题,但只能依靠手算。
- 计算器:更快,但前提是你知道如何使用高级功能。
- 电脑:最强大的工具,但前提是你能编写并运行代码。
这个类比对于设计 agent 的工具非常有用:
你应该给 agent 提供与其能力相匹配的工具。
但问题是:
你如何知道模型的能力边界在哪里?
答案很简单:
- 观察它
- 阅读它的输出
- 不断实验
最终,你要学会 像 agent 一样看世界。
下面是我们在构建 Claude Code 时得到的一些经验。
提升信息获取能力:AskUserQuestion 工具
在设计 AskUserQuestion 工具时,我们的目标是提升 Claude 提问的能力(elicitation)。
Claude 本身当然可以用普通文本提问。
但我们发现这种方式存在一个问题:
用户回答这些问题通常需要 额外的时间和精力。
于是我们开始思考:
如何减少沟通摩擦,提高用户与 Claude 之间的信息带宽?
尝试 1:修改 ExitPlanTool
我们最初的尝试是:
在 ExitPlanTool 中新增一个参数,让它在输出计划时,同时包含一组问题。
实现起来很简单,但结果并不好。
原因是:
Claude 同时被要求:
- 生成一个计划
- 询问关于该计划的问题
这会导致一些困惑,例如:
- 如果用户的回答与计划冲突怎么办?
- Claude 是否需要再次调用 ExitPlanTool?
因此我们需要一种新的方法。
尝试 2:改变输出格式
接下来,我们尝试修改 Claude 的输出格式。
例如:
让 Claude 输出一种 特定的 Markdown 结构,用来表示问题,例如:
- 使用 bullet list
- 在括号中提供多个选项
然后我们可以解析这个格式,并生成 UI。
这种方法的优点是:
- 非常通用
- Claude 大多数情况下能够遵循
但问题是:
它并不可靠。
Claude 有时会:
- 添加额外句子
- 漏掉选项
- 使用不同的格式
结果是解析经常失败。
尝试 3:AskUserQuestion 工具
最终,我们选择创建一个专门的工具:
AskUserQuestion
Claude 可以在任何时候调用它,但我们特别鼓励它在 plan mode 中使用。
当工具触发时:
- UI 会弹出一个对话框
- 显示问题
- agent 循环暂停
- 等待用户回答
这种方式带来了几个好处:
- Claude 可以输出 结构化问题
- 用户可以看到 多个可选答案
- 功能可以被复用(例如在 Agent SDK 或 skills 中)
最重要的是:
Claude 似乎很喜欢调用这个工具。
即使是最精心设计的工具,如果 Claude 不理解如何调用它,也不会成功。
随模型能力演进:Tasks 与 Todos
在 Claude Code 的早期版本中,我们意识到:
模型需要一个 Todo 列表 来保持任务进度。
因此我们设计了一个工具:
TodoWrite
它允许 Claude:
- 写入 Todo
- 更新 Todo
- 展示给用户
同时,Claude 可以在完成任务时勾选它们。
问题:模型仍然会忘记任务
尽管如此,我们仍然经常看到 Claude 忘记要做的事情。
于是我们采取了一个简单的办法:
每 5 次对话 插入系统提醒,告诉 Claude 当前目标。
模型能力提升后的变化
随着模型能力提升,这种设计反而变成了问题。
因为:
- Claude 已经 不再需要提醒
- Todo 反而 限制了它
模型会误以为:
它必须严格遵守 Todo 列表。
而不是修改它。
此外,新的模型(如 Opus 4.5)已经能够很好地使用 subagents。
那么问题来了:
多个 agent 如何共享 Todo?
解决方案:Task Tool
因此我们将 TodoWrite 替换为:
Task Tool
区别在于:
Todos → 用于跟踪任务
Tasks → 用于 agent 之间协作
Task 可以:
- 定义依赖关系
- 在多个 subagent 之间共享更新
- 被修改或删除
一个重要的经验是:
随着模型能力提高,过去有用的工具可能会变成限制。
因此你必须 不断重新审视工具设计。
搜索工具:让 Agent 自己构建上下文
对于 Claude 来说,最重要的一类工具是:
搜索工具
因为它们决定了 Claude 如何构建上下文。
第一阶段:RAG 向量数据库
Claude Code 最初使用:
RAG + 向量数据库
来查找相关上下文。
优点:
- 快
- 强大
缺点:
- 需要索引
- 配置复杂
- 容易在不同环境中失效
更重要的问题是:
Claude 不是自己找上下文,而是 被动接收上下文。
第二阶段:Grep
后来我们想到:
如果 Claude 能搜索互联网,
为什么不能搜索代码库?
于是我们给 Claude 一个工具:
Grep
这样 Claude 可以:
- 搜索文件
- 自己构建上下文
随着模型变强,它越来越擅长做这件事。
Progressive Disclosure(渐进式披露)
在引入 Agent Skills 后,我们正式提出了一个概念:
Progressive Disclosure
意思是:
agent 通过探索逐步发现信息。
例如:
- Claude 可以读取 skill 文件
- 这些文件可以引用其他文件
- Claude 可以递归读取
这使得 Claude 可以在多层文件结构中搜索信息。
一年时间里,Claude 从:
- 几乎不能构建上下文
变成:
- 可以进行多层嵌套搜索
如今,progressive disclosure 是我们添加功能的重要方式。
因为:
它不需要新增工具。
一个案例:Claude Code Guide Agent
目前 Claude Code 大约有 20 个工具。
我们一直在问自己:
这些工具真的都需要吗?
增加工具的门槛很高,因为:
每个工具都会增加模型的决策复杂度。
一个实际问题
Claude 并不知道如何使用 Claude Code。
例如:
如果你问:
- 如何添加 MCP?
- slash command 是什么?
Claude 可能回答不了。
解决方案
我们没有把这些内容写进系统 prompt。
因为:
用户很少问这些问题。
如果放进去,会造成:
context rot(上下文腐化)
影响 Claude 的主要任务:
写代码。
Guide Subagent
因此我们创建了一个 Guide 子 agent。
当用户询问 Claude Code 自身的问题时:
Claude 会调用这个 subagent。
这个 agent:
- 有完整的文档
- 知道如何搜索文档
- 返回精确答案
这样我们:
扩展了 Claude 的能力,但没有增加新的工具。
设计 Agent 工具是一门艺术
如果你希望这篇文章给出一套严格规则,
那可能会失望。
因为:
设计 AI 工具既是科学,也是艺术。
它取决于:
- 模型能力
- agent 的目标
- 所处环境
真正有效的方法只有:
- 不断实验
- 阅读输出
- 持续调整
最终:
学会 像 agent 一样看世界。