Back to Blog

Claude Code Harness 第8章:工具提示词作为微型驾驭器

2026-04-05
Claude Code Tool Prompts Micro-Harnesses Tool Design

前言

在第5章中,我们剖析了系统提示词的宏观架构——段落注册、缓存分层、动态拼装。但系统提示词只是"顶层战略"。在每次工具调用的微观层面,还有一套平行的驾驭体系在运作:工具提示词(tool description / tool prompt)。它们作为 description 字段注入 API 请求的 tools 数组,直接塑造模型对每个工具的使用方式。

本章将深入分析 Claude Code 核心工具的提示词设计,揭示其中的引导策略与可复用模式。我们将看到,每个工具的提示词实际上是一个微型驾驭器(micro-harness),包含功能描述、正面引导、负面禁令、条件分支和格式模板等多个层次的行为约束。

8.1 工具提示词的驾驭本质

工具的 description 字段在 Anthropic API 中的定位是"告诉模型这个工具做什么"。但 Claude Code 将这个字段从简单的功能描述,扩展为一套完整的行为约束协议

8.1.1 双层驾驭架构

工具提示词与系统提示词共同构成 Claude Code 的"双层驾驭架构":

  • 系统提示词:设定全局人格、工作原则和高层策略
  • 工具提示词:塑造局部行为、操作约束和输出格式

这种设计的核心洞察是:模型对每个工具的行为质量,直接受该工具提示词质量制约。系统提示词告诉模型"你是谁",工具提示词告诉模型"怎么做"。

8.1.2 微型驾驭器的五大要素

每个工具的提示词作为微型驾驭器,通常包含以下五个要素:

  1. 功能描述:工具做什么,能解决什么问题
  2. 正面引导:应该怎么用,在什么场景下用
  3. 负面禁令:不能怎么用,什么行为是禁止的
  4. 条件分支:在特定场景下该怎么做(如嵌套式规则)
  5. 格式模板:输出应该长什么样,参数如何组合

这五个要素共同构成了一个完整的行为契约,不仅告诉模型工具的能力边界,更重要的是引导模型在正确场景下以正确方式使用工具。

8.2 BashTool:最复杂的微型驾驭器

BashTool 是 Claude Code 中提示词最长、约束最密集的工具。它的提示词由 getSimplePrompt() 函数动态生成,最终可达数千字。

8.2.1 工具偏好矩阵:流量导向策略

提示词的第一部分建立了一个明确的工具偏好矩阵

