Claude Code 构建经验:像 Agent 一样设计工具

基于 Claude Code 团队的工具设计经验,讨论 Agent harness 里最难的一件事:不是工具越多越好,而是给模型一个它真的会理解、会使用、会自我校正的行动空间。

8 min read Part of Claude Code · Ch. 2

像 Agent 一样设计工具

flowchart LR
  A["用户目标"] --> B["Agent 理解"]
  B --> C["选择工具"]
  C --> D["执行动作"]
  D --> E["观察反馈"]
  E --> B
  C --> F["工具太少:做不了"]
  C --> G["工具太多:选不动"]

Agent 产品最难的地方,不是给模型接一个工具,而是设计一组它真的会用的工具。工具太少,模型被困住;工具太多,模型开始犹豫、误用、绕路。你要做的不是堆能力,而是替模型设计一个合适的行动空间。


出处与延伸阅读


这篇文章会讲什么

构建 Agent harness 时,你会很快遇到一个问题:

我到底应该给模型什么工具?

一个万能 bash?一个 code execution?一堆针对具体场景的专用工具?搜索工具、提问工具、计划工具、任务工具、subagent,又该放在哪里?

这篇文章想讨论的是工具设计背后的判断:

  • 为什么工具是 Agent 的行动空间
  • 为什么结构化工具有时比输出格式指令更可靠
  • 为什么工具会随着模型能力变化而过时
  • 搜索工具为什么应该让模型主动构建上下文
  • progressive disclosure 为什么能扩展能力,却不污染主上下文

先说结论

好的 Agent 工具设计,不是“把所有 API 都暴露给模型”。

它更像给一个聪明但不稳定的同事设计工作台:

  • 常用动作要顺手
  • 危险动作要有边界
  • 不确定时要能提问
  • 做复杂任务时要能分解
  • 查资料时要能自己找,而不是一次性被喂一堆
  • 工具本身要随着模型能力变化而更新

Claude Code 这篇经验最有价值的地方,是它没有把工具设计写成死规则。相反,它反复强调一件事:

观察模型真实输出,比相信自己的工具设计直觉更重要。

Agent 工具设计不是一次建模完成,而是长期调试模型行为。


工具不是功能清单,而是行动空间

我们习惯用人类视角看工具:这个按钮能做什么、这个 API 能返回什么、这个 CLI 有哪些参数。

但 Agent 不是这样感知工具的。

它看到的是一组名称、description、schema、调用结果,以及过往经验里“调用这个工具通常会发生什么”。工具定义会直接影响它如何理解任务、如何拆分步骤、什么时候停下来问人。

所以工具设计首先不是 API 设计,而是行为设计。

一个工具至少要回答四个问题:

问题设计含义
模型什么时候应该用它?description 要写触发条件,而不只是功能摘要
用它之前需要知道什么?schema 要逼近必要上下文
调用后模型会看到什么?结果要可解释、可继续推理
调错了会怎样?错误信息要帮助恢复,而不是只报失败

如果这些问题没想清楚,工具越多越危险。模型不是“多一个选择就多一份能力”,它也可能多一份误判。


AskUserQuestion:把提问做成工具

Agent 经常需要向用户补信息。

最简单的方式是让模型直接用自然语言问:“你希望我怎么做?”但这类问题常常有两个问题:

  • 用户回答成本高
  • 模型输出格式不稳定

Claude Code 团队一开始也尝试过几种方案,比如在退出计划时附带问题,或者要求模型用固定 Markdown 格式输出选项。问题是,这些办法都容易混在原任务里,模型也可能漏格式、加废话、问错粒度。

最后更稳定的做法,是把提问做成一个独立工具。

当模型调用 AskUserQuestion 时,界面弹出结构化问题,Agent 暂停循环,等用户回答后再继续。

这个设计很小,但它体现了 Agent 工具设计的一条重要原则:

如果某种交互对任务很关键,就不要只靠文本格式约定。

工具可以把一个模糊行为变成可控交互。

提问也是一种动作,不只是输出。


Todo 到 Task:工具会随着模型能力变化而过时

早期 Claude Code 需要 Todo 列表。

这是很自然的设计:让模型列任务、逐项完成、保持不跑偏。为了让模型更稳,还可以定期插入系统提醒,告诉它当前目标是什么。

但随着模型能力提升,这类工具可能开始反过来限制模型。

一个更强的模型不只是“更会完成 Todo”,它可能更会在执行中发现原计划不合理、拆出子任务、交给 subagent、调整依赖关系。此时固定 Todo 就变成了束缚。

于是 TodoWrite 被更接近协作单元的 Task Tool 替代。

