Skip to content

工具延迟加载覆盖(Defer / NoDefer)

CodeBuddy Code 支持在指定可用工具列表的同时,使用 Defer(...) / NoDefer(...) 修饰符临时改变工具的延迟加载状态,而无需改动全局配置。本文是该能力的完整参考。

背景:默认情况下,工具是否走延迟加载由产品默认或 MCP 配置的 defer_loading 字段决定(详见 MCP 文档 §延迟加载)。这些是全局生效的默认设置。本特性允许你在不改默认配置的前提下,针对单次会话或单个自定义代理临时调整行为。

1. 语法速览

在任何接受工具名的「工具列表」字段里,普通工具名前加修饰符:

写法含义
Read普通工具,按全局默认决定是否 defer
Defer(Glob)该会话/代理强制让 Glob 走延迟加载(不直接进入模型的工具列表,可通过 ToolSearch 发现)
NoDefer(Bash)该会话/代理强制让 Bash 不走延迟加载(直接进入模型的工具列表)
Defer(mcp__github__*)通配匹配,把整组 MCP 工具置为延迟加载
Defer(*)把当前列表内的所有工具都置为延迟加载(极端裁剪)

* 是唯一支持的通配字符,匹配任意字符序列。其他字符(?[] 等)按字面量处理。

2. 可用渠道

修饰符可以写在以下三类「工具列表」字段中:

2.1 CLI --tools 参数

bash
codebuddy --tools "Read,Defer(Glob),NoDefer(Bash)"

ACP 与 SDK 客户端的 tools 字段与 CLI 等价。

2.2 自定义代理 frontmatter