// src/tools/BashTool/prompt.ts
const toolPreferenceItems = [
  `File search: Use ${GLOB_TOOL_NAME} (NOT find or ls)`,
  `Content search: Use ${GREP_TOOL_NAME} (NOT grep or rg)`,
  `Read files: Use ${FILE_READ_TOOL_NAME} (NOT cat/head/tail)`,
  `Edit files: Use ${FILE_EDIT_TOOL_NAME} (NOT sed/awk)`,
  `Write files: Use ${FILE_WRITE_TOOL_NAME} (NOT echo >/cat <

这个设计体现了一个重要的驾驭模式:流量导向(traffic steering)。Bash 是一个"万能工具"——理论上可以完成文件读写、搜索、编辑等所有操作。但让模型通过 Bash 完成这些操作会带来两个问题:

  1. 用户体验差:专用工具(如 FileEditTool)有结构化输入、可视化 diff、权限检查等能力,Bash 命令则是不透明的字符串
  2. 权限控制失效:专用工具有细粒度权限校验,Bash 命令绕过了这些检查

嵌入式工具的条件适配

注意源码中的条件分支:

const embedded = hasEmbeddedSearchTools()

const toolPreferenceItems = [
  ...(embedded
    ? []
    : [
        `File search: Use ${GLOB_TOOL_NAME} (NOT find or ls)`,
        `Content search: Use ${GREP_TOOL_NAME} (NOT grep or rg)`,
      ]),
  // ... 其他项
]

当系统检测到嵌入式搜索工具时,findgrep 从禁用列表中移除。这是为 Anthropic 内部构建版本做的适配——这些构建将 find/grep 别名为嵌入式 bfs/ugrep,同时移除了独立的 Glob/Grep 工具。

可复用模式——"万能工具降级":当你的工具集中存在一个功能覆盖面极广的工具时,在其提示词中显式列出"什么场景应该用什么替代工具",避免模型过度依赖单一工具。

8.2.2 命令执行指南:从超时到并发

提示词的第二部分是一套详细的命令执行规范,涵盖多个维度:

目录验证与路径引用

const instructionItems: Array = [
  'If your command will create new directories or files, first use this tool to run `ls` to verify the parent directory exists and is the correct location.',
  'Always quote file paths that contain spaces with double quotes in your command (e.g., cd "path with spaces/file.txt")',
  'Try to maintain your current working directory throughout the session by using absolute paths and avoiding usage of `cd`.',
  // ...
]

这些规则看似简单,但每个都对应具体的故障模式:

  • 目录验证防止在错误位置创建文件
  • 路径引用防止包含空格的路径被错误分割
  • 工作目录保持防止相对路径的混乱

超时控制

`You may specify an optional timeout in milliseconds (up to ${getMaxTimeoutMs()}ms / ${getMaxTimeoutMs() / 60000} minutes). By default, your command will timeout after ${getDefaultTimeoutMs()}ms (${getDefaultTimeoutMs() / 60000} minutes).`

默认 120 秒,最大 600 秒(10分钟)。这是一个资源保护机制——防止无限循环或网络卡死消耗会话预算。

多命令并发指南

最精巧的是多命令并发指南

const multipleCommandsSubitems = [
  `If the commands are independent and can run in parallel, make multiple ${BASH_TOOL_NAME} tool calls in a single message.`,
  `If the commands depend on each other and must run sequentially, use a single ${BASH_TOOL_NAME} call with '&&' to chain them together.`,
  "Use ';' only when you need to run commands sequentially but don't care if earlier commands fail.",
  'DO NOT use newlines to separate commands.',
]

这不是简单的"最佳实践建议",而是一套并发决策树

flowchart TD
    A[需要执行多个命令] --> B{命令是否独立?}
    B -->|是| C[并行工具调用
单消息多工具] B -->|否| D{是否允许失败?} D -->|否| E[使用 && 链式调用
任一失败则停止] D -->|是| F[使用 ; 分隔
继续执行后续命令] C --> G[❌ 禁止用换行符分隔] E --> G F --> G

每条规则都对应一个具体的故障模式:

  • 并行调用提升性能(如 git statusgit diff 同时执行)
  • && 确保依赖关系(如 mkdir foo && cd foo
  • ; 用于容错场景(如清理命令)
  • 禁止换行符防止意外行为

8.2.3 Git 安全协议:深度防御

Git 操作是 BashTool 提示词中最重要的安全领域。完整的 Git 安全协议定义在 getCommitAndPRInstructions() 函数中,其核心禁令列表构成了一道七层防线

Git Safety Protocol:
- NEVER update the git config
- NEVER run destructive git commands (push --force, reset --hard, checkout ., restore ., clean -f, branch -D) unless the user explicitly requests these actions
- NEVER skip hooks (--no-verify, --no-gpg-sign, etc) unless the user explicitly requests it
- NEVER run force push to main/master, warn the user if they request it
- CRITICAL: Always create NEW commits rather than amending, unless the user explicitly requests a git amend
- When staging files, prefer adding specific files by name rather than using "git add -A" or "git add ."
- NEVER commit changes unless the user explicitly asks you to

每一条禁令都对应一个真实的数据丢失场景:

禁令 防御的故障场景
NEVER update git config 模型可能修改用户的全局 Git 配置
NEVER push --force 覆盖远程仓库的提交历史
NEVER skip hooks 绕过代码质量检查、签名验证
NEVER force push to main 破坏团队共享分支
Always create NEW commits pre-commit hook 失败后 amend 会修改上一个提交
Prefer specific files git add . 可能暴露 .env、credentials
NEVER commit unless asked 避免 agent 过度自主

"CRITICAL" 标记的深意

"CRITICAL" 标记被保留给最微妙的场景:pre-commit hook 失败后的 --amend 陷阱。

CRITICAL: Always create NEW commits rather than amending, unless the user explicitly requests a git amend. When a pre-commit hook fails, the commit did NOT happen — so --amend would modify the PREVIOUS commit, which may result in destroying work or losing previous changes.

这条规则需要理解 Git 的内部机制——hook 失败意味着 commit 没有发生,此时 --amend 会修改的是上一个已存在的提交,而不是"重试当前提交"。

工作流脚手架

提示词还包含完整的 commit 工作流模板,用编号步骤明确指定哪些操作可以并行、哪些必须串行:

1. Run the following bash commands in parallel, each using the ${BASH_TOOL_NAME} tool:
  - Run a git status command to see all untracked files.
  - Run a git diff command to see both staged and unstaged changes.
  - Run a git log command to see recent commit messages.
2. Analyze all staged changes and draft a commit message:
  - Summarize the nature of the changes
  - Do not commit files that likely contain secrets
  - Draft a concise (1-2 sentences) commit message
3. Run the following commands in parallel:
   - Add relevant untracked files to the staging area.
   - Create the commit with a message ending with attribution.
   - Run git status after the commit completes to verify success.

这是一种**工作流脚手架(workflow scaffolding)**模式——不是告诉模型"做什么",而是告诉它"按什么顺序做"。

8.2.4 沙箱配置的 JSON 内联

当沙箱(sandbox)启用时,getSimpleSandboxSection() 函数会将完整的沙箱配置以 JSON 格式内联到提示词中:

function getSimpleSandboxSection(): string {
  if (!SandboxManager.isSandboxingEnabled()) {
    return ''
  }

  const filesystemConfig = {
    read: {
      denyOnly: dedup(fsReadConfig.denyOnly),
      allowWithinDeny: dedup(fsReadConfig.allowWithinDeny),
    },
    write: {
      allowOnly: normalizeAllowOnly(fsWriteConfig.allowOnly),
      denyWithinAllow: dedup(fsWriteConfig.denyWithinAllow),
    },
  }

  const networkConfig = {
    ...(networkRestrictionConfig?.allowedHosts && {
      allowedHosts: dedup(networkRestrictionConfig.allowedHosts),
    }),
    // ...
  }

  return [
    '## Command sandbox',
    'By default, your command will be run in a sandbox.',
    'The sandbox has the following restrictions:',
    `Filesystem: ${jsonStringify(filesystemConfig)}`,
    `Network: ${jsonStringify(networkConfig)}`,
    // ...
  ].join('\n')
}

这是一个值得深思的设计决策:将机器可读的安全策略直接暴露给模型。模型需要"理解"自己可以访问哪些路径、可以连接哪些网络主机,才能在生成命令时主动避免违规。JSON 格式保证了信息的精确性和无歧义性。

路径归一化优化

源码中的 normalizeAllowOnly 函数:

const claudeTempDir = getClaudeTempDir()
const normalizeAllowOnly = (paths: string[]): string[] =>
  [...new Set(paths)].map(p => (p === claudeTempDir ? '$TMPDIR' : p))

将用户特定的临时目录路径替换为 $TMPDIR 占位符。这个优化保证了跨用户的 prompt 缓存一致性——每个用户的临时目录路径不同(如 /private/tmp/claude-1001/),但 $TMPDIR 是通用的。

dedup 函数也节省了约 150-200 token——SandboxManager 合并多层配置时不去重,内联前去重避免重复路径。

可复用模式——"策略透明化":当安全策略需要模型配合执行时,将策略的完整规则集以结构化格式(JSON/YAML)内联到提示词中,让模型在生成阶段就能自检合规性。

8.2.5 sleep 反模式抑制

提示词专门用一个小节来抑制 sleep 的滥用:

const sleepSubitems = [
  'Do not sleep between commands that can run immediately — just run them.',
  'Use the Monitor tool to stream events from a background process.',
  'If your command is long running and you would like to be notified when it finishes — use `run_in_background`.',
  'Do not retry failing commands in a sleep loop — diagnose the root cause.',
  'If waiting for a background task you started with `run_in_background`, you will be notified when it completes — do not poll.',
  'If you must sleep, keep the duration short (1-5 seconds) to avoid blocking the user.',
]

这是一个典型的**反模式抑制(anti-pattern suppression)**策略。LLM 在代码生成场景中倾向于使用 sleep + 轮询来处理异步等待,因为这是训练数据中最常见的模式。提示词通过逐一列举替代方案(后台执行、事件通知、诊断根因)来"覆写"这个默认行为。

8.3 FileEditTool:前置条件强制与接口契约

FileEditTool 的提示词相比 BashTool 精简得多,但每一句都承载着关键的工程约束。

8.3.1 前置读取强制

提示词的第一条规则:

function getPreReadInstruction(): string {
  return `\n- You must use your \`${FILE_READ_TOOL_NAME}\` tool at least once in the conversation before editing. This tool will error if you attempt an edit without reading the file. `
}

