Claude Code 源码解析(二):工具篇,Harness 的艺术(Agent 开发必看)

0 阅读12分钟

在聊 Claude Code 的工具设计之前,我想先说说我对 Harness 这个概念的理解。

Harness 本意是缰绳、鞍具、笼头,指一整套用来驾驭强壮却难以预测的动物的装备。这一概念由 HashiCorp 创始人 Mitchell Hashimoto 于 2026 年 2 月首次提出,随后经 OpenAI 大规模实践推广,迅速在 AI 圈爆火。虽然这个词听起来很怪,感觉不搭边,但是其内在含义用在 AI 领域其实十分贴切。

举个例子,如果朋友想看看你手机里最近拍的照片,你会怎么做?

  1. 直接把手机交给他,他很可能在翻看时无意间滑到你不想公开的其他照片,造成隐私泄露。

  2. 由你拿着手机、一张张手动切换给他看,这样不仅显得很拘束,而且要你全程陪同。对方想仔细查看时还需要频繁沟通操作,整体体验很差。

  3. 当然也可以直接直接拒绝分享,但这显然也不是合适的做法。

如果手机有这样的一个功能,能够指定几张照片的临时查看权限,再把手机交给对方。这样既能让对方自由浏览想分享的内容,又能避免隐私暴露,在自由度与安全性之间取得了很好的平衡。

这正是 Harness 最核心的设计理念——给能力加上可控的边界,在开放与安全之间找到平衡。而这种思想,在 Claude Code 的工具设计里可以说无处不在。

在这里插入图片描述


一、工具系统的两个核心文件

Claude Code 的工具系统梳理,可以从两个核心文件入手:

2.1 src/Tool.ts - 工具定义的超类

这是所有工具的 "身份定义",是每个工具的设计和实现都必须奉行的准则。

Tool.ts 中定义的 14 个必需属性

#属性说明
1name工具名称,必须是全局唯一的标识符,用于在整个系统中引用该工具
2description工具描述,向模型解释工具的功能和用途,支持动态调整
3inputSchema输入 schema,定义工具的输入参数结构、类型、是否必需等信息,用于模型调用时的参数校验
4isConcurrencySafe并发安全检查,判断工具是否可以在多个会话中并发调用,默认不安全
5isEnabled是否启用,检查工具在当前环境下是否可用,可基于环境变量、网络状态等动态判断
6isReadOnly是否只读,判断工具是否会修改系统状态,用于 UI 显示和权限检查
7maxResultSizeChars最大结果大小,限制工具返回结果的最大长度,防止返回过大内容导致性能问题
8checkPermissions权限检查,在工具调用前检查是否允许执行,返回 allow/deny/ask
9prompt系统提示词,生成工具的详细使用说明,包含参数说明、使用场景示例等,帮助模型理解如何正确使用
10userFacingName用户可见名称,返回面向用户的工具显示名称,用于 UI 界面展示
11toAutoClassifierInput安全分类器输入,返回用于自动安全分类的文本描述,帮助 AI 判断工具调用是否安全
12mapToolResultToToolResultBlockParam输出格式化,将工具的执行结果转换为 Anthropic API 要求的 tool_result 格式
13renderToolUseMessageUI 渲染,返回 React 组件,用于在 REPL 终端中显示工具调用信息
14call执行函数,真正执行工具逻辑的函数,接收输入和上下文,返回执行结果

2.2 src/tools.ts - 工具注册和管理

这是工具系统的 "仓库管理员",核心函数 getAllBaseTools()。这个函数在用户每次对话的时候都会调用,然后根据境条件动态加载所有可使用工具。工具的加载条件,可以大致分为4类。

类别 1:永远加载的(无条件提供)

这类工具在任何情况下都可以提供给Agent使用,是cc运行所必须的核心工具。

  • BashTool - 执行 Shell 命令
  • FileReadToolFileEditToolFileWriteTool - 文件操作
  • WebSearchToolWebFetchTool - 网络访问
  • AgentTool - 创建Agent
  • AskUserQuestionTool - 与用户交互
  • ...

类别 2:远程控制的(Feature Flag 控制)

这类工具由 Claude 团队远程控制是否启用,目的是为了实验性功能的 A/B 测试,以及在部分工具出现问题的时候,可以远程直接控制关闭。

  • WebBrowserTool - 网页浏览
  • TaskCreateToolTaskUpdateToolTaskListTool - 任务管理
  • EnterWorktreeToolExitWorktreeTool - 工作树模式
  • SendMessageToolTeamCreateTool - 团队协作
  • SleepToolMonitorTool - 定时和监控
  • ...

类别 3:本地环境变量控制的

