A arquitetura de ferramentas do Claude Code

@spandan_madan
INGLÊShá 4 semanas · 17 de jun. de 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ão "fail-closed" que viabiliza 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 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

Spandan Madan - inline image

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:

text
1type Tool<Input, Output, P> = {
2 name: string
3 inputSchema: ZodType // Schema 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 executar 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 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:

text
1type ToolResult<T> = {
2 data: T // A saída real
3 newMessages?: Message[] // Mensagens de acompanhamento opcionais
4 contextModifier?: (ctx) => ToolUseContext // Altera 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 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)

Spandan Madan - inline image

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:

  1. getAllBaseTools() — retorna a lista bruta de mais de 43 ferramentas integradas com gates de funcionalidade aplicados
  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 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:

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 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:

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

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 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 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:

  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 método checkPermissions() da própria ferramenta é executado. BashTool, por exemplo, analisa o comando para verificar regras em nível de subcomando.
  4. 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.
  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 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:

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

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

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}

Extração: query.ts filtra isso do conteúdo da mensagem do assistente.

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

Salvar com um clique

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

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

Explorar o YouMind
Para criadores

Transforme seu Markdown em um artigo 𝕏 impecável

Quando você publica 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 em um artigo 𝕏 impecável e pronto para publicar.

Experimente Markdown para 𝕏

Mais padrões para decifrar

Artigos virais recentes

Explorar mais artigos virais