这不是一个"建议",而是一个硬性约束——工具的运行时实现会检查对话历史中是否存在对该文件的 Read 调用,没有则直接返回错误。提示词中的说明是为了让模型提前知道这个约束,避免浪费一次工具调用。

这个设计解决了一个核心问题:模型幻觉(hallucination)。如果模型不先读取文件就尝试编辑,它对文件内容的假设可能完全错误。强制先读取保证了编辑操作基于真实的文件状态,而不是模型对文件内容的"记忆"或"猜测"。

可复用模式——"前置条件强制":当工具 B 的正确性依赖于工具 A 的先行调用时,在 B 的提示词中声明这个依赖关系,并在 B 的运行时中强制检查。双重保障——提示词层防止浪费调用,运行时层兜底防止错误操作。

8.3.2 最小唯一 old_string

提示词对 old_string 参数的要求体现了精妙的平衡:

- The edit will FAIL if \`old_string\` is not unique in the file. Either provide a larger string with more surrounding context to make it unique or use \`replace_all\` to change every instance of \`old_string\`.

对于 Anthropic 内部用户,还有一条额外的优化指引:

const minimalUniquenessHint =
  process.env.USER_TYPE === 'ant'
    ? `\n- Use the smallest old_string that's clearly unique — usually 2-4 adjacent lines is sufficient. Avoid including 10+ lines of context when less uniquely identifies the target.`
    : ''

这揭示了一个token 经济学问题:模型在使用 FileEditTool 时,需要在 old_string 参数中提供要替换的原文。如果模型习惯性地包含大段上下文来"确保唯一性",每次编辑操作的 token 消耗就会急剧膨胀。"2-4 行"的指导让模型在唯一性和简洁性之间找到甜点。

8.3.3 缩进保持与行号前缀

提示词中最容易被忽视但最关键的技术细节:

const prefixFormat = isCompactLinePrefixEnabled()
  ? 'line number + tab'
  : 'spaces + line number + arrow'

return `Performs exact string replacements in files.

Usage:
- When editing text from Read tool output, ensure you preserve the exact indentation (tabs/spaces) as it appears AFTER the line number prefix. The line number prefix format is: ${prefixFormat}. Everything after that is the actual file content to match. Never include any part of the line number prefix in the old_string or new_string.`

Read 工具返回的内容带有行号前缀(如 42 →),模型需要在编辑时剥离这个前缀,只提取实际的文件内容作为 old_string。这是 Read 工具与 Edit 工具之间的接口契约——提示词承担了"接口文档"的角色。

可复用模式——"工具间接口声明":当两个工具的输出/输入存在格式转换关系时,在下游工具的提示词中显式描述上游工具的输出格式,避免模型在格式转换中出错。

8.4 FileReadTool:资源感知的读取策略

FileReadTool 的提示词看似简单,实则包含了精心设计的资源管理策略。

8.4.1 2000 行默认限制

export const MAX_LINES_TO_READ = 2000