这类工具会根据用户的设置不同,决定是否启用,由用户主动控制。

  • LSPTool - LSP 工具(ENABLE_LSP_TOOL=true),由用户设置是否启用。
  • TeamCreateToolTeamDeleteTool - 本地控制是否启用,同时混合 Feature Flag 控制
  • ...

类别 4:外部环境条件控制的

这类工具会受一定的环境限制,无法由用户单纯决定。例如 PowerShellTool 仅在 windows 环境下可以启动。

  • GlobTool, GrepTool - 检测是否有内置搜索工具(bfs/ugrep)
  • PowerSellTool - 受限于 windows 环境

二、工具过滤

拿到工具列表后,Claude Code 不会一股脑儿给模型,而是继续经过 5 层过滤得到最终的可用工具列表。

第一步:Simple Mode 检查

该步骤会检测用户是否启用了简单模式,如果是简单模式,那么只会留下几个最核心的工具,其余的工具全部过滤掉。

  • 保留 BashTool - 执行 Shell 命令
  • 保留 FileReadTool - 读取文件
  • 保留 FileEditTool - 编辑文件

如果不是 Simple Mode,那么继续第二步过滤

第二步:移除 Special Tools(内部专用)

该步骤会检测并移除内部专用工具,这部分移除的工具只在特定场景下使用,一般情况下不暴露给大模型。

  • 移除 ListMcpResourcesTool - 列出 MCP 服务器的资源列表。内部后台管理,不需要模型知道

  • 移除 ReadMcpResourceTool - 读取 MCP 服务器的资源内容。内部后台管理,不需要模型知道

这里的两个工具先加载,再移除,逻辑上完全是没必要的,但是从代码注释中可以看出,这里应该是属于后续调整的内容

/*
 * ENABLE LATER (NEED WORK):
 * - MCPTool: TBD
 * - ListMcpResourcesTool: TBD
 * - ReadMcpResourceTool: TBD
 */

第三步:权限规则过滤

该步骤会查看用户本地配置的禁用规则,来进行进一步的匹配。目的是希望提供给用户精细化的控制工具的能力。

📂 权限规则的5个文件来源,优先级从上往下以此增高,高优先级会覆盖低优先级同一项配置。

序号来源文件路径作用优先级
1userSettings~/.claude/settings.json全局个人偏好配置最低
2projectSettings.claude/settings.json项目团队共享(Git)
3localSettings.claude/settings.local.json个人项目偏好(不提交)中高
4flagSettings--settings /path/to/file.json临时配置文件
5policySettings/etc/claude-code/managed-settings.json企业强制策略(企业用户)最高

第四步:REPL Mode 过滤

检查用户是否开启了 REPL 模式(批量操作模式),如果开启了,那么会把一些单步操作的工具移除,并使用 REPLTool 替代,调用的时候强制走 REPL 通道,一次性执行多条指令。

如果开启,会隐藏以下 8 个工具:

export const REPL_ONLY_TOOLS = new Set([
  FILE_READ_TOOL_NAME,     // 读取文件
  FILE_WRITE_TOOL_NAME,    // 写入文件
  FILE_EDIT_TOOL_NAME,     // 编辑文件
  GLOB_TOOL_NAME,          // 文件搜索
  GREP_TOOL_NAME,          // 文本搜索
  BASH_TOOL_NAME,          // 执行命令
  NOTEBOOK_EDIT_TOOL_NAME, // 编辑 Notebook
  AGENT_TOOL_NAME,         // 创建子 Agent
])

这些工具的调用会被聚合到 REPLTool 中批量执行。

第五步:isEnabled() 动态检查

动态检查每个工具在当前环境下是否可用。例如在网络断开的情况下,WebSearchTool 工具无法使用,即使提供也会失败,所以干脆不提供,避免模型调用会失败的工具。

工具是否可用,由每个工具自己维护,即前面提到的工具属性 isEnabled()。调用之后,如果返回true,那么该工具保留;如果返回 false,工具被移除。

const isEnabled = allowedTools.map(_ => _.isEnabled())
return allowedTools.filter((_, i) => isEnabled[i])

三、延时加载工具

前面部分讲到的工具,基本都是常用工具,使用频率较高,在启动时直接加载。此外,Claude Code 还有延时加载工具,在启动的时候仅仅提供工具的名称和用途,并无详细的信息,然后通过 ToolSearchTool 搜索工具来进行搜索,获取延时加载工具的详细情况。

这样做是为了尽可能减少模型上下文压力,很多工具使用视频不高,且工具 prompt 可能很繁琐和庞大,直接加载,对于模型的考验太大,所以采用延时加载的策略,初始只需要知道有哪些类型的工具就好,在使用的时候再去关注工具的细节。