.codebuddy/agents/*.md 的 YAML frontmatter 里:

yaml
---
name: code-reader
description: 只读型代码探索代理
tools:
  - Read
  - Grep
  - Defer(Glob)        # 让 Glob 走延迟加载,需要时通过 ToolSearch 发现
  - NoDefer(Bash)      # 即便默认让 Bash 延迟加载,本代理也强制不延迟
---

详见 子代理 §配置字段

3. 修饰符不能用在哪里

修饰符仅作用于「工具列表」字段。以下「权限规则」字段不接受修饰符:

  • --allowed-tools / settings.permissions.allow
  • --disallowed-tools / settings.permissions.deny
  • hooks 配置中的 matcher
bash
# ❌ 错误:会立即报错
codebuddy --allowed-tools "Defer(Glob)"
# Error: Defer(...) / NoDefer(...) modifiers belong in --tools, not in permission rule fields.

# ✅ 正确:分开写
codebuddy --tools "...,Defer(Glob)" --allowed-tools "Glob(src/**)"

理由:「暴露策略」(是否走延迟加载)与「行为约束」(参数过滤)是两件正交的事,分开表达更清晰。

4. 优先级与合并

最终决策按以下优先级(从高到低):

优先级来源说明
0a任一层 NoDefer(X) 命中强制非 defer(无论其他来源说什么)
0b任一层 Defer(X) 命中强制 defer
1MCP 工具级 tools[name].defer_loadingMCP 静态配置
2MCP 服务器级 defer_loadingMCP 静态配置
3环境变量 CODEBUDDY_DEFER_TOOL_LOADING全局开关
4用户设置 settings.deferToolLoading全局开关
5内置默认(CodeBuddy Code 出厂配置)兜底

关键规则

  • NoDefer 永远胜过 Defer:即使自定义代理写了 Defer(X),CLI --toolsNoDefer(X) 后,本次会话内 X 仍然不延迟加载——用户运行时表态优先。
  • CLI 与代理配置平等参与:两个来源的修饰符按并集生效,不分先后;最终结果由「NoDefer 优先」这一条规则裁决。
  • 同名重复:同一字段中多次出现同一工具时,最后一次出现的修饰符胜出(与 --allowedTools 重复时的语义一致)。

5. 自动附加:ToolSearchDeferExecuteTool

只要工具列表里出现了至少一条 Defer(...),CodeBuddy Code 会自动把以下两个工具加进去(如果还没在列表里):

工具作用
ToolSearch让模型能搜索发现被延迟加载的工具
DeferExecuteTool让模型实际调用被延迟加载的工具

也就是说,你可以这样写:

bash
# 等价于 codebuddy --tools "Bash,Read,Defer(Edit),ToolSearch,DeferExecuteTool"
codebuddy --tools "Bash,Read,Defer(Edit)"

为什么自动附加

延迟加载的工具不直接进入模型的工具列表,模型必须通过 ToolSearch 发现并通过 DeferExecuteTool 调用。少了任意一个,Defer(...) 就成了"模型再也用不到这个工具"。自动附加让最常见的写法符合直觉。

通配护栏:Defer(*) 不会卷走自身

Defer(*) 时,理论上"所有工具"都会被延迟加载——包括 ToolSearchDeferExecuteTool 自身,从而让 defer 工作流失效。为避免这种"自我封死",自动附加会同时给这两个工具加上 NoDefer 护栏:

bash
codebuddy --tools "Defer(*)"
# 行为等价于:
# codebuddy --tools "*,ToolSearch,DeferExecuteTool" + NoDefer(ToolSearch),NoDefer(DeferExecuteTool)

模型仍能看到并使用 ToolSearch / DeferExecuteTool,从而能搜索并调用其他被延迟加载的工具。

不会触发自动附加的情形

  • 工具列表里只有 NoDefer(...)、没有 Defer(...):用户意图是把工具拉回直接调用,与 defer 工作流无关
  • 工具列表里没有任何修饰符:保持原有行为
  • 用户已显式列出 ToolSearchDeferExecuteTool:幂等,不重复添加

6. 典型用例

6.1 临时把工具收进延迟加载,省 token

bash
# 知道这次只用 Read/Edit,把其他大型工具暂时收起来
codebuddy --tools "Read,Edit,Defer(Bash),Defer(Glob),Defer(Grep)"

模型上下文里只看到 ReadEditToolSearch,需要时再通过 ToolSearch 调出其他工具。

6.2 临时强制让被 defer 的工具直接可用

bash
# 假设 Bash 默认被配为延迟加载,但本次需要它直接可调用
codebuddy --tools "default,NoDefer(Bash)"

注:default 表示「全部内置工具」,与修饰符可同时使用。

6.3 自定义代理的精细化暴露策略

yaml
---
name: explorer
description: 大型仓库探索专用代理,把搜索类工具收起来减少干扰
tools:
  - Read
  - Edit
  - ToolSearch
  - Defer(Glob)
  - Defer(Grep)
  - Defer(LSP)
---

6.4 整组 MCP 工具按需暴露

bash
codebuddy --tools "default,Defer(mcp__github__*)"

把 GitHub MCP 服务器的所有工具收进延迟加载,需要时模型通过 ToolSearch 搜索 github prgithub issue 等关键词找到。

7. 错误排查

7.1 写错语法

text
Error: Invalid --tools value: Invalid tool spec "Defer(Read(*.md))": Defer(...) only accepts a tool name or glob; permission filters like Read(*.md) belong in --allowed-tools, not --tools.

→ 把参数过滤搬到 --allowed-tools

bash
codebuddy --tools "Defer(Read)" --allowed-tools "Read(*.md)"

7.2 修饰符写到了错误的字段

text
Error: Invalid permission rule "Defer(Glob)" in --allowed-tools / settings.permissions.allow: Defer(...) / NoDefer(...) modifiers belong in --tools, not in permission rule fields.

→ 见 §3。

7.3 嵌套 / 空内容

  • Defer(NoDefer(X)):嵌套修饰被拒,无意义语义
  • Defer():空内容被拒
  • defer(Read):小写不识别,按字面量「含括号的工具名」拒绝

7.4 在自定义代理里出现拼写错误

自定义代理 frontmatter 中的 tools 字段使用宽松校验:单条非法写法只会发出警告日志后被忽略,不会让 CodeBuddy Code 启动失败。从日志中查找形如下面的提示来定位:

text
[AgentToolSpec] my-agent: invalid-pattern — Invalid tool spec "Defer(Read(*.md))": ...

CLI 与 ACP 客户端直接输入的 --tools 使用严格校验,遇到非法写法会立刻报错。

8. 与「权限规则」的协作

修饰符与 --allowed-tools 完全正交,可以同时使用:

bash
codebuddy \
  --tools "Read,Write,Defer(Glob)" \
  --allowed-tools "Glob(src/**)"

含义:

  • Glob 走延迟加载(不直接进入模型的工具列表)
  • 当模型通过 ToolSearchDeferExecuteTool 调用 Glob 时,仍受 Glob(src/**) 权限约束
  • 调用 Glob(/etc/*) 会按权限规则被拒绝

9. 与 ToolSearch / DeferExecuteTool 的交互

修饰符直接影响这两个工具的行为:

修饰ToolSearch 的影响DeferExecuteTool 的影响
Defer(X)(X 原本不延迟加载)首次访问时自动加入可被搜索的延迟工具集合首次调用时自动注册并放行
NoDefer(X)(X 原本延迟加载)不会出现在搜索结果中(X 已直接进入模型的工具列表,无需搜索)调用 X 时返回错误,引导直接调用

报错示例(NoDefer 路径):

text
Error: Tool "Bash" is not deferred in this session (NoDefer modifier). Call it directly instead of via DeferExecuteTool.

10. 已知边界

  • MCP 服务器整体名:暂不支持在 --tools 里直接以服务器名作为修饰目标,只能写工具名或通配(如 Defer(mcp__github__*)
  • 通配反向枚举Defer(mcp__*) 等通配条目无法反向枚举具体工具名,MCP 工具的懒索引仍依赖 mcp-server-manager 自身的异步驱动流程