// 在提示词模板中:
`By default, it reads up to ${MAX_LINES_TO_READ} lines starting from the beginning of the file`

2000 行是一个经过权衡的数字。Anthropic 的模型有 200K token 的上下文窗口,但上下文越大,注意力分散越严重、推理成本越高。2000 行大约对应 8000-16000 个 token(取决于代码密度),占上下文窗口的 4-8%。这个预算足够覆盖绝大多数单文件场景,同时为多文件操作留出空间。

8.4.2 offset/limit 的渐进式引导

提示词对 offset/limit 参数提供了两种措辞模式:

export const OFFSET_INSTRUCTION_DEFAULT =
  "- You can optionally specify a line offset and limit (especially handy for long files), but it's recommended to read the whole file by not providing these parameters"

export const OFFSET_INSTRUCTION_TARGETED =
  '- When you already know which part of the file you need, only read that part. This can be important for larger files.'

两种模式服务于不同的使用阶段:

  • DEFAULT 模式鼓励完整读取——适用于模型首次接触文件时,需要全局理解
  • TARGETED 模式鼓励精准读取——适用于模型已经知道目标位置时,节省 token 预算

8.4.3 多媒体能力声明

提示词用一系列声明式语句扩展了 Read 工具的能力边界:

- This tool allows Claude Code to read images (eg PNG, JPG, etc). When reading an image file the contents are presented visually as Claude Code is a multimodal LLM.
- This tool can read PDF files (.pdf). For large PDFs (more than 10 pages), you MUST provide the pages parameter to read specific page ranges (e.g., pages: "1-5"). Reading a large PDF without the pages parameter will fail. Maximum 20 pages per request.
- This tool can read Jupyter notebooks (.ipynb files) and returns all cells with their outputs.

PDF 的分页限制是一个渐进式资源限制:小文件直接读取,大文件强制分页。这比"所有文件都必须分页"或"不限制分页"都更合理——前者增加不必要的工具调用轮次,后者可能一次性注入过多内容。

注意 PDF 支持是条件性的:

${isPDFSupported()
  ? '\n- This tool can read PDF files (.pdf). For large PDFs (more than 10 pages), you MUST provide the pages parameter...'
  : ''
}

如果运行时环境不支持 PDF 解析,整个 PDF 说明段落从提示词中消失。这避免了"提示词承诺了运行时无法兑现的能力"这一常见陷阱。

可复用模式——"能力声明与运行时对齐":工具提示词中的能力描述应该由运行时能力动态决定。如果某个功能在特定环境下不可用,不要在提示词中提及它——这会导致模型尝试使用不存在的功能,产生困惑和浪费。

8.5 GrepTool:排他性声明与安全默认值

GrepTool 的提示词精简到极致,但每一行都是硬约束。

8.5.1 排他性声明

提示词的第一条使用规则:

`ALWAYS use ${GREP_TOOL_NAME} for search tasks. NEVER invoke \`grep\` or \`rg\` as a ${BASH_TOOL_NAME} command. The ${GREP_TOOL_NAME} tool has been optimized for correct permissions and access.`

这是与 BashTool 的工具偏好矩阵双向配合的设计:

  • BashTool 说"不要用 bash 做搜索"
  • GrepTool 说"搜索必须用我"

两个方向的约束形成闭环,最大程度降低模型"走错路"的概率。

"has been optimized for correct permissions and access" 给出了理由,而非仅仅发出禁令。理由很重要——GrepTool 底层调用的是相同的 ripgrep,但包裹了权限检查、忽略模式应用和版本控制目录排除。通过 Bash 直接调用 rg 会绕过这些安全层。

8.5.2 ripgrep 语法提示

提示词提供了三条关键的语法差异说明:

- Supports full regex syntax (e.g., "log.*Error", "function\\s+\\w+")
- Filter files with glob parameter (e.g., "*.js", "**/*.tsx") or type parameter (e.g., "js", "py", "rust")
- Pattern syntax: Uses ripgrep (not grep) - literal braces need escaping (use \`interface\\{\\}\` to find \`interface{}\` in Go code)
- Multiline matching: By default patterns match within single lines only. For cross-line patterns like \`struct \\{[\\s\\S]*?field\`, use \`multiline: true\`

第一条明确了语法家族(ripgrep 的 Rust regex),第二条给出了最常见的陷阱(大括号需要转义——这与 GNU grep 不同),第三条解释了 multiline 参数的使用场景。

从代码实现看,multiline: true 对应的 ripgrep 参数是 -U --multiline-dotall。提示词选择用"使用场景 + 示例"来解释这个功能,而不是暴露底层参数细节——模型不需要知道 -U 是什么,只需要知道什么时候设置 multiline: true

8.5.3 输出模式与 head_limit

GrepTool 的输入 schema 定义了丰富的参数,但提示词中只简要提及三种输出模式:

Output modes: "content" shows matching lines, "files_with_matches" shows only file paths (default), "count" shows match counts

head_limit 参数的设计尤其值得关注:

const DEFAULT_HEAD_LIMIT = 250

// 在 schema 描述中:
'Defaults to 250 when unspecified. Pass 0 for unlimited (use sparingly — large result sets waste context).'

默认 250 条结果上限是一个上下文保护机制——不受限的 content 模式搜索可能填满 20KB 的工具结果持久化阈值。"use sparingly" 的措辞给模型一个温和的警告,而 0 作为"无限制"的逃生舱口保留了灵活性。

可复用模式——"安全默认值 + 逃生舱口":为可能产生大量输出的工具设置保守的默认限制,同时提供一个显式的方式来解除限制。在提示词中说明两者的存在和适用场景。

8.6 GlobTool:简洁的工具定位

GlobTool 的提示词展示了如何用最少的文字实现清晰的行为引导:

export const DESCRIPTION = `- Fast file pattern matching tool that works with any codebase size
- Supports glob patterns like "**/*.js" or "src/**/*.ts"
- Returns matching file paths sorted by modification time
- Use this tool when you need to find files by name patterns
- When you are doing an open ended search that may require multiple rounds of globbing and grepping, use the Agent tool instead`

这个提示词的精妙之处在于:

  1. 能力定位:第一行就明确工具的核心价值——"works with any codebase size"
  2. 使用模式:第二、三行展示典型的使用方式
  3. 边界划分:最后一行指出了工具的边界——当任务过于复杂时,应该使用 Agent 工具

这种"三明治结构"(能力-使用-边界)是工具提示词的有效模板。

8.7 FileWriteTool:写入前的读取要求

FileWriteTool 的提示词与 FileEditTool 类似,也强制要求前置读取:

function getPreReadInstruction(): string {
  return `\n- If this is an existing file, you MUST use the ${FILE_READ_TOOL_NAME} tool first to read the file's contents. This tool will fail if you did not read the file first.`
}

