CodeDocs Vault

5. The Tool System

5.1 The Tool contract — Tool.ts:362

Every capability the model has is a Tool<Input, Output, Progress>. Minimally:

type Tool<Input extends AnyObject, Output> = {
  readonly name: string
  aliases?: string[]
  readonly inputSchema: Input                  // zod
  readonly inputJSONSchema?: ToolInputJSONSchema  // for MCP tools
 
  call(
    args: z.infer<Input>,
    context: ToolUseContext,
    canUseTool: CanUseToolFn,
    parentMessage: AssistantMessage,
    onProgress?: ToolCallProgress,
  ): Promise<ToolResult<Output>>
 
  description(input, { isNonInteractiveSession, toolPermissionContext, tools }): Promise<string>
  prompt(getCtx, tools, agents): Promise<string>
 
  isEnabled(): boolean
  isReadOnly(input): boolean
  isConcurrencySafe(input): boolean
  isDestructive?(input): boolean
  interruptBehavior?(): 'cancel' | 'block'
  isSearchOrReadCommand?(input): { isSearch; isRead; isList }
 
  shouldDefer?: boolean           // requires ToolSearch to load
  alwaysLoad?: boolean            // opt out of deferral
  searchHint?: string             // 3-10 words for ToolSearch keyword match
 
  maxResultSizeChars: number      // output > this → persisted to disk, preview returned
  strict?: boolean                // API-level strict schema enforcement
 
  checkPermissions?(input, context): Promise<PermissionResult>
  validateInput?(input, context): Promise<ValidationResult>
  getPath?(input): string         // for wildcard rules like "FileEdit(/src/*)"
  preparePermissionMatcher?(ctx): (pattern: string) => boolean
  backfillObservableInput?(input: Record<string, unknown>): void
 
  renderToolUseMessage?, renderToolResultMessage?, renderToolUseProgressMessage?,
  renderToolUseRejectedMessage?, renderToolUseErrorMessage?,
  renderGroupedToolUse?,
  ...
  mapToolResultToToolResultBlockParam?(…)
  extractSearchText?(input, output): string  // for transcript search index
  inputsEquivalent?(a, b): boolean           // dedupe identical consecutive calls
  mcpInfo?: { serverName; toolName }
}

buildTool() at Tool.ts:783 is the factory: it supplies safe defaults (isEnabled: () => true, isConcurrencySafe: () => false, isReadOnly: () => false, checkPermissions: allow-passthrough, etc.).

Two tiny helpers toolMatchesName(tool, name) (Tool.ts:348) and findToolByName(tools, name) (Tool.ts:358) respect aliases.

The ToolUseContext

Threaded through every call(). Includes: options (model, tools, mcpClients, agentDefinitions, thinkingConfig, theme), AppState getter/setter, abortController, readFileState, permission context, file-history / attribution updaters, discoveredSkillNames, loadedNestedMemoryPaths, dynamicSkillDirTriggers, nestedMemoryAttachmentTriggers, setInProgressToolUseIDs, setResponseLength, setSDKStatus. The bag is big because tool execution touches many subsystems.

5.2 Registry and gating — tools.ts

getAllBaseTools() (tools.ts:193) returns the superset:

AgentTool, TaskOutputTool,
BashTool, FileReadTool, FileEditTool, FileWriteTool,
NotebookEditTool, WebFetchTool, TodoWriteTool, WebSearchTool,
ExitPlanModeV2Tool, EnterPlanModeTool,
SkillTool, BriefTool,
GlobTool, GrepTool (omitted when embedded search tools present),
Task{Create,Get,Update,List}Tool  (when TODO_V2),
REPLTool, PowerShellTool (ant/platform gated),
SleepTool, CronTools, MonitorTool (PROACTIVE/KAIROS/AGENT_TRIGGERS),
ListMcpResourcesTool, ReadMcpResourceTool,
ToolSearchTool (when deferred tools are enabled)

getTools(permissionContext) (tools.ts:271) applies deny-rules via filterToolsByDenyRules() (tools.ts:262-269) which match tool names or MCP server prefixes against blanket-deny rules.

5.3 Tour of the notable tools

BashTool (tools/BashTool/)

