Skip to content

Tool Deferred Loading Override (Defer / NoDefer) ​

CodeBuddy Code supports using Defer(...) / NoDefer(...) modifiers to temporarily change a tool's deferred loading status when specifying the available tool list, without modifying the global configuration. This document is the complete reference for this capability.

Background: By default, whether a tool uses deferred loading is determined by the product defaults or the MCP configuration's defer_loading field (see MCP docs Β§Deferred Loading). These are globally applied default settings. This feature allows you to temporarily adjust the behavior for a single session or custom agent without changing the defaults.

1. Syntax Overview ​

In any "tool list" field that accepts tool names, add a modifier before the tool name:

SyntaxMeaning
ReadRegular tool, deferred status determined by global defaults
Defer(Glob)Force Glob to use deferred loading in this session/agent (not directly in the model's tool list; discoverable via ToolSearch)
NoDefer(Bash)Force Bash to not use deferred loading in this session/agent (directly in the model's tool list)
Defer(mcp__github__*)Wildcard match, set an entire group of MCP tools to deferred loading
Defer(*)Set all tools in the current list to deferred loading (extreme trimming)

* is the only supported wildcard character, matching any character sequence. Other characters (?, [], etc.) are treated as literals.

2. Available Channels ​

Modifiers can be used in the following three types of "tool list" fields:

2.1 CLI --tools Parameter ​

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

The tools field in ACP and SDK clients is equivalent to the CLI.

2.2 Custom Agent Frontmatter ​

In the YAML frontmatter of .codebuddy/agents/*.md:

yaml
---
name: code-reader
description: Read-only code exploration agent
tools:
  - Read
  - Grep
  - Defer(Glob)        # Make Glob use deferred loading; discover via ToolSearch when needed
  - NoDefer(Bash)      # Even if Bash defaults to deferred loading, force it to be non-deferred in this agent
---

See Sub-Agents Β§Configuration Fields for details.

3. Where Modifiers Cannot Be Used ​

Modifiers only apply to "tool list" fields. The following "permission rule" fields do not accept modifiers:

  • --allowed-tools / settings.permissions.allow
  • --disallowed-tools / settings.permissions.deny
  • matcher in hooks configuration
bash
# ❌ Wrong: Will immediately throw an error
codebuddy --allowed-tools "Defer(Glob)"
# Error: Defer(...) / NoDefer(...) modifiers belong in --tools, not in permission rule fields.

# βœ… Correct: Separate them
codebuddy --tools "...,Defer(Glob)" --allowed-tools "Glob(src/**)"

Rationale: "Exposure strategy" (whether to use deferred loading) and "behavior constraints" (parameter filtering) are two orthogonal concerns β€” expressing them separately is clearer.

4. Priority and Merging ​

The final decision follows this priority order (highest to lowest):

PrioritySourceDescription
0aAny layer NoDefer(X) matchesForce non-defer (regardless of what other sources say)
0bAny layer Defer(X) matchesForce defer
1MCP tool-level tools[name].defer_loadingMCP static configuration
2MCP server-level defer_loadingMCP static configuration
3Environment variable CODEBUDDY_DEFER_TOOL_LOADINGGlobal switch
4User setting settings.deferToolLoadingGlobal switch
5Built-in default (CodeBuddy Code factory configuration)Fallback

Key Rules ​

  • NoDefer always wins over Defer: Even if a custom agent specifies Defer(X), adding NoDefer(X) in the CLI --tools means X will not be deferred in the current session β€” the user's runtime declaration takes priority.
  • CLI and agent configuration participate equally: Modifiers from both sources take effect as a union, with no precedence between them; the final result is determined by the "NoDefer wins" rule.
  • Duplicate names: When the same tool appears multiple times in the same field, the last occurrence's modifier wins (consistent with the semantics of --allowedTools duplicates).

5. Auto-Append: ToolSearch and DeferExecuteTool ​

Whenever the tool list contains at least one Defer(...), CodeBuddy Code automatically adds the following two tools (if they're not already in the list):

ToolPurpose
ToolSearchLets the model search for and discover deferred tools
DeferExecuteToolLets the model actually call deferred tools

This means you can write:

bash
# Equivalent to codebuddy --tools "Bash,Read,Defer(Edit),ToolSearch,DeferExecuteTool"
codebuddy --tools "Bash,Read,Defer(Edit)"

Why Auto-Append ​

Deferred tools don't enter the model's tool list directly β€” the model must discover them via ToolSearch and call them via DeferExecuteTool. Without either one, Defer(...) effectively becomes "the model can never use this tool." Auto-append makes the most common usage pattern intuitive.

Wildcard Guard: Defer(*) Won't Swallow Itself ​

When writing Defer(*), theoretically "all tools" would be deferred β€” including ToolSearch and DeferExecuteTool themselves, which would break the defer workflow. To prevent this "self-lockout," auto-append also adds a NoDefer guard for these two tools:

bash
codebuddy --tools "Defer(*)"
# Behavior equivalent to:
# codebuddy --tools "*,ToolSearch,DeferExecuteTool" + NoDefer(ToolSearch),NoDefer(DeferExecuteTool)

The model can still see and use ToolSearch / DeferExecuteTool, enabling it to search for and call other deferred tools.

Cases That Don't Trigger Auto-Append ​

  • The tool list only has NoDefer(...) without any Defer(...): The user's intent is to pull tools back to direct invocation, unrelated to the defer workflow
  • The tool list has no modifiers at all: Original behavior is preserved
  • The user has explicitly listed ToolSearch or DeferExecuteTool: Idempotent, no duplicate addition

6. Typical Use Cases ​

6.1 Temporarily Defer Tools to Save Tokens ​

bash
# Know you only need Read/Edit this time; temporarily tuck away other large tools
codebuddy --tools "Read,Edit,Defer(Bash),Defer(Glob),Defer(Grep)"

The model's context only shows Read, Edit, and ToolSearch; other tools can be retrieved via ToolSearch when needed.

6.2 Temporarily Force a Deferred Tool to Be Directly Available ​

bash
# Suppose Bash is configured as deferred by default, but you need it directly available this time
codebuddy --tools "default,NoDefer(Bash)"

Note: default means "all built-in tools" and can be used alongside modifiers.

6.3 Fine-Grained Exposure Strategy for Custom Agents ​

yaml
---
name: explorer
description: Agent specialized for large repo exploration; tuck search tools away to reduce noise
tools:
  - Read
  - Edit
  - ToolSearch
  - Defer(Glob)
  - Defer(Grep)
  - Defer(LSP)
---

6.4 Expose MCP Tool Groups On-Demand ​

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

Defers all tools from the GitHub MCP server; the model can find them via ToolSearch by searching keywords like github pr, github issue, etc.

7. Troubleshooting ​

7.1 Syntax Error ​

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.

β†’ Move the parameter filter to --allowed-tools:

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

7.2 Modifier in the Wrong Field ​

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

β†’ See Β§3.

7.3 Nesting / Empty Content ​

  • Defer(NoDefer(X)): Nested modifiers are rejected β€” semantically meaningless
  • Defer(): Empty content is rejected
  • defer(Read): Lowercase is not recognized β€” treated as a literal "tool name with parentheses" and rejected

7.4 Typos in Custom Agent ​

The tools field in custom agent frontmatter uses lenient validation: a single invalid entry only emits a warning log and is ignored, without causing CodeBuddy Code to fail on startup. Look for log messages like the following to locate the issue:

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

CLI and ACP client --tools input uses strict validation and will immediately throw an error on invalid entries.

8. Interaction with Permission Rules ​

Modifiers and --allowed-tools are completely orthogonal and can be used together:

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

Meaning:

  • Glob uses deferred loading (not directly in the model's tool list)
  • When the model calls Glob via ToolSearch β†’ DeferExecuteTool, it's still subject to the Glob(src/**) permission constraint
  • Calling Glob(/etc/*) will be denied by permission rules

9. Interaction with ToolSearch / DeferExecuteTool ​

Modifiers directly affect the behavior of these two tools:

ModifierEffect on ToolSearchEffect on DeferExecuteTool
Defer(X) (X was originally not deferred)Automatically added to the searchable deferred tool set on first accessAutomatically registered and allowed on first call
NoDefer(X) (X was originally deferred)Does not appear in search results (X is already directly in the model's tool list β€” no need to search)Calling X returns an error, directing to call it directly

Error example (NoDefer path):

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

10. Known Limitations ​

  • MCP server name as target: Currently not supported to use a server name directly as a modifier target in --tools β€” only tool names or wildcards are accepted (e.g., Defer(mcp__github__*))
  • Wildcard reverse enumeration: Wildcard entries like Defer(mcp__*) cannot reverse-enumerate specific tool names; lazy indexing of MCP tools still relies on the mcp-server-manager's own asynchronous driving process