export const getWriteToolDescription(): string {
  return `Writes a file to the local filesystem.

Usage:
- This tool will overwrite the existing file if there is one at the provided path.${getPreReadInstruction()}
- Prefer the Edit tool for modifying existing files — it only sends the diff. Only use this tool to create new files or for complete rewrites.
- NEVER create documentation files (*.md) or README files unless explicitly requested by the User.
- Only use emojis if the user explicitly requests it.`
}

这个提示词引入了几个关键约束:

  1. 写入前读取:对于已存在的文件,必须先读取才能写入——防止意外覆盖
  2. Edit vs Write:明确区分两个工具的使用场景——Edit 用于修改,Write 用于创建或完全重写
  3. 文件类型限制:除非用户明确要求,否则不创建文档文件——防止模型过度主动

8.8 AgentTool:动态提示词与委派质量

AgentTool 是所有工具中提示词生成逻辑最复杂的,因为它需要根据运行时状态动态组合内容。

8.8.1 内联 vs 附件:agent 列表的两种注入方式

提示词中的 agent 列表可以通过两种方式注入:

export function shouldInjectAgentListInMessages(): boolean {
  if (isEnvTruthy(process.env.CLAUDE_CODE_AGENT_LIST_IN_MESSAGES)) return true
  if (isEnvDefinedFalsy(process.env.CLAUDE_CODE_AGENT_LIST_IN_MESSAGES)) return false
  return getFeatureValue_CACHED_MAY_BE_STALE('tengu_agent_list_attach', false)
}

方式一(内联):agent 列表直接嵌入工具描述。

const agentListSection = listViaAttachment
  ? `Available agent types are listed in  messages in the conversation.`
  : `Available agent types and the tools they have access to:
${effectiveAgents.map(agent => formatAgentLine(agent)).join('\n')}`

方式二(附件):工具描述只包含静态文本,实际列表通过 agent_listing_delta 附件消息单独注入。

源码注释解释了动机:动态 agent 列表占了全局 cache_creation token 的约 10.2%。每当 MCP 服务器异步连接、插件重载、或权限模式变化时,agent 列表就会变化,导致包含列表的工具 schema 全部失效,触发昂贵的缓存重建。将列表移到附件消息中,工具描述变为静态文本,从而保护了工具 schema 层的 prompt 缓存。

每个 agent 的描述格式:

export function formatAgentLine(agent: AgentDefinition): string {
  const toolsDescription = getToolsDescription(agent)
  return `- ${agent.agentType}: ${agent.whenToUse} (Tools: ${toolsDescription})`
}

getToolsDescription 函数处理了工具白名单和黑名单的交叉过滤,最终生成如 "All tools except Bash, Agent" 或 "Read, Grep, Glob" 这样的描述。这让模型知道每个 agent 类型能用什么工具,从而做出合理的委派决策。

可复用模式——"动态内容外移":当工具提示词中的某个部分频繁变化且对缓存影响大时,将其从工具 description 移到消息流中(如附件、system-reminder),保持工具描述的稳定性。

8.8.2 Fork 子代理:继承上下文的轻量委派