The most complex tool. Its prompt (tools/BashTool/prompt.ts) is huge — see doc 07 for excerpts of the commit / PR / sandbox / background-task guidance it injects.

FileReadTool (tools/FileReadTool/)

FileEditTool (tools/FileEditTool/)

FileWriteTool (tools/FileWriteTool/)

Creates files. Refuses to write inside ~/.claude unless an explicit override. Same sandboxing and permission flows as Edit.

GlobTool / GrepTool (tools/GlobTool/, tools/GrepTool/)

WebFetchTool (tools/WebFetchTool/)

WebSearchTool (tools/WebSearchTool/)

Uses the API's native web_search_20250305 tool. allowed_domains / blocked_domains filtering. Max 8 searches per invocation.

AgentTool (tools/AgentTool/)

Described in detail in doc 06. Schema includes description (3-5 words), prompt, subagent_type (optional), model (sonnet/opus/haiku), run_in_background (feature-gated), and under multi-agent mode: name, team_name, mode, isolation ('worktree' | 'remote'), cwd.

SkillTool (tools/SkillTool/)

Invokes skills. Two execution contexts: 'inline' (expand prompt into current context) or 'fork' (run in a forked subagent with its own budget). Discovers bundled skills, plugin skills, user/project skills, and MCP prompts as one merged set.

Todo/Task tools

TodoWriteTool is the v1 surface (enabled when !isTodoV2Enabled()) — takes a whole list of todos. The newer v2 suite (TaskCreate, TaskGet, TaskUpdate, TaskList) allows granular CRUD with hooks. Deferred. TodoWriteTool does a structural nudge when 3+ tasks have been completed with no verification step (TodoWriteTool.ts:76-86).

MCPTool + ListMcpResourcesTool + ReadMcpResourceTool (tools/MCPTool/, tools/List…, tools/Read…)

Generic wrappers for MCP (Model Context Protocol) capabilities. The MCPTool has all of its real methods overridden per-instance in services/mcp/client.ts:29-51. Passthrough permission model — delegates to MCP server rules.

ToolSearchTool (tools/ToolSearchTool/)

Input schema:

query: "select:Read,Edit,Grep"  — exact match
query: "notebook jupyter"        — keyword search, rank by relevance
query: "+slack send"             — require 'slack' substring, rank by remaining terms
max_results: number (default 5)

Output: { matches: string[], total_deferred_tools: number, pending_mcp_servers?: string[] }. The matched tool schemas are returned inside a <functions> block matching the encoding of the system-prompt tool list. Memoized per tool-set via cache-invalidation on change.

EnterPlanMode / ExitPlanModeV2 (tools/EnterPlanModeTool/, tools/ExitPlanModeTool/)

Flip toolPermissionContext.mode = 'plan' and remember prePlanMode for restoration. Plan mode disables writes and bash side effects. Deferred, so the model sees them only after a ToolSearch call or when a plan-related context hints at them.

AskUserQuestionTool (tools/AskUserQuestionTool/)

Interactive tool: opens a UI prompt to the user. Used sparingly — the "Doing tasks" prompt explicitly tells the model to use it only when you're genuinely stuck after investigation, not as a first response to friction (constants/prompts.ts:233).

TodoWriteTool vs TaskCreateTool

Different generations, same purpose. v1 was a full-list replace; v2 is CRUD + hooks (TaskCreated, TaskCompleted).

SendMessageTool (tools/SendMessageTool/)

Inter-agent messaging for teammate mode: "continue a previously spawned agent." Referenced from AgentTool's prompt (tools/AgentTool/prompt.ts:267).

BriefTool (tools/BriefTool/)

Kairos / assistant-daemon specific: sends a brief (summary/status) out through a messaging channel.

REPLTool (tools/REPLTool/)

Interactive Python/JS REPL. Gated to ant / REPL-mode only. Hides the conventional filesystem tools — its own prompt tells the model to call them from scripts.

PowerShellTool (tools/PowerShellTool/)

Windows-platform variant of BashTool. Gated on Windows.

SleepTool, cron tools, MonitorTool (tools/SleepTool/, tools/ScheduleCron*, tools/RemoteTriggerTool)

