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 engenheirado de 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 agendador de concorrência — tudo interligado 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, até como as chamadas de ferramenta do modelo são despachadas, e como os resultados fluem de volta para a conversa. De forma 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 agendador de concorrência que decide o que é executado em paralelo.
A Arquitetura em Resumo

A Interface de Ferramenta
Toda ferramenta no Claude Code implementa a mesma interface, definida em Tool.ts. O tipo é genérico em três parâmetros: Input (um schema 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 forma central:
1type Tool<Input, Output, P> = {2 name: string3 inputSchema: ZodType // Schema Zod para validação de entrada4 call(input, context, canUseTool,5 parentMessage, onProgress): Promise<ToolResult>67 // Declarações de comportamento8 isConcurrencySafe(input): boolean // Pode executar 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 de ferramenta 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 atenção:
1type ToolResult<T> = {2 data: T // A saída real3 newMessages?: Message[] // Mensagens de acompanhamento opcionais4 contextModifier?: (ctx) => ToolUseContext // Altera 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 no mesmo turno. É assim que ferramentas como EnterWorktree alteram o diretório de trabalho para tudo que vem depois. 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 estão atrás de flags de funcionalidade, variáveis de ambiente ou verificações de plataforma.
Sempre Disponíveis (16 ferramentas)

Ferramentas com Gate de Funcionalidade (~27 ferramentas)
As ferramentas restantes são incluídas condicionalmente. Algumas estão atrás de variáveis de ambiente (USER_TYPE=ant para ferramentas internas da Anthropic como config e tungsten). Algumas estão atrás de flags de funcionalidade via Statsig (web_browser, sleep, monitor). Algumas são específicas de plataforma (powershell no Windows). Algumas estão atrás de condições compostas — a ferramenta repl requer tanto USER_TYPE=ant quanto uma flag de funcionalidade REPL.
Lista completa de ferramentas com gate de funcionalidade
Apenas Ant: config, tungsten, suggest_background_pr, repl (também precisa da flag REPL)
Flags de funcionalidade: 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 envolvidas 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 gates de funcionalidade aplicados
- 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 nomes, a integrada vence. A ordenação alfabética dentro de cada partição mantém a ordem estável entre as 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 schema Zod de cada ferramenta para o formato JSON Schema da API Anthropic.
O Pipeline de Despacho
Quando o Claude responde, sua mensagem pode conter blocos tool_use — requisições estruturadas para invocar ferramentas. O pipeline de despacho processa esses blocos por meio de sete fases. Toda chamada de ferramenta passa por todas as fases, em ordem.
Fase 1: Extração
No loop de consulta principal (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 schema 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 schema, e a execução para para aquela ferramenta. Nenhum código é executado com 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 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 schema — por exemplo, verificar se um caminho de arquivo é absoluto, 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 ou scripts externos que disparam em invocações de ferramenta. Um hook pré-ferramenta pode:
- Permitir a chamada de ferramenta, ignorando o prompt de permissão interativo
- Negar a chamada de ferramenta diretamente
- Modificar a entrada antes da execução
- Bloquear a execução com uma mensagem de erro
- Fornecer contexto adicional ao usuário
Uma invariante crítica: a permissão 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: "A 'permissão' do hook NÃO ignora as regras de negar/perguntar do settings.json." A intenção é que hooks podem 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 por meio de várias 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 método checkPermissions() da própria ferramenta é executado. BashTool, por exemplo, analisa o comando para verificar regras em nível de subcomando.
- Verificações de segurança — proteções hardcoded para caminhos sensíveis (.git/, .claude/, configurações shell). Elas são imunes a bypass — mesmo no modo de bypass 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 foi acionada, a ferramenta prossegue.
Modos de permissão
default — Sempre pergunta ao usuário para decisões "perguntar".
acceptEdits — Permite automaticamente operações seguras de arquivo (leituras, edições), pergunta para todo o resto.
bypassPermissions — Permite automaticamente tudo, exceto regras de negação e verificações de segurança.
plan — Aprova um plano primeiro, depois segue o modo anterior para execução.
auto — Usa um classificador de IA para decidir se permite ou pergunta.
dontAsk — Converte todas as decisões "perguntar" para "negar" — nunca pergunta, apenas recusa.
As regras de permissão vêm de várias 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 abortamento, 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 disparam. Eles podem modificar a saída da ferramenta MCP, fornecer contexto adicional ou bloquear a continuação da conversa. Há também um hook PostToolUseFailure separado que dispara apenas em erros, dando a sistemas externos a chance de registrar falhas ou sugerir correções.
Fase 7: Mapeamento de 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 (leitura de um arquivo de 10.000 linhas, saída de comando verbosa) inflacionem o contexto da conversa.
O Agendador de Concorrência
Quando o modelo emite várias chamadas de ferramenta em uma única mensagem, elas não são executadas todas de uma vez. Um agendador 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 simultaneamente (até o limite de 10, configurável via CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY). Lotes inseguros são executados em série, 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 esses 5 arquivos" produz um lote concorrente, enquanto "leia este arquivo e depois edite-o" produz dois lotes em série. O modelo pode até acionar ambos os padrões em um único turno — chamadas consecutivas somente leitura são agrupadas, e a primeira gravação 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 aguardar a resposta completa.
O executor de streaming usa as mesmas regras de concorrência, mas adiciona um comportamento: cascata de erros 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 falho provavelmente invalida o contexto no qual outras ferramentas estão operando — continuá-las desperdiça 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() }). Passa.
- Hooks pré-ferramenta: Quaisquer hooks configurados pelo usuário disparam. Nenhum modifica a entrada.
- Verificação de permissão: checkPermissions() de FileReadTool chama checkReadPermissionForTool(). Ferramentas de leitura são geralmente 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 de 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 da 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 de 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 conservadora buildTool() garante padrões seguros, um registro com gate de funcionalidade controla o que está disponível, um pipeline de despacho de sete fases valida e verifica permissões de cada chamada, e um agendador de concorrência maximiza o paralelismo enquanto preserva a correção. O executor de streaming adiciona uma otimização de desempenho por cima — as ferramentas começam a executar 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 Agendador
O agendador de concorrência é reativo — ele agrupa o que o modelo emite. Mas o próprio modelo é o verdadeiro agendador. O prompt do sistema diz para ele "fazer todas as chamadas de ferramenta independentes em paralelo" e "usar uma única chamada Bash com && para encadear comandos dependentes." O runtime confia nisso. Se o modelo emitir cinco leituras seguidas por uma gravação, o agendador paralelizará as leituras e serializará a gravação. Mas o modelo decidiu essa ordem. O agendador está aplicando 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 flag de funcionalidade? 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 hooks pré), como sistemas de registro capturam o uso de ferramentas (hooks pós) e como pipelines de CI/CD se integram (hooks de falha). Importante, os hooks só podem apertar 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 tarefa cron 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 despachante. A complexidade está nas implementações individuais das ferramentas e nas regras de permissão, não no roteamento.