这件事有一个很重要的提醒:

工具不是永恒基础设施,它是对当前模型能力的补丁。

模型弱的时候,工具要帮它保持轨道。

模型强了以后,工具要帮它协作和表达状态。

如果团队不重新评估工具,就会把过去为旧模型设计的拐杖,变成新模型的绊脚石。


搜索工具:让 Agent 自己构建上下文

Claude Code 早期也尝试过用 RAG 给模型喂上下文。

RAG 有优势:快、可索引、召回稳定。但它也有一个问题:上下文是外部系统替模型选好的。模型不一定知道为什么这些材料重要,也不一定能继续追问。

后来 Claude Code 更偏向给模型搜索工具,比如 grep,让它自己去找文件、自己读、自己组装上下文。

这条路线很有启发。

随着模型变强,它越来越像一个会使用资料室的人,而不是一个只接受资料包的人。你给它一个可以探索的环境,它反而能找到更精确的上下文。

当然,这不意味着 RAG 没用。更好的理解是:

方式更适合
系统主动检索高吞吐、标准问答、召回范围清楚
Agent 主动搜索复杂仓库、路径不清、需要逐步追踪线索
两者结合先给候选,再让 Agent 自己确认和扩展

Coding Agent 的特殊之处在于,代码库是一个高度结构化、可搜索、可验证的世界。让 Agent 自己搜索,往往比一次性塞上下文更自然。


Progressive Disclosure:别把所有知识塞进主上下文

Agent 会遇到大量低频知识。

比如用户问:“Claude Code 的 slash command 怎么配?”或者“某个内部 API 应该怎么查?”这些信息确实有用,但如果全部塞进 system prompt,主任务会被污染。

Progressive disclosure 的思路是:只在需要时逐步披露。

一个典型做法是:

主 Agent 知道有某类资料存在

需要时读取入口文档

入口文档指向更细资料

必要时调用专门 subagent

主 Agent 只拿回最终答案

Claude Code Guide subagent 就是这个思路。用户问 Claude Code 自身用法时,主 Agent 不把所有文档读进来,而是把问题交给一个更懂文档搜索的子 agent。子 agent 在自己的上下文里搜索,再把答案交回主线。

这个设计解决的是上下文卫生问题。

主 Agent 不应该什么都知道。它应该知道什么时候找谁。


工具数量不是越少越好,也不是越多越好

很多团队会在两个极端之间摇摆:

  • 极简派:只给 bash,让模型自己搞定一切
  • 专用派:每个场景都做一个工具

这两种都有问题。

只给 bash,看起来灵活,但模型要自己记所有命令、参数、错误恢复方式。它很容易把轮次浪费在试错上。

工具太细,看起来可靠,但模型每次都要在一大堆工具里做选择。工具描述稍微重叠,就可能选错。

更实用的判断是看任务频率和错误成本:

场景工具设计倾向
高频、标准、错误成本高做专用结构化工具
低频、探索性、边界不清给搜索或通用执行能力
需要人类确认做显式提问或审批工具
涉及危险动作工具层加权限和护栏
需要隔离上下文用 subagent 或 skill

工具设计的目标不是“工具列表漂亮”,而是让模型在关键时刻少猜。


读输出,比猜模型更重要

这篇经验里最朴素的一句话,反而最值得带走:多实验,认真读输出。

因为 Agent 工具设计很难纯靠推理想出来。

你可能觉得某个 schema 很清晰,模型却总是漏参数。

你可能觉得某个工具太专用,模型却特别喜欢用,而且效果很好。

你可能觉得某个提醒很必要,但新模型已经不需要它了,甚至被它束缚。

所以工具迭代应该有一个固定循环:

设计工具

观察模型什么时候调用

读调用前后的推理和结果

记录误用与漏用

修改 description / schema / 返回值 / 护栏

再观察

不要只看成功案例。真正有价值的是失败样本:模型为什么没用这个工具?为什么用了另一个?为什么问了一个很差的问题?为什么把搜索结果读偏了?

这些失败会告诉你,模型眼里的工具世界和你设计文档里的工具世界,差在哪里。


小结

像 Agent 一样设计工具,本质上是暂时放下人类工程师的直觉。

你不是在给自己设计 CLI,也不是在给后端服务设计 API。你是在给一个会阅读、会推理、会犯错、会被上下文影响的模型设计行动空间。

好工具会让模型更果断、更可控、更容易恢复。

坏工具会让模型更困惑、更啰嗦、更会绕路。

所以 Agent 工具设计没有终局答案。它只能随着模型、任务和环境一起长。