isForkSubagentEnabled() 为 true 时,提示词增加一个"When to fork"段落,引导模型在两种委派模式间选择:

  1. Fork(省略 subagent_type:继承父代理的完整对话上下文,适合研究型和实现型任务
  2. Fresh agent(指定 subagent_type:从零开始,需要完整的上下文传递

Fork 的使用指南包含三条核心纪律:

**Don't peek.** The tool result includes an \`output_file\` path — do not Read or tail it unless the user explicitly asks for a progress check. You get a completion notification; trust it.

**Don't race.** After launching, you know nothing about what the fork found. Never fabricate or predict fork results in any format — not as prose, summary, or structured output.

**Writing a fork prompt.** Since the fork inherits your context, the prompt is a *directive* — what to do, not what the situation is. Be specific about scope: what's in, what's out, what another agent is handling.

"Don't peek" 防止父代理读取 fork 的中间输出,这会把 fork 的工具噪音拉入父代理的上下文,违背了 fork 的初衷。"Don't race" 防止父代理在结果返回前"猜测"fork 的结论——这是 LLM 的一个已知倾向。

8.8.3 Prompt 写作指南:防止浅层委派

提示词中最独特的部分是一段"如何写好 agent prompt"的指南:

## Writing the prompt

Brief the agent like a smart colleague who just walked into the room — it hasn't seen this conversation, doesn't know what you've tried, doesn't understand why this task matters.
- Explain what you're trying to accomplish and why.
- Describe what you've already learned or ruled out.
- Give enough context about the surrounding problem that the agent can make judgment calls rather than just following a narrow instruction.

**Never delegate understanding.** Don't write "based on your findings, fix the bug" or "based on the research, implement it." Those phrases push synthesis onto the agent instead of doing it yourself. Write prompts that prove you understood: include file paths, line numbers, what specifically to change.

"Never delegate understanding" 是一条深刻的元认知约束。它防止模型将需要综合判断的思考工作甩给子代理——子代理应该是执行者,不是决策者。这条规则将"理解"锚定在父代理身上,确保工作流中的知识不会丢失。

可复用模式——"委派质量保障":当工具涉及向子系统传递任务时,在提示词中约束任务描述的质量标准,防止模型生成模糊、不完整的委派指令。

8.9 SkillTool:预算约束与三级截断

SkillTool 的独特之处在于它不仅驾驭模型的行为,还管理自身提示词的体积

8.9.1 1% 上下文窗口预算

export const SKILL_BUDGET_CONTEXT_PERCENT = 0.01
export const CHARS_PER_TOKEN = 4
export const DEFAULT_CHAR_BUDGET = 8_000 // Fallback: 1% of 200k * 4

技能列表的总字符预算被硬性限制为上下文窗口的 1%。对于 200K token 的上下文窗口,这是 200K × 4 chars/token × 1% = 8000 字符。这个预算约束确保了技能发现功能不会侵蚀模型的工作上下文——技能列表是"目录",不是"内容",模型只需要看到足够的信息来决定是否调用某个技能,实际的技能内容在调用时才加载。

8.9.2 三级截断策略

formatCommandsWithinBudget 函数实现了一套渐进式截断策略:

第一级:完整保留。 如果所有技能的完整描述加起来不超过预算,全部保留。

if (fullTotal <= budget) {
  return fullEntries.map(e => e.full).join('\n')
}

第二级:描述裁剪。 超预算时,将非内置技能(non-bundled)的描述裁剪到平均可用长度。内置技能(bundled)始终保留完整描述。

const maxDescLen = Math.floor(availableForDescs / restCommands.length)
return commands.map((cmd, i) => {
  if (bundledIndices.has(i)) return fullEntries[i]!.full
  const description = getCommandDescription(cmd)
  return `- ${cmd.name}: ${truncate(description, maxDescLen)}`
}).join('\n')

第三级:仅保留名称。 如果裁剪后的平均描述长度小于 20 字符(MIN_DESC_LENGTH),非内置技能退化为仅显示名称。

if (maxDescLen < MIN_DESC_LENGTH) {
  return commands
    .map((cmd, i) =>
      bundledIndices.has(i) ? fullEntries[i]!.full : `- ${cmd.name}`,
    )
    .join('\n')
}

这套三级策略的优先级是:内置技能 > 非内置技能的描述 > 非内置技能的名称。内置技能作为 Claude Code 的核心功能,永远不会被截断。第三方插件技能则按需降级,确保在任何规模的技能生态中都能控制 token 成本。

8.9.3 单条目硬上限

除了总预算外,每个技能条目还有独立的硬上限:

export const MAX_LISTING_DESC_CHARS = 250

function getCommandDescription(cmd: Command): string {
  const desc = cmd.whenToUse
    ? `${cmd.description} - ${cmd.whenToUse}`
    : cmd.description
  return desc.length > MAX_LISTING_DESC_CHARS
    ? desc.slice(0, MAX_LISTING_DESC_CHARS - 1) + '\u2026'
    : desc
}

注释解释了原因:技能列表是发现(discovery) 用途,不是使用(usage) 用途。冗长的 whenToUse 字符串浪费的是 turn-1 的 cache_creation token,对技能匹配率没有提升。

8.9.4 调用协议

SkillTool 的核心提示词包含一条关键的阻塞性要求

- When a skill matches the user's request, this is a BLOCKING REQUIREMENT: invoke the relevant Skill tool BEFORE generating any other response about the task

"BLOCKING REQUIREMENT" 是 Claude Code 提示词体系中最强的约束措辞之一。它要求模型在识别到匹配技能时,立即调用 Skill 工具,不得先生成文本回复。这防止了一个常见的反模式:模型先输出一段分析文字,然后才调用技能——这段文字往往与技能加载后的实际指令冲突。

另一条防御性规则:

- If you see a <${COMMAND_NAME_TAG}> tag in the current conversation turn, the skill has ALREADY been loaded - follow the instructions directly instead of calling this tool again

这防止了重复加载:如果技能已经通过 <command-name> 标签注入到当前轮次,模型不应该再次调用 SkillTool,而应该直接执行技能指令。

可复用模式——"预算感知的目录生成":当工具需要向模型呈现一个动态增长的列表(插件、技能、API 端点等)时,为列表分配固定的 token 预算,并实现多级降级策略。优先保留高价值条目的完整性,低优先级条目渐进退化。

8.10 WebSearchTool:强制引用与时间感知

WebSearchTool 的提示词展示了几种特殊的约束模式。

8.10.1 强制引用要求

export function getWebSearchPrompt(): string {
  return `
CRITICAL REQUIREMENT - You MUST follow this:
  - After answering the user's question, you MUST include a "Sources:" section at the end of your response
  - In the Sources section, list all relevant URLs from the search results as markdown hyperlinks: [Title](URL)
  - This is MANDATORY - never skip including sources in your response
`
}

这是一个输出格式强制约束——不仅要求模型使用工具,还要求模型在最终输出中包含特定格式的引用。这种约束确保了信息的可追溯性,是网络搜索工具的特殊要求。

8.10.2 时间感知提示

const currentMonthYear = getLocalMonthYear()
return `
IMPORTANT - Use the correct year in search queries:
  - The current month is ${currentMonthYear}. You MUST use this year when searching for recent information, documentation, or current events.
  - Example: If the user asks for "latest React docs", search for "React documentation" with the current year, NOT last year
`

这是一个时间感知提示——模型的训练数据有截止日期,搜索时容易使用过年的时间。通过在提示词中注入当前时间,引导模型搜索最新内容。

8.11 TodoWriteTool:何时使用的明确指南

TodoWriteTool 的提示词展示了一种独特的"元提示"风格——它不仅告诉模型如何使用工具,还告诉模型什么时候不应该使用工具

## When to Use This Tool
Use this tool proactively in these scenarios:
1. Complex multi-step tasks - When a task requires 3 or more distinct steps or actions
2. Non-trivial and complex tasks - Tasks that require careful planning or multiple operations
3. User explicitly requests todo list
4. User provides multiple tasks
5. After receiving new instructions
6. When you start working on a task - Mark it as in_progress BEFORE beginning work
7. After completing a task - Mark it as completed and add follow-up tasks

## When NOT to Use This Tool
Skip using this tool when:
1. There is only a single, straightforward task
2. The task is trivial
3. The task can be completed in less than 3 trivial steps
4. The task is purely conversational or informational

这种双向指南(when to use + when NOT to use)比单纯的功能描述更有效,因为它帮助模型在边界情况下做出正确决策。提示词还包含大量示例,展示了在不同场景下应该如何决策。

8.11.1 任务状态管理

提示词详细说明了任务状态的转换规则:

1. **Task States**: Use these states to track progress:
   - pending: Task not yet started
   - in_progress: Currently working on (limit to ONE task at a time)
   - completed: Task finished successfully

   **IMPORTANT**: Task descriptions must have two forms:
   - content: The imperative form (e.g., "Run tests")
   - activeForm: The present continuous form (e.g., "Running tests")

2. **Task Management**:
   - Update task status in real-time as you work
   - Mark tasks complete IMMEDIATELY after finishing
   - Exactly ONE task must be in_progress at any time
   - Complete current tasks before starting new ones

这种状态机规范确保了任务列表的一致性和可预测性。

8.12 LSPTool:能力声明与前提条件

LSPTool 的提示词展示了一种简洁而有效的工具描述模式:

export const DESCRIPTION = `Interact with Language Server Protocol (LSP) servers to get code intelligence features.

Supported operations:
- goToDefinition: Find where a symbol is defined
- findReferences: Find all references to a symbol
- hover: Get hover information (documentation, type info) for a symbol
- documentSymbol: Get all symbols (functions, classes, variables) in a document
- workspaceSymbol: Search for symbols across the entire workspace
- goToImplementation: Find implementations of an interface or abstract method
- prepareCallHierarchy: Get call hierarchy item at a position
- incomingCalls: Find all functions/methods that call the function
- outgoingCalls: Find all functions/methods called by the function

All operations require:
- filePath: The file to operate on
- line: The line number (1-based, as shown in editors)
- character: The character offset (1-based, as shown in editors)

Note: LSP servers must be configured for the file type. If no server is available, an error will be returned.`

这个提示词的结构:

  1. 总体定位:第一行说明工具的核心价值
  2. 操作列表:列出所有支持的操作及其功能
  3. 参数要求:明确所有操作必需的参数
  4. 前提条件:说明工具的依赖(LSP 服务器配置)

这种结构让模型快速理解工具的能力边界和使用前提。

8.13 工具提示词设计模式总结

从以上工具的分析中,我们可以提炼出一套通用的工具提示词设计模式。

8.13.1 工具提示词的四个象限

quadrantChart
    title 工具提示词驾驭模式象限图
    x-axis "低复杂度" --> "高复杂度"
    y-axis "行为约束" --> "资源管理"
    "BashTool": [0.85, 0.2]
    "FileEditTool": [0.3, 0.3]
    "FileReadTool": [0.4, 0.7]
    "GrepTool": [0.2, 0.6]
    "AgentTool": [0.9, 0.4]
    "SkillTool": [0.7, 0.9]

行为约束型(BashTool, FileEditTool, GrepTool):

  • 核心是告诉模型"不能做什么"和"必须怎么做"
  • 大量使用禁令(NEVER)和强制要求(MUST)
  • 侧重安全性和正确性

资源管理型(FileReadTool, SkillTool, GrepTool):

  • 核心是控制工具的 token 消耗和输出规模
  • 设置默认限制和逃生舱口
  • 侧重要求预算和性能优化

协作编排型(BashTool, AgentTool, FileEditTool):

  • 核心是定义工具间的协作规则
  • 前置条件、接口契约、并发策略
  • 侧重工作流的协调性

缓存优化型(AgentTool, SkillTool, BashTool):

  • 核心是保护 prompt 缓存的有效性
  • 动态内容外移、路径归一化、去重优化
  • 侧重 token 经济学

8.13.2 七条设计原则

  1. 双向闭环:当工具 A 不应处理某类任务时,同时在 A 中说"不要做 X,用 B",在 B 中说"做 X 必须用我"。单向约束留有漏洞。

  2. 理由先于禁令:每条 "NEVER" 后面跟一个 "because"。模型在理解原因后更不容易违反约束。

  3. 能力与运行时对齐:提示词声明的能力必须由运行时保证。条件性地注入能力描述(如 PDF 支持)。

  4. 安全默认值 + 逃生舱口:为所有可能产生大量输出或副作用的参数设置保守默认值,同时提供显式的解除方式。

  5. 预算意识:工具提示词本身消耗 token。使用预算约束、多级截断、去重优化等策略控制成本。

  6. 前置条件声明:如果工具的正确使用依赖于特定前提,在提示词中声明,在运行时中强制。双重保障优于单层防御。

  7. 委派质量标准:当工具涉及向子系统传递任务时,约束任务描述的完整性和具体性。

8.14 实战应用:设计你自己的工具提示词

基于本章的分析,以下是读者在设计自己的工具提示词时可以直接应用的建议。

8.14.1 为"万能工具"建立流量导向表

如果你的工具集中有一个功能覆盖面极广的工具(如 Bash、通用 API 调用器),在其描述的最前面放置一张"场景 -> 专用工具"的映射表。同时在每个专用工具中声明排他性。

// 在万能工具中
const toolPreferenceItems = [
  `File search: Use GlobTool (NOT find)`,
  `Content search: Use GrepTool (NOT grep)`,
  `Read files: Use ReadTool (NOT cat)`,
]

// 在专用工具中
`ALWAYS use GrepTool for search tasks. NEVER invoke grep as a Bash command.`

8.14.2 强制工具间的前置条件

当工具 B 的正确性依赖于工具 A 的先行调用时,在 B 的提示词中声明这个依赖,并在 B 的运行时中用代码强制检查。

// 在工具 B 的提示词中
`- You must use Tool A at least once before using Tool B. This tool will error if you attempt to use it without calling Tool A first.`

// 在工具 B 的运行时中
if (!hasToolBeenCalled('ToolA')) {
  throw new Error('Tool A must be called before Tool B')
}

8.14.3 将安全策略以 JSON 内联到提示词

如果模型需要"理解"自己的权限边界,将完整的策略规则集以结构化格式注入提示词。

const securityPolicy = {
  allowedPaths: ['/src/**', '/lib/**'],
  deniedPaths: ['/etc/**', '/usr/**'],
  allowedHosts: ['api.example.com'],
}

`Security policy: ${JSON.stringify(securityPolicy)}`

8.14.4 为大输出工具设置保守默认值

对所有可能产生大量输出的工具参数,设置保守的默认限制。同时提供显式的"解除限制"选项。

// 在 schema 中
headLimit: {
  type: 'number',
  default: 250,
  description: 'Defaults to 250. Pass 0 for unlimited (use sparingly).'
}

8.14.5 控制工具描述本身的 token 成本

参考 SkillTool 的预算约束和多级截断策略。为工具描述分配固定预算,优先保留核心工具的完整性。

const BUDGET = 8000 // characters
const BUNDLED_PRIORITY = true

// 三级截断:完整描述 -> 裁剪描述 -> 仅名称
if (totalLength <= BUDGET) {
  return fullDescriptions
} else if (avgDescLength >= 20) {
  return truncatedDescriptions
} else {
  return namesOnly
}

8.14.6 用动态条件控制能力声明

不要在提示词中声明运行时不一定能兑现的能力。使用条件检查来动态生成提示词。

`${isPDFSupported()
  ? '- This tool can read PDF files (.pdf).'
  : ''
}`

8.15 小结

工具提示词是 Claude Code 驾驭体系中最"接地"的层级。系统提示词设定人格,工具提示词塑造动作。通过分析十个核心工具的提示词设计,我们看到了一个核心规律:优秀的工具提示词不是功能文档,而是行为契约

它们不仅告诉模型"这个工具能做什么",更告诉它:

  • 在什么条件下用这个工具
  • 怎样用才安全
  • 什么时候该用其他工具
  • 如何与其他工具协作
  • 如何控制资源消耗

从 BashTool 的数千字安全协议,到 GrepTool 的十八字精准声明;从 AgentTool 的动态提示词生成,到 SkillTool 的三级截断策略——每个工具的提示词都是一个精心设计的微型驾驭器,共同构成了 Claude Code 的双层驾驭架构。

下一章我们将从单个工具的微观驾驭上升到工具协作的宏观编排——探索工具之间如何通过权限系统、状态传递和并发控制形成一个协调的整体。


参考资源


作者简介:本文是《Claude Code Harness》系列的第8章,深入解析工具提示词作为微型驾驭器的设计原理和实战应用。系列文章探索如何在 AI 编码工具中实现精细化的行为引导和质量控制。

Enjoyed this article? Share it with others!