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.
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
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
}
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
renderToolUseMessage()-- React node shown while streaming tool inputrenderToolResultMessage()-- React node for the result in transcripttoAutoClassifierInput()-- compact representation for security classifier; return '' to skipinterruptBehavior()-- 'cancel' or 'block': what happens when user types during tool executionshouldDefer/alwaysLoad-- ToolSearch deferred loading flags
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>
}
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.
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
- 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',
)
}
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.
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] })
}
}
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)
}
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
queued -- block received, waiting for concurrency slotexecuting -- runToolUse() generator being consumedcompleted -- results collected, not yet emitted to calleryielded -- emitted in order, doneConcurrency 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
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).
Tool Execution Pipeline (toolExecution.ts)
runToolUse() e a funcao central de dispatch. O fluxo completo de checkPermissionsAndCallTool() tem 9 etapas:
- Zod validation -- inputSchema.safeParse(input). Falha retorna InputValidationError imediatamente.
- Semantic validation -- tool.validateInput(). Checks custom per-tool (path traversal, file size limits).
- Speculative classifier -- Bash inicia o allow-classifier antes dos hooks rodarem.
- backfillObservableInput -- cria clone shallow. Clone e o que hooks e canUseTool veem.
- PreToolUse hooks -- async generators; podem atualizar input, setar permission result, parar execucao.
- canUseTool() -- permission gate principal. Checa always-allow/always-deny, auto-classifier, prompt interativo.
- tool.call() -- execucao real com progress callback.
- PostToolUse hooks -- rodam apos tool completar.
- 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
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.
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
}>
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
Protocol, not class hierarchy. Tool e um tipo estrutural TypeScript. Qualquer objeto satisfazendo a interface e uma tool. buildTool() preenche defaults fail-closed seguros.
Three-tier assembly. getAllBaseTools() -> getTools() -> assembleToolPool(). Feature flags gate no load, deny rules filtram antes do modelo ver, isEnabled() e o veto final.
Concurrency is data-driven. isConcurrencySafe(input) e chamado per-call. O orchestrator particiona em batches concorrentes/seriais em runtime.
Immutable context, functional mutations. ToolPermissionContext e DeepImmutable. Mudancas de estado via contextModifier functions retornadas do ToolResult.
Input mutation carefully controlled. Tres copias: API-bound original, observable clone backfilled, hook-updated call input. Cada fronteira e intencional.
Bash is special. Somente erros de Bash cascateiam para siblings. Bash inicia classifier speculatively. Bash tem _simulatedSedEdit defense-stripped.