A arquitetura de ferramentas do Claude Code

@spandan_madan
INGLÊShá 4 semanas · 17/06/2026
552K
416
115
86
479

TL;DR

Esta análise técnica detalha a arquitetura de ferramentas do Claude Code, explorando seu pipeline de despacho de sete fases, o agendador de concorrência e o sistema de permissões 'fail-closed' que possibilita ações complexas de agentes de IA.

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

Spandan Madan - inline image

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:

text
1type Tool<Input, Output, P> = {
2 name: string
3 inputSchema: ZodType // Esquema Zod para validação de entrada
4 call(input, context, canUseTool,
5 parentMessage, onProgress): Promise<ToolResult>
6
7 // Declarações de comportamento
8 isConcurrencySafe(input): boolean // Pode ser executada em paralelo?
9 isReadOnly(input): boolean // Operação somente leitura?
10 isDestructive(input): boolean // Ação destrutiva?
11
12 // Permissão e validação
13 checkPermissions(input, context): Promise<PermissionResult>
14 validateInput(input, context): Promise<ValidationResult>
15
16 // Integração com API
17 description(input, options): Promise<string>
18 prompt(options): Promise<string> // Texto do prompt do sistema para esta ferramenta
19 mapToolResultToToolResultBlockParam(result, toolUseId): ToolResultBlockParam
20
21 // Renderização de UI (React)
22 renderToolUseMessage(input, options): ReactNode
23 renderToolResultMessage(content, ...): ReactNode
24}

Nenhuma ferramenta implementa isso do zero. Uma função de fábrica chamada buildTool() preenche valores padrão seguros:

Spandan Madan - inline image

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:

text
1type ToolResult<T> = {
2 data: T // A saída real
3 newMessages?: Message[] // Mensagens de acompanhamento opcionais
4 contextModifier?: (ctx) => ToolUseContext // Modifica o contexto para a próxima ferramenta
5 mcpMeta?: { ... } // Metadados do protocolo MCP
6}

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)

Spandan Madan - inline image

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:

  1. getAllBaseTools() — retorna a lista bruta de mais de 43 ferramentas integradas com as feature flags aplicadas
  2. getTools(permissionContext) — filtra por regras de negação e isEnabled()
  3. 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:

text
1// Ordena cada partição alfabeticamente, concatena, deduplica
2const 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:

text
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.

text
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ção
5}

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:

  1. 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.
  2. 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).
  3. 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.
  4. 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.
  5. Modo de permissão — o modo configurado pelo usuário determina o comportamento padrão.
  6. 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:

text
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.

text
1// Simplificado de toolOrchestration.ts
2for (const toolUse of toolUseMessages) {
3 const isSafe = tool.isConcurrencySafe(parsedInput)
4 if (isSafe && lastBatch.isConcurrencySafe) {
5 lastBatch.blocks.push(toolUse) // Mescla no lote concorrente
6 } 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.

text
1if (isErrorResult && tool.block.name === BASH_TOOL_NAME) {
2 this.hasErrored = true
3 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:

text
1{
2 "type": "tool_use",
3 "id": "toolu_01XYZ",
4 "name": "read",
5 "input": { "file_path": "/src/index.ts" }
6}
  1. Extração: query.ts filtra isso do conteúdo da mensagem do assistente.
  2. Busca de ferramenta: findToolByName(tools, "read") encontra FileReadTool.
  3. 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.
  4. Hooks pré-ferramenta: Quaisquer hooks configurados pelo usuário são acionados. Nenhum modifica a entrada.
  5. Verificação de permissão: checkPermissions() do FileReadTool chama checkReadPermissionForTool(). Ferramentas de leitura geralmente são permitidas na maioria dos modos de permissão.
  6. Execução: FileReadTool.call() lê o arquivo, aplica numeração de linhas (formato cat -n), lida com PDFs/imagens/notebooks como casos especiais.
  7. Mapeamento do resultado: O conteúdo do arquivo se torna um bloco tool_result referenciando "toolu_01XYZ".
  8. 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.

Guardar com um clique

Faça leitura aprofundada de artigos virais com IA no YouMind

Guarde a fonte, faça perguntas específicas, resuma o argumento e transforme um artigo viral em notas reutilizáveis num único espaço de trabalho com IA.

Explorar o YouMind
Para criadores

Transforme o seu Markdown num artigo 𝕏 impecável

Quando publica os seus próprios textos longos, formatar imagens, tabelas e blocos de código para o 𝕏 é uma dor de cabeça. O YouMind transforma um rascunho completo em Markdown num artigo 𝕏 impecável e pronto a publicar.

Experimente Markdown para 𝕏

Mais padrões para decifrar

Artigos virais recentes

Explorar mais artigos virais