Trilha 2 2.1 The Tool System
MODULO 2.1

🔧 The Tool System

A interface Tool, o pipeline de registro e filtragem, orquestracao de concorrencia, permissoes e o fluxo completo de execucao.

~50
Minutos
8
Topicos
6
Arquivos-fonte
Avancado
Nivel
1

Visao Geral e Arquitetura

Toda capacidade que o Claude Code expoe ao modelo -- leitura de arquivos, execucao de bash, busca web, chamadas MCP -- e uma Tool. O tool system e a ponte entre o raciocinio em linguagem natural da IA e os efeitos colaterais concretos na sua maquina.

CONCEITO CENTRAL

O ciclo de vida completo de uma invocacao de ferramenta segue 8 etapas: Interface -> Registration -> Routing -> Validation -> Permission -> Execution -> Result Processing -> Orchestration.

Arquivos cobertos: Tool.ts, tools.ts, tools/utils.ts, services/tools/toolOrchestration.ts, services/tools/toolExecution.ts, services/tools/StreamingToolExecutor.ts

Tool Lifecycle

flowchart TD A["Tool Interface\n(Tool.ts)\nname - inputSchema - call\ncheckPermissions - isConcurrencySafe"] -->|"buildTool() fills defaults"| B B["Registration\n(tools.ts)\ngetAllBaseTools - getTools\nassembleToolPool"] -->|"readonly Tool[]"| C C["Routing / Dispatch\n(toolExecution.ts)\nfindToolByName - alias fallback"] -->|"tool found"| D D["Input Validation\nZod safeParse\ntool.validateInput\nbackfillObservableInput"] -->|"parsedInput"| E E["Permission Gate\nPreToolUse hooks\ncanUseTool - allow/deny rules\nInteractive prompt"] -->|"allow"| F F["Execution\ntool.call - async generator\nToolResult + contextModifier\nPostToolUse hooks"] -->|"ToolResult"| G G["Result Processing\nmapToolResultToToolResultBlockParam\nprocessToolResultBlock\nyield MessageUpdateLazy"] -->|"MessageUpdate"| H H["Orchestration\npartitionToolCalls\nconcurrent vs serial\nsibling-abort on Bash error"] style A fill:#1e293b,stroke:#3b82f6,color:#e2e8f0 style B fill:#1e293b,stroke:#3b82f6,color:#e2e8f0 style C fill:#1e293b,stroke:#6366f1,color:#e2e8f0 style D fill:#1e293b,stroke:#6366f1,color:#e2e8f0 style E fill:#1e293b,stroke:#f59e0b,color:#e2e8f0 style F fill:#1e293b,stroke:#10b981,color:#e2e8f0 style G fill:#1e293b,stroke:#6366f1,color:#e2e8f0 style H fill:#1e293b,stroke:#ef4444,color:#e2e8f0
2

The Tool Interface (Tool.ts)

O tipo Tool<Input, Output, P> e um protocol contract -- um tipo estrutural TypeScript que toda ferramenta deve satisfazer. E generico sobre tres parametros: o schema Zod de input, o tipo de output e o shape do evento de progresso.

Core Required Members

export type Tool<
  Input extends AnyObject,
  Output,
  P extends ToolProgressData
> = {
  name: string                 // primary identifier the model uses
  aliases?: string[]          // legacy names for backward compat
  inputSchema: Input          // Zod schema -- source of truth for validation
  maxResultSizeChars: number  // overflow -> persist to disk

  call(
    args: z.infer<Input>,
    context: ToolUseContext,
    canUseTool: CanUseToolFn,
    parentMessage: AssistantMessage,
    onProgress?: ToolCallProgress<P>
  ): Promise<ToolResult<Output>>

  checkPermissions(
    input: z.infer<Input>,
    context: ToolUseContext
  ): Promise<PermissionResult>

  isConcurrencySafe(input: z.infer<Input>): boolean
  isReadOnly(input: z.infer<Input>): boolean
  isDestructive?(input: z.infer<Input>): boolean
}
INSIGHT

Protocol, not class hierarchy. Tool e um tipo estrutural TypeScript. Qualquer objeto que satisfaca a interface e uma tool -- built-in, MCP ou gerada dinamicamente.

ToolResult e contextModifier

export type ToolResult<T> = {
  data: T
  newMessages?: (UserMessage | AssistantMessage | ...)[]
  // Only honored for non-concurrency-safe tools
  contextModifier?: (context: ToolUseContext) => ToolUseContext
}

O contextModifier e como uma tool (ex: EnterPlanMode) muta estado compartilhado sem acessar variaveis globais. Tools concorrentes NAO podem usar contextModifier -- limitacao conhecida documentada no StreamingToolExecutor.

Metodos Opcionais: UI e Classifier

METODOS DE INTERFACE
  • renderToolUseMessage() -- React node shown while streaming tool input
  • renderToolResultMessage() -- React node for the result in transcript
  • toAutoClassifierInput() -- compact representation for security classifier; return '' to skip
  • interruptBehavior() -- 'cancel' or 'block': what happens when user types during tool execution
  • shouldDefer / alwaysLoad -- ToolSearch deferred loading flags
3

buildTool() -- Factory e Fail-Closed Defaults

Em vez de forcar autores a suprir cada metodo, buildTool() faz merge de um ToolDef (parcial) com defaults seguros. Os defaults sao fail-closed: assumem writes, assumem nao-safe para concorrencia.

const TOOL_DEFAULTS = {
  isEnabled:         () => true,
  isConcurrencySafe: () => false,   // conservative: assume state mutation
  isReadOnly:        () => false,
  isDestructive:     () => false,
  checkPermissions:  (input) =>
    Promise.resolve({ behavior: 'allow', updatedInput: input }),
  toAutoClassifierInput: () => '',
  userFacingName:    () => '',
}

export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {
  return {
    ...TOOL_DEFAULTS,
    userFacingName: () => def.name,
    ...def,
  } as BuiltTool<D>
}
POR QUE FAIL-CLOSED?

isConcurrencySafe defaulta para false para assumir mutacao de estado. Execucao paralela requer opt-in explicito. Uma tool custom roda serialmente por padrao ate que o autor confirme que e segura para concorrencia.

4

Three-Tier Registration Pipeline

tools.ts e a source of truth para quais tools existem. Implementa um pipeline de assembly de tres camadas.

getAllBaseTools() -- Catalogo Exaustivo

export function getAllBaseTools(): Tools {
  return [
    AgentTool, TaskOutputTool, BashTool,
    ...(hasEmbeddedSearchTools() ? [] : [GlobTool, GrepTool]),
    FileReadTool, FileEditTool, FileWriteTool,
    WebFetchTool, TodoWriteTool, WebSearchTool,
    // ... 30+ more tools, conditionally included
    ...(isToolSearchEnabledOptimistic() ? [ToolSearchTool] : []),
  ]
}

getTools() -- Filtrado por Contexto

FILTROS APLICADOS
  • Simple mode (CLAUDE_CODE_SIMPLE) -- apenas Bash, Read, Edit
  • REPL mode -- esconde tools primitivas; vivem dentro da REPL VM
  • Deny rules -- filtra tools matching alwaysDenyRules
  • isEnabled() -- cada tool pode vetar a si mesma

assembleToolPool() -- Built-ins + MCP

export function assembleToolPool(
  permissionContext: ToolPermissionContext,
  mcpTools: Tools,
): Tools {
  const builtInTools = getTools(permissionContext)
  const allowedMcpTools = filterToolsByDenyRules(mcpTools, permissionContext)

  // Built-ins sorted alphabetically, then MCP tools sorted alphabetically.
  // Keeps a stable cache breakpoint between the two groups.
  const byName = (a, b) => a.name.localeCompare(b.name)
  return uniqBy(
    [...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),
    'name',
  )
}
CACHE STABILITY

Sorting built-ins e MCP tools como dois grupos alfabeticos separados preserva o breakpoint de cache do servidor. Interleaving quebraria o cache sempre que uma MCP tool fosse adicionada ou renomeada.

5

Orchestration e Concurrency

Quando uma resposta do modelo contem multiplos blocos tool_use, o orchestrator decide quais rodam concorrentemente e quais serialmente usando partitionToolCalls().

Algoritmo de Particionamento

// Simplified from partitionToolCalls()
for (const toolUse of toolUseMessages) {
  const safe = isConcurrencySafe(toolUse)
  if (safe && lastBatch?.isConcurrencySafe) {
    lastBatch.blocks.push(toolUse)       // extend parallel group
  } else {
    acc.push({ isConcurrencySafe: safe, blocks: [toolUse] })
  }
}
CONCORRENCIA DATA-DRIVEN

isConcurrencySafe(input) e chamado per-tool-call, nao per-tool-type. Um Bash tool rodando ls pode ser safe enquanto um rodando rm -rf nao e. Ceiling de concorrencia: CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY (default 10).

Context Modifier Dance

// Serial: apply immediately so next tool sees updated context
if (update.contextModifier) {
  currentContext = update.contextModifier.modifyContext(currentContext)
}

// Concurrent: queue, apply after batch
queuedContextModifiers[toolUseID].push(modifyContext)
// ... after all concurrent tools complete:
for (const modifier of modifiers) {
  currentContext = modifier(currentContext)
}
6

StreamingToolExecutor

A variante real-time: inicia tools enquanto blocos ainda stream da API, antes da resposta completa do modelo. E a classe usada em producao no modo streaming.

Tool Lifecycle States

TOOLSTATUS
queued -- block received, waiting for concurrency slot
executing -- runToolUse() generator being consumed
completed -- results collected, not yet emitted to caller
yielded -- emitted in order, done

Concurrency Guard: canExecuteTool()

private canExecuteTool(isConcurrencySafe: boolean): boolean {
  const executing = this.tools.filter(t => t.status === 'executing')
  return (
    executing.length === 0 ||
    (isConcurrencySafe && executing.every(t => t.isConcurrencySafe))
  )
}

Sibling Abort: Bash Errors Cascade

APENAS BASH CASCATA

Somente erros de Bash abortam siblings via siblingAbortController. Read/WebFetch/etc sao tratados como independentes -- uma falha nao mata reads paralelos. Bash tem dependency chains implicitas (ex: mkdir falha -> comandos subsequentes sem sentido).

7

Tool Execution Pipeline (toolExecution.ts)

runToolUse() e a funcao central de dispatch. O fluxo completo de checkPermissionsAndCallTool() tem 9 etapas:

PIPELINE DE 9 ETAPAS
  1. Zod validation -- inputSchema.safeParse(input). Falha retorna InputValidationError imediatamente.
  2. Semantic validation -- tool.validateInput(). Checks custom per-tool (path traversal, file size limits).
  3. Speculative classifier -- Bash inicia o allow-classifier antes dos hooks rodarem.
  4. backfillObservableInput -- cria clone shallow. Clone e o que hooks e canUseTool veem.
  5. PreToolUse hooks -- async generators; podem atualizar input, setar permission result, parar execucao.
  6. canUseTool() -- permission gate principal. Checa always-allow/always-deny, auto-classifier, prompt interativo.
  7. tool.call() -- execucao real com progress callback.
  8. PostToolUse hooks -- rodam apos tool completar.
  9. Result serialization -- mapToolResultToToolResultBlockParam() + size-budget processing.

Defense-in-Depth: _simulatedSedEdit Stripping

if (tool.name === BASH_TOOL_NAME && '_simulatedSedEdit' in processedInput) {
  const { _simulatedSedEdit: _, ...rest } = processedInput
  processedInput = rest  // field stripped, execution proceeds safely
}

Medida de defense-in-depth mesmo que o Zod strictObject ja devesse rejeitar o campo.

Input Mutation Safety

TRES COPIAS DE INPUT

O codigo mantem tres copias distintas: (1) o original API-bound (para cache), (2) o clone observable backfilled (para hooks/canUseTool), (3) o input potencialmente atualizado por hooks para call. Cada fronteira e intencional -- mutar o original alteraria o transcript serializado e quebraria hashes de fixtures VCR em testes.

8

The Permission Context

O ToolPermissionContext (wrapped em DeepImmutable) flui por todo o sistema. Controla tanto filtragem no registro quanto permission checks em runtime.

export type ToolPermissionContext = DeepImmutable<{
  mode: PermissionMode             // 'default' | 'plan' | 'bypassPermissions'
  additionalWorkingDirectories: Map<string, AdditionalWorkingDirectory>
  alwaysAllowRules: ToolPermissionRulesBySource
  alwaysDenyRules:  ToolPermissionRulesBySource
  alwaysAskRules:   ToolPermissionRulesBySource
  isBypassPermissionsModeAvailable: boolean
  shouldAvoidPermissionPrompts?: boolean  // background agents: auto-deny
}>
IMUTABILIDADE FUNCIONAL

DeepImmutable previne qualquer code path de mutar permissoes acidentalmente in-place. A unica forma de mudar o contexto e via contextModifier retornado do ToolResult. Isso previne contaminacao cruzada de estado entre tools.

🎯 Resumo e Takeaways

1

Protocol, not class hierarchy. Tool e um tipo estrutural TypeScript. Qualquer objeto satisfazendo a interface e uma tool. buildTool() preenche defaults fail-closed seguros.

2

Three-tier assembly. getAllBaseTools() -> getTools() -> assembleToolPool(). Feature flags gate no load, deny rules filtram antes do modelo ver, isEnabled() e o veto final.

3

Concurrency is data-driven. isConcurrencySafe(input) e chamado per-call. O orchestrator particiona em batches concorrentes/seriais em runtime.

4

Immutable context, functional mutations. ToolPermissionContext e DeepImmutable. Mudancas de estado via contextModifier functions retornadas do ToolResult.

5

Input mutation carefully controlled. Tres copias: API-bound original, observable clone backfilled, hook-updated call input. Cada fronteira e intencional.

6

Bash is special. Somente erros de Bash cascateiam para siblings. Bash inicia classifier speculatively. Bash tem _simulatedSedEdit defense-stripped.

Trilha 2: Index 2.2 The Bash Tool