Uma análise aprofundada de como o Claude Code descobre, despacha e executa ferramentas
Introdução
Em um post anterior, fizemos engenharia reversa do sistema de memória do Anthropic Harness a partir de seu código-fonte vazado. A memória acabou sendo surpreendentemente simples — arquivos markdown, frontmatter e engenharia de prompt. O sistema de ferramentas é o oposto. É o subsistema mais projetado em toda a base de código: mais de 43 ferramentas, um pipeline de execução em streaming, um sistema de permissões em camadas, uma estrutura de hooks e um escalonador de concorrência — tudo conectado para transformar um modelo de linguagem sem estado em algo que pode ler arquivos, executar comandos shell, pesquisar na web e gerar subagentes.
Este post percorre o ciclo de vida de uma ferramenta, desde como ela é definida, como as chamadas de ferramenta do modelo são despachadas, até como os resultados fluem de volta para a conversa. De modo geral, o sistema é composto por quatro camadas: uma interface de ferramenta que toda ferramenta implementa, um registro que monta o pool de ferramentas, um pipeline de despacho que valida, verifica permissões e executa cada chamada, e um escalonador de concorrência que decide o que é executado em paralelo.
A Arquitetura de Relance

A Interface da Ferramenta
Toda ferramenta no Claude Code implementa a mesma interface, definida em Tool.ts. O tipo é genérico sobre três parâmetros: Input (um esquema Zod), Output (o tipo de resultado) e P (dados de progresso). Na prática, uma ferramenta é um objeto com cerca de 30 métodos, mas apenas alguns são importantes para entender o sistema.
A estrutura principal:
1type Tool<Input, Output, P> = {2 name: string3 inputSchema: ZodType // Esquema Zod para validação de entrada4 call(input, context, canUseTool,5 parentMessage, onProgress): Promise<ToolResult>67 // Declarações de comportamento8 isConcurrencySafe(input): boolean // Pode ser executada em paralelo?9 isReadOnly(input): boolean // Operação somente leitura?10 isDestructive(input): boolean // Ação destrutiva?1112 // Permissão e validação13 checkPermissions(input, context): Promise<PermissionResult>14 validateInput(input, context): Promise<ValidationResult>1516 // Integração com API17 description(input, options): Promise<string>18 prompt(options): Promise<string> // Texto do prompt do sistema para esta ferramenta19 mapToolResultToToolResultBlockParam(result, toolUseId): ToolResultBlockParam2021 // Renderização de UI (React)22 renderToolUseMessage(input, options): ReactNode23 renderToolResultMessage(content, ...): ReactNode24}
Nenhuma ferramenta implementa isso do zero. Uma função de fábrica chamada buildTool() preenche valores padrão seguros:

Os padrões são deliberadamente conservadores. Um autor de ferramenta que esquece de declarar segurança de concorrência obtém execução serial. Um autor que esquece de implementar verificações de permissão obtém o fluxo de permissão padrão. O sistema falha de forma segura.
O tipo ToolResult merece destaque:
1type ToolResult<T> = {2 data: T // A saída real3 newMessages?: Message[] // Mensagens de acompanhamento opcionais4 contextModifier?: (ctx) => ToolUseContext // Modifica o contexto para a próxima ferramenta5 mcpMeta?: { ... } // Metadados do protocolo MCP6}
O contextModifier é importante — ele permite que uma ferramenta altere o contexto de execução para ferramentas subsequentes na mesma rodada. É assim que ferramentas como EnterWorktree mudam o diretório de trabalho para tudo que vem em seguida. Criticamente, modificadores de contexto só são permitidos para ferramentas que não são seguras para concorrência. Se uma ferramenta é executada em paralelo, ela não pode modificar o estado compartilhado.
O Registro de Ferramentas
Todas as ferramentas são registradas em uma única função: getAllBaseTools() em tools.ts. Ela retorna um array simples. Algumas ferramentas estão sempre presentes; outras são controladas por feature flags, variáveis de ambiente ou verificações de plataforma.
Sempre Disponíveis (16 ferramentas)

Ferramentas Controladas por Feature Flags (~27 ferramentas)
As ferramentas restantes são incluídas condicionalmente. Algumas são controladas por variáveis de ambiente (USER_TYPE=ant para ferramentas internas da Anthropic como config e tungsten). Algumas são controladas por feature flags via Statsig (web_browser, sleep, monitor). Algumas são específicas de plataforma (powershell no Windows). Algumas são controladas por condições compostas — a ferramenta repl requer tanto USER_TYPE=ant quanto uma feature flag REPL.
Lista completa de ferramentas controladas por feature flags
Apenas Ant: config, tungsten, suggest_background_pr, repl (também precisa da flag REPL)
Feature flags: web_browser, web_search, sleep, monitor, overflow_test, ctx_inspect, terminal_capture, list_peers, workflow, snip
Gatilhos de agente: cron_create, cron_delete, cron_list, remote_trigger
Kairos (agente proativo): sleep, send_user_file, push_notification, subscribe_pr
Enxames multiagente: team_create, team_delete, send_message
Todo v2: task_create, task_get, task_update, task_list
Ambiente: lsp (ENABLE_LSP_TOOL), enter_worktree / exit_worktree (modo worktree), powershell (Windows)
Descoberta de ferramentas: tool_search (quando o pool de ferramentas é grande)
Apenas teste: testing_permission (NODE_ENV=test)
Ferramentas MCP
Além das ferramentas integradas, o Claude Code suporta servidores Model Context Protocol (MCP) — processos externos que expõem suas próprias ferramentas por meio de um protocolo padronizado. As ferramentas MCP são registradas dinamicamente em tempo de execução a partir de servidores conectados e encapsuladas na mesma interface Tool. Da perspectiva do pipeline de despacho, uma ferramenta MCP é indistinguível de uma ferramenta integrada.
Cada ferramenta MCP carrega metadados sobre seu servidor de origem (mcpInfo: { serverName, toolName }), que são usados para regras de permissão, tratamento de erros e autenticação. Quando uma ferramenta MCP falha com um erro de autenticação, o sistema atualiza automaticamente o status do servidor para needs-auth e exibe o problema para o usuário.
Montagem do Pool de Ferramentas
Três funções montam o conjunto final de ferramentas:
- getAllBaseTools() — retorna a lista bruta de mais de 43 ferramentas integradas com as feature flags aplicadas
- getTools(permissionContext) — filtra por regras de negação e isEnabled()
- assembleToolPool(permissionContext, mcpTools) — mescla ferramentas integradas e MCP
A estratégia de mesclagem em assembleToolPool() é deliberada:
As ferramentas integradas vêm primeiro, então, em caso de colisão de nome, a integrada vence. A ordenação alfabética dentro de cada partição mantém a ordem estável entre sessões, o que é importante para o cache de prompt — o array de ferramentas faz parte da requisição da API, e reordená-lo quebraria o cache.
A estratégia de mesclagem em assembleToolPool() é deliberada:
1// Ordena cada partição alfabeticamente, concatena, deduplica2const byName = (a, b) => a.name.localeCompare(b.name)3return uniqBy(4 [...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),5 'name',6)
Serialização da API
Antes que as ferramentas cheguem à API Claude, toolToAPISchema() converte o esquema Zod de cada ferramenta para o formato JSON Schema da API Anthropic.
O Pipeline de Despacho
Quando Claude responde, sua mensagem pode conter blocos tool_use — requisições estruturadas para invocar ferramentas. O pipeline de despacho processa esses blocos através de sete fases. Toda chamada de ferramenta passa por todas as fases, em ordem.
Fase 1: Extração
No loop principal de consulta (query.ts), os blocos tool_use são filtrados da mensagem do assistente:
1const msgToolUseBlocks = message.message.content.filter(2 content => content.type === 'tool_use',3) as ToolUseBlock[]
Cada bloco tem um name, um objeto input e um id único. O id é crítico — o resultado da ferramenta deve referenciar o mesmo id quando enviado de volta para a API, ou a conversa quebra.
Fase 2: Validação de Entrada
O esquema Zod da ferramenta valida a entrada bruta usando safeParse() — uma variante que não lança exceções e retorna dados válidos ou um erro estruturado. Se a validação falhar, o modelo recebe uma mensagem de erro formatada com dicas do esquema, e a execução para para aquela ferramenta. Nenhum código é executado em entrada inválida.
1const parsedInput = tool.inputSchema.safeParse(input)2if (!parsedInput.success) {3 let errorContent = formatZodValidationError(tool.name, parsedInput.error)4 // Retorna erro para o modelo, pula a execução5}
Após a validação Zod, algumas ferramentas executam uma segunda verificação validateInput() para validação semântica que não pode ser expressa em um esquema — por exemplo, verificar se um caminho de arquivo é absoluto, e não relativo.
Fase 3: Hooks Pré-Ferramenta
Antes das verificações de permissão, os hooks configurados pelo usuário são executados. São comandos shell externos ou scripts que são acionados nas invocações de ferramentas. Um hook pré-ferramenta pode:
- Permitir a chamada da ferramenta, ignorando o prompt de permissão interativo
- Negar a chamada da ferramenta imediatamente
- Modificar a entrada antes da execução
- Bloquear a execução com uma mensagem de erro
- Fornecer contexto adicional para o usuário
Uma invariante crítica: a permissão allow de um hook não ignora regras de negação das configurações. O código-fonte tem um comentário explícito sobre isso: "'Allow' do hook NÃO ignora regras de deny/ask do settings.json". A intenção é que hooks possam abrir portas, mas não substituir fechaduras.
Fase 4: Verificação de Permissão
O sistema de permissão é a parte mais intrincada do pipeline. Ele resolve através de múltiplas camadas, em ordem:
- Regras de negação — verificadas primeiro. Se alguma regra de negação corresponder, a execução para imediatamente. Regras de negação são finais e não podem ser substituídas por nenhuma outra camada.
- Regras de pergunta — se correspondidas, o usuário é solicitado a aprovar (a menos que a permissão automática de sandbox se aplique para Bash).
- Permissões específicas da ferramenta — o próprio método checkPermissions() da ferramenta é executado. BashTool, por exemplo, analisa o comando para verificar regras no nível de subcomandos.
- Verificações de segurança — proteções hardcoded para caminhos sensíveis (.git/, .claude/, configurações de shell). Elas são imunes a desvios — mesmo no modo de desvio total, exigem aprovação interativa.
- Modo de permissão — o modo configurado pelo usuário determina o comportamento padrão.
- Regras de permissão — verificadas por último. Se uma regra de permissão corresponder e nenhuma regra de negação/pergunta for acionada, a ferramenta prossegue.
Modos de permissão
default — Sempre perguntar ao usuário para decisões "ask".
acceptEdits — Permitir automaticamente operações seguras de arquivo (leituras, edições), perguntar para todo o resto.
bypassPermissions — Permitir automaticamente tudo, exceto regras de negação e verificações de segurança.
plan — Aprovar um plano primeiro, depois seguir o modo anterior para execução.
auto — Usar um classificador de IA para decidir se deve permitir ou perguntar.
dontAsk — Converter todas as decisões "ask" em "deny" — nunca perguntar, apenas recusar.
As regras de permissão vêm de múltiplas fontes, resolvidas em ordem de prioridade: policySettings, localSettings, projectSettings, userSettings, flagSettings, cliArg, command, session. Isso permite que políticas organizacionais substituam preferências do usuário, e argumentos de CLI substituam ambos.
Fase 5: Execução
Se a permissão for concedida, o método call() da ferramenta é invocado:
1const result = await tool.call(2 callInput,3 { ...toolUseContext, toolUseId: toolUseID },4 canUseTool,5 assistantMessage,6 progress => onToolProgress({ toolUseID: progress.toolUseID, data: progress.data })7)
Cinco argumentos: a entrada validada, um contexto de execução (diretório de trabalho, controlador de aborto, estado do aplicativo), um callback de permissão (para ferramentas que precisam solicitar permissões adicionais durante a execução), a mensagem pai do assistente e um callback de progresso para atualizações em tempo real. A duração é rastreada globalmente.
Um detalhe sutil: a entrada passada para call() é a entrada original do modelo, não a versão preenchida que hooks e permissões viram. Isso preserva a consistência da transcrição — a chamada de ferramenta registrada na conversa corresponde exatamente ao que o modelo gerou.
Fase 6: Hooks Pós-Ferramenta
Após a execução, os hooks pós-ferramenta são acionados. Eles podem modificar a saída de ferramentas MCP, fornecer contexto adicional ou bloquear a continuação da conversa. Há também um hook separado PostToolUseFailure que é acionado apenas em erros, dando a sistemas externos a chance de registrar falhas ou sugerir remediação.
Fase 7: Mapeamento do Resultado
Cada ferramenta implementa mapToolResultToToolResultBlockParam() para converter sua saída no formato ToolResultBlockParam da API Anthropic — um bloco tool_result com uma referência tool_use_id e conteúdo string ou estruturado.
Se o resultado exceder um limite de tamanho, ele é persistido em disco em sessionDir/tool-results/{toolUseId}.txt e uma prévia com uma referência de arquivo é enviada para a API. Isso evita que saídas grandes (uma leitura de arquivo de 10.000 linhas, uma saída de comando verbosa) inchem o contexto da conversa.
O Escalonador de Concorrência
Quando o modelo emite múltiplas chamadas de ferramenta em uma única mensagem, elas não são executadas todas de uma vez. Um escalonador as particiona em lotes com base na segurança de concorrência.
O algoritmo é simples. Percorra as chamadas de ferramenta em ordem. Para cada uma, verifique isConcurrencySafe(input). Se for segura e o lote anterior também foi seguro, adicione-a ao lote. Caso contrário, inicie um novo lote.
1// Simplificado de toolOrchestration.ts2for (const toolUse of toolUseMessages) {3 const isSafe = tool.isConcurrencySafe(parsedInput)4 if (isSafe && lastBatch.isConcurrencySafe) {5 lastBatch.blocks.push(toolUse) // Mescla no lote concorrente6 } else {7 batches.push({ isConcurrencySafe: isSafe, blocks: [toolUse] })8 }9}
Lotes seguros são executados concorrentemente (até um limite de 10, configurável via CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY). Lotes inseguros são executados serialmente, uma ferramenta de cada vez. Modificadores de contexto são aplicados apenas entre lotes, não dentro deles.
Na prática, isso significa que uma mensagem como "leia estes 5 arquivos" produz um lote concorrente, enquanto "leia este arquivo, depois edite-o" produz dois lotes seriais. O modelo pode até acionar ambos os padrões em uma única rodada — chamadas consecutivas somente leitura são agrupadas, e a primeira escrita quebra o lote.
O Executor de Streaming
Há um segundo caminho de execução: o StreamingToolExecutor. Quando o streaming está habilitado, as ferramentas começam a executar enquanto o modelo ainda está gerando sua resposta. À medida que cada bloco tool_use é concluído no stream, ele é imediatamente enfileirado para execução em vez de esperar pela resposta completa.
O executor de streaming usa as mesmas regras de concorrência, mas adiciona um comportamento: Cascata de erro Bash. Se um comando Bash falhar enquanto ferramentas irmãs estão sendo executadas em paralelo, o executor aborta todas as irmãs. A lógica é que um comando Bash com falha provavelmente invalida o contexto no qual outras ferramentas estão operando — continuá-las perde tempo e pode causar erros confusos.
1if (isErrorResult && tool.block.name === BASH_TOOL_NAME) {2 this.hasErrored = true3 this.siblingAbortController.abort('sibling_error')4}
Um Exemplo Prático
Para tornar isso concreto, vamos rastrear o que acontece quando o modelo decide ler um arquivo. O modelo emite:
1{2 "type": "tool_use",3 "id": "toolu_01XYZ",4 "name": "read",5 "input": { "file_path": "/src/index.ts" }6}
- Extração: query.ts filtra isso do conteúdo da mensagem do assistente.
- Busca de ferramenta: findToolByName(tools, "read") encontra FileReadTool.
- Validação de entrada: Zod analisa { file_path: "/src/index.ts" } contra z.object({ file_path: z.string(), offset: z.number().optional(), limit: z.number().optional(), pages: z.string().optional() }). A validação passa.
- Hooks pré-ferramenta: Quaisquer hooks configurados pelo usuário são acionados. Nenhum modifica a entrada.
- Verificação de permissão: checkPermissions() do FileReadTool chama checkReadPermissionForTool(). Ferramentas de leitura geralmente são permitidas na maioria dos modos de permissão.
- Execução: FileReadTool.call() lê o arquivo, aplica numeração de linhas (formato cat -n), lida com PDFs/imagens/notebooks como casos especiais.
- Mapeamento do resultado: O conteúdo do arquivo se torna um bloco tool_result referenciando "toolu_01XYZ".
- Retorno: O resultado é anexado à conversa como uma mensagem do usuário e enviado na próxima chamada de API.
Como FileReadTool declara isConcurrencySafe: () => true e isReadOnly: () => true, se o modelo tivesse emitido cinco chamadas de leitura na mesma mensagem, todas as cinco seriam executadas em paralelo.
Resumo
O sistema de ferramentas é a espinha dorsal da execução do Claude Code. Ele pega a intenção do modelo — expressa como blocos tool_use estruturados — e a transforma em ações reais em sua máquina, com validação, permissões e controle de concorrência em cada etapa.
O design é em camadas: uma fábrica buildTool() conservadora garante padrões seguros, um registro controlado por feature flags determina o que está disponível, um pipeline de despacho de sete fases valida e verifica permissões de cada chamada, e um escalonador de concorrência maximiza o paralelismo enquanto preserva a correção. O executor de streaming adiciona uma otimização de desempenho — as ferramentas começam a ser executadas antes que o modelo termine de pensar.
Comparado ao sistema de memória (5 caminhos, um diretório de arquivos markdown e engenharia de prompt), o sistema de ferramentas é um runtime de verdade. É a diferença entre um arquivo e um sistema operacional.
O Que é Interessante
O Modelo como Escalonador
O escalonador de concorrência é reativo — ele agrupa o que o modelo emitir. Mas o modelo em si é o verdadeiro escalonador. O prompt do sistema diz para "fazer todas as chamadas de ferramenta independentes em paralelo" e para "usar uma única chamada Bash com && para encadear comandos dependentes." O runtime confia nisso. Se o modelo emitir cinco leituras seguidas por uma escrita, o escalonador paralelizará as leituras e serializará a escrita. Mas o modelo decidiu essa ordem. O escalonador está impondo o plano do modelo, não criando o seu próprio.
Falha Segura por Padrão
O princípio de design mais consistente: tudo falha de forma segura. Ferramenta desconhecida? Erro. Entrada inválida? Erro. Nenhuma declaração de concorrência? Execução serial. Nenhuma declaração de permissão? Pergunte ao usuário. Nenhuma feature flag? Ferramenta não existe. Isso é incomum para um sistema onde o usuário principal é um modelo de IA que pode alucinar nomes de ferramentas ou malformar entradas. O sistema é projetado para conter os erros do modelo, não para acomodá-los.
Hooks como Ponto de Extensão
O sistema de hooks — pré-ferramenta, pós-ferramenta e pós-falha — é o principal ponto de extensão. É como as organizações aplicam políticas (regras de negação em pré-hooks), como sistemas de log capturam o uso de ferramentas (pós-hooks) e como pipelines de CI/CD se integram (hooks de falha). Importante, os hooks só podem apertar as restrições, não afrouxá-las. Um hook pode negar uma ferramenta que as configurações permitem, mas não pode permitir uma ferramenta que as configurações negam.
43 Ferramentas, 1 Interface
Talvez o mais impressionante seja a uniformidade. Um comando bash, um web_fetch, um spawn de subagente, uma criação de cron job e uma notificação push implementam todos a mesma interface de 30 métodos, passam pelo mesmo pipeline de sete fases e respeitam o mesmo sistema de permissões. Não há casos especiais no dispatcher. A complexidade está nas implementações individuais das ferramentas e nas regras de permissão, não no roteamento.