例如,提供给 cc 调用的 mcp 工具,全部默认延时加载

Until fetched, only the name is known — there is no parameter schema, so the tool cannot be invoked. This tool takes a query, matches it against the deferred tool list, and returns the matched tools' complete JSONSchema definitions inside a <functions> block. Once a tool's schema appears in that result, it is callable exactly like any tool defined at the top of the prompt.

Result format: each matched tool appears as one <function>{"description": "...", "name": "...", "parameters": {...}}</function> line inside the <functions> block — the same encoding as the tool list at the top of this prompt.

Query forms:
- "select:Read,Edit,Grep" — fetch these exact tools by name
- "notebook jupyter" — keyword search, up to max_results best matches
- "+slack send" — require "slack" in the name, rank by remaining terms

可以延时加载的工具列表,会在用户对话之前插入,一同发送给模型。这条特殊信息的角色是 user,但是会被标记为 isMeta: true,用于和一般的用户信息作区分。被标记为 isMeta: true 的信息,不会被用户看到,只能模型看到。

messagesForAPI = [
  createUserMessage({
    content: `<available-deferred-tools>\n${deferredToolList}\n</available-deferred-tools>`,
    isMeta: true,  // ← 标记为元数据消息
  }),
  ...messagesForAPI,
]

四、工具权限控制