Scheduling and long-watch tools for the proactive / Kairos / agent-triggers configuration. Let an agent wake later, or monitor a running process.

NotebookEditTool (tools/NotebookEditTool/)

Edits .ipynb cells with proper JSON preservation.

SyntheticOutputTool (tools/SyntheticOutputTool/)

Used only when jsonSchema is requested by an SDK caller — the model emits its structured output by calling this tool. QueryEngine.ts:328-333 registers a registerStructuredOutputEnforcement hook so retries are limited.

5.4 Permissions

Contexts and rules (Tool.ts:123-148)

ToolPermissionContext is DeepImmutable:

mode: 'default' | 'plan' | 'auto' | 'ask' | 'deny'
additionalWorkingDirectories: Map<string, AdditionalWorkingDirectory>
alwaysAllowRules | alwaysDenyRules | alwaysAskRules: ToolPermissionRulesBySource
isBypassPermissionsModeAvailable, isAutoModeAvailable
shouldAvoidPermissionPrompts, awaitAutomatedChecksBeforeDialog
prePlanMode

Rules come from 4 sources (utils/permissions/permissions.ts:109-114): settings (.claude/settings.json), cliArg (--allow-bash), command (session-scoped), session (API-set). Flattened via getAllowRules().

The canUseTool flow (hooks/useCanUseTool.tsx)

canUseTool(tool, input, context, message, toolUseID, forceDecision?):
  → hasPermissionsToUseTool(tool, input, context)
    → step 1a: blanket-deny (tool name or MCP server prefix)
    → step 1b: allow/ask/deny pattern match via preparePermissionMatcher
    → step 2: if mode='bypass' → allow
    → step 3: tool.checkPermissions()
    → step 4: general classification / denial-tracking / ask path
  On 'allow' → log + return
  On 'deny'  → log, record denial, notify
  On 'ask'   → coordinator pending check? swarm worker?
              for Bash with classifier: race the classifier result
              fallback: interactive permission dialog (UI) or remote permission bridge

Denial tracking (permissions.ts:95-101) counts repeated denials; past DENIAL_LIMITS, auto-mode escalates to prompts to avoid getting stuck fighting the user.

Bash classifier (tools/BashTool/bashPermissions.ts)

When feature('BASH_CLASSIFIER') is on, a cheap model classifies commands. High-confidence "safe" commands auto-allow in auto-mode; low-confidence or policy-mismatches fall back to prompting. The classifier result is raced against the UI prompt (useCanUseTool.tsx:127-141) — whichever arrives first wins.

5.5 Deferred tools and ToolSearch

Why defer?

With 40+ tools in production, the full tool-schema block balloons the system prompt to a point where keep-in-cache costs matter. Solution: send most tools with defer_loading: true and expose a single meta-tool ToolSearch that resolves schemas on demand.

What counts as deferred (tools/ToolSearchTool/prompt.ts:62-108)

The model-visible contract

From the ToolSearchTool description (tools/ToolSearchTool/ToolSearchTool.ts:21-44):

Deferred tools appear by name in <system-reminder> messages. 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.

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

5.6 Rendering and UI integration

A tool contributes several render hooks (all optional):

The UI uses isSearchOrReadCommand to decide whether to collapse a row by default; the user can expand.

5.7 Tool categories at a glance

Category Tools Deferred Key invariant
Core I/O Bash, Read, Edit, Write, Glob, Grep No Edit requires Read-before-write
Filesystem-adjacent NotebookEdit Yes JSON structural preservation
Web WebFetch, WebSearch Yes Preapproved host list; domain filtering
Tasks TodoWrite / TaskCreate/Get/Update/List Yes v1 vs v2 picked by isTodoV2Enabled
Agents Agent, Skill No Skill via SkillTool, Agent via AgentTool
MCP MCPTool, ListMcpResources, ReadMcpResource Yes (usually) Passthrough permissions
Planning EnterPlanMode, ExitPlanModeV2 Yes Sets toolPermissionContext.mode
Meta ToolSearch No Loads other deferred tools
Scheduling Sleep, Cron*, Monitor, RemoteTrigger Yes Feature-gated
Communication Brief, SendMessage, SendUserFile Never deferred Channel integrations
Interactive AskUserQuestion Yes Requires user interaction