# 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](mcp.md#deferred-loading-defer_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:

| Syntax | Meaning |
|---|---|
| `Read` | Regular 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
```

See [Sub-Agents §Configuration Fields](sub-agents.md#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):

| Priority | Source | Description |
|:-:|---|---|
| 0a | Any layer `NoDefer(X)` matches | **Force non-defer** (regardless of what other sources say) |
| 0b | Any layer `Defer(X)` matches | Force defer |
| 1 | MCP tool-level `tools[name].defer_loading` | MCP static configuration |
| 2 | MCP server-level `defer_loading` | MCP static configuration |
| 3 | Environment variable `CODEBUDDY_DEFER_TOOL_LOADING` | Global switch |
| 4 | User setting `settings.deferToolLoading` | Global switch |
| 5 | Built-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):

| Tool | Purpose |
|---|---|
| `ToolSearch` | Lets the model search for and discover deferred tools |
| `DeferExecuteTool` | Lets 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:

| Modifier | Effect on `ToolSearch` | Effect on `DeferExecuteTool` |
|---|---|---|
| `Defer(X)` (X was originally not deferred) | Automatically added to the searchable deferred tool set on first access | Automatically 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