为了检具工具调用的自由度及安全控制,ClausCode 的工具设计可谓是把权限控制做到了极致。基本上每一个工具都考虑到其使用场景和危险程度,做了针对每一个工具的精细化控制。这里我们为了方便探讨,可以简单分为4个类别。(有很多工具没法简单的归为其中的某一个类别,一些工具会有更加专用的权限控制,或者存在多种混合权限控制,这里简单分为4类,主要是为了方便探讨。

4.1 无权限校验 - Level 1

代表工具TaskCreateToolTodoWriteToolSleepTool

TaskCreateTool 为例,此工具用于在内存中构建模型列好的计划,一旦任务完成即销毁,不留痕,无隐患,并且不涉及到外部资源,所以安全程度较高,不做权限控制,模型可以随意调用。

4.2 外部访问权限 - Level 2

代表工具WebSearchToolWebFetchToolMCPTool

此类工具一方面受限于环境状态,需要在 工具加载阶段 通过 isEnabled() 判断是否正常可用(二、工具过滤中第五步)。同时因为涉及到外部环境的访问,所以需要在 调用阶段 通过 checkPermissions() 征求用户的同意。

  • Yes:允许本次使用,下次使用时仍然询问。
  • Yes, and don't ask again:允许使用,以后不再询问。
  • No:本次不允许,下次使用时仍然询问。

4.3 文件访问权限 - Level 3

代表工具FileReadToolFileEditToolGrepTool

此类工具因为涉及到本地文件的访问和操作,所以需要进行更严格的校验,主要有以下几层校验逻辑。

  1. 路径安全检查UNC 路径防御,阻止工具访问网络路径下的文件(未经过安全验证)。同时该步也会 检测 Windows 可疑路径,阻止包含特殊模式的路径(交替数据流、短文件名等)。

  2. 权限规则检查:检查用户的规则文件 deny > ask > allow,根据用户配置的规则来控制是否可以操作。deny 最优先,一旦匹配,直接拒绝;ask 次优先,如果 deny 不匹配,检查 ask;allow 最后,只有 deny 和 ask 都不匹配,才检查。

  3. 权限溢出机制:高权限的操作,可以覆盖低权限的操作。比如文件允许编辑,那么文件就允许读取,即 Edit 权限溢出到 Read 权限。(前提是没有显式的 read deny/ask 规则)

  4. 目录白名单检查工作目录自动允许,位于工作目录内的文件自动允许读取;内部路径自动允许:session-memory、plans、tool-results 等内部路径自动允许。

  5. 危险文件保护:系统中的危险目录,或者危险文件,即使有用户配置的名单,仍然需要通过主动询问来获得权限,不可以直接修改。例如 .gitconfig.bashrc.zshrc 等。

  6. 文件大小限制:这类文件的限制,应该是为了防止cc在不小心访问到超大文件时,意外消耗掉大量的token,产生过多费用。超过限制的文件无法读取,但是限制通过配置调整。

  7. 设备文件防护:本规则会防止访问产生无限输出或阻塞输入的文件,比如 /dev/zero 文件会无限输出,而 /dev/stdin 会等待输入(阻塞)。

  8. Claude 设置文件保护:claude 本身的配置文件,不允许 cc 轻易的访问和修改,以防止Agent自己给自己过高的权限。

  9. 二进制文件检测:二进制文件不具备语义,给模型访问没有意义,只会浪费token。

  10. 最终默认检测:如果上述检测都通过,那么根据权限配置来决定 询问自动允许自动拒绝

4.4 命令执行权限 - Level 4

代表工具BashToolPowerShellTool

对于命令行工具而言,其存在的潜在危险和不可控因素更多,一旦发生错误,很可能是灾难性质的。所以针对命令执行,特意从5个不同的维度进行了5层安全检查。

Layer 1: 命令解析(AST + 语义检查)

该步骤使用 tree-sitter 工具将命令解析为抽象语法树(AST),然后进行语义级别的安全检查,识别命令替换、危险内建指令、数组下标执行、控制字符等静态可检测的危险模式。

Layer 2: 路径验证

验证命令中所有访问的路径是否在允许的范围内,检查输出重定向目标的路径安全性,防止访问项目目录外的文件或使用 Shell 扩展语法绕过验证。例如一些访问目录遍历攻击 cd /tmp && cat /etc/passwd,利用扩展路径绕过验证 echo x > $HOME/evil.txt 等攻击都可以在当前步骤拦截。

Layer 3: 只读约束

识别命令是否为只读操作(如 grep, ls, cat),并确保所有子命令都是只读的,不包含任何写入操作(>、>>、管道到 tee)、混合运算符(&&、|| 连接写入命令),防止用户以 "只读" 名义执行写入操作。例如 grep "TODO" src/*.ts > todo.txt,grep 是只读命令,但是 > 会写入。

Layer 4: 沙箱决策

根据命令的危险程度、是否包含网络操作、是否访问项目目录外路径等因素,决定是否使用沙箱隔离执行命令,防止危险命令直接修改主机文件系统或访问敏感资源。例如 ls -la 命令,通过 bwrap 放入沙箱中执行:

bwrap \
  --ro-bind /usr /usr \
  --bind /home/user/project . \
  --unshare-all \
  /bin/sh -c 'ls -la'

Layer 5: 命令验证

基于用户配置的 alwaysAllow、alwaysDeny、alwaysAsk 规则对命令进行匹配(区分Layer 2 的路经检测,这里主要是命令检测),再次通过 deny 检查确保 path 规则和 command 规则都被执行,根据权限模式返回最终的 allow/ask/deny 决策。

Layer 2 与 Layer 5 对比

场景Layer 2 检测Layer 5 检测结果
cat /etc/passwd❌ 拒绝(路径:/etc/*)✅ 通过Layer 2 拒绝,最终拒绝
rm -rf /tmp/test✅ 通过❌ 拒绝(命令:rm *)Layer 5 拒绝,最终拒绝
ls -la src/✅ 通过✅ 通过弹窗询问

五、总结

Claude Code 的源码很多,层级嵌套很深,阅读过程还是比较痛苦的,所以有很多地方整理的可能还不到位~

但是基于这几天的整理,最大的感受是:这不仅仅是一个AI产品,而是一套为 AI Agent 设计的完整体系。

从 Mitchell Hashimoto 提出 "Harness" 概念的那一刻起,这个隐喻就注定要成为未来 AI Agent 设计的标杆。给强大但不可控的能力加上缰绳,不是约束,而是赋能。这一点在 Claude Code 的每一个设计决策中都得到了体现。

  1. 渐进式安全

从 Level 1 的无检查,到 Level 4 的5层嵌套验证,Claude Code 没有选择 "一刀切" 的安全策略,而是根据工具的危险程度,为每一个工具都定制了专有的安全级别。这既保证了常用工具的效率,又为危险操作筑起了坚固防线。

  1. 多维度防御

没有相信简单的单一维度检查。文件权限同时检查 10 个维度,命令权限同时进行 5 层验证。不同维度之间互相弥补,互相兜底,这种防御策略是确实很值得参考。

  1. 用户自治 vs 企业管控

权限规则支持 5 层优先级,从用户的个人设置到企业的强制策略,都在一个统一的系统中各司其职。IT 管理员可以统一禁止 rm -rf 命令,开发者可以为自己的项目配置特定规则。在总的框架下,提供安全范围的自由度。

  1. 静态检查 + 运行时隔离

Tree-sitter 抽象语法树负责静态分析,Bubblewrap 负责运行时隔离。前者在代码层面上发现问题,后者在运行层面上限制影响。两者结合,形成了完整的防御体系。

Harness 体现了一种典型的架构设计哲学。它不同于可标准化的方法论,而是需要架构师在实践中体悟。即在安全与自由的边界上,如何寻找完美的平衡。这个平衡点的位置,终究是仁者见仁,智者见智的。