L'architettura degli strumenti di Claude Code

@spandan_madan
INGLESE4 settimane fa · 17 giu 2026
552K
416
115
86
479

TL;DR

Questo approfondimento tecnico esplora l'architettura degli strumenti di Claude Code, descrivendo in dettaglio la sua pipeline di invio a sette fasi, lo scheduler di concorrenza e il sistema di autorizzazione fail-closed che abilita complesse azioni degli agenti AI.

Ecco la traduzione italiana del testo, seguendo tutte le linee guida fornite.


Introduzione

In un post precedente, abbiamo fatto reverse engineering del sistema di memoria dell'Anthropic Harness a partire dal suo codice sorgente trapelato. La memoria si è rivelata sorprendentemente semplice: file markdown, frontmatter e prompt engineering. Il sistema degli strumenti è l'opposto. È il sottosistema più ingegnerizzato dell'intero codebase: oltre 43 strumenti, una pipeline di esecuzione in streaming, un sistema di autorizzazioni a più livelli, un framework di hook e uno scheduler di concorrenza, il tutto collegato per trasformare un modello linguistico stateless in qualcosa in grado di leggere file, eseguire comandi shell, cercare sul web e generare sotto-agenti.

Questo post analizza il ciclo di vita di uno strumento, dalla definizione, a come le richieste di strumento del modello vengono distribuite, a come i risultati rifluiscono nella conversazione. In generale, il sistema è composto da quattro livelli: un'interfaccia dello strumento che ogni strumento implementa, un registro che assembla il pool di strumenti, una pipeline di dispatch che convalida, verifica le autorizzazioni ed esegue ogni chiamata, e uno scheduler di concorrenza che decide cosa viene eseguito in parallelo.

L'Architettura a Colpo d'Occhio

Spandan Madan - inline image

L'Interfaccia dello Strumento

Ogni strumento in Claude Code implementa la stessa interfaccia, definita in Tool.ts. Il tipo è generico su tre parametri: Input (uno schema Zod), Output (il tipo del risultato) e P (dati di avanzamento). In pratica, uno strumento è un oggetto con circa 30 metodi, ma solo alcuni sono importanti per comprendere il sistema.

La struttura principale:

text
1type Tool<Input, Output, P> = {
2 name: string
3 inputSchema: ZodType // Schema Zod per la convalida dell'input
4 call(input, context, canUseTool,
5 parentMessage, onProgress): Promise<ToolResult>
6
7 // Dichiarazioni di comportamento
8 isConcurrencySafe(input): boolean // Può essere eseguito in parallelo?
9 isReadOnly(input): boolean // Operazione di sola lettura?
10 isDestructive(input): boolean // Azione distruttiva?
11
12 // Autorizzazione e convalida
13 checkPermissions(input, context): Promise<PermissionResult>
14 validateInput(input, context): Promise<ValidationResult>
15
16 // Integrazione API
17 description(input, options): Promise<string>
18 prompt(options): Promise<string> // Testo del prompt di sistema per questo strumento
19 mapToolResultToToolResultBlockParam(result, toolUseId): ToolResultBlockParam
20
21 // Rendering UI (React)
22 renderToolUseMessage(input, options): ReactNode
23 renderToolResultMessage(content, ...): ReactNode
24}

Nessuno strumento implementa tutto da zero. Una funzione factory chiamata buildTool() fornisce valori predefiniti sicuri:

Spandan Madan - inline image

I valori predefiniti sono deliberatamente conservativi. Un autore di strumenti che dimentica di dichiarare la sicurezza di concorrenza ottiene l'esecuzione seriale. Un autore che dimentica di implementare i controlli di autorizzazione ottiene il flusso di autorizzazione predefinito. Il sistema fallisce in modo bloccato.

Vale la pena notare il tipo ToolResult:

text
1type ToolResult<T> = {
2 data: T // L'output effettivo
3 newMessages?: Message[] // Messaggi di follow-up opzionali
4 contextModifier?: (ctx) => ToolUseContext // Modifica il contesto per il prossimo strumento
5 mcpMeta?: { ... } // Metadati del protocollo MCP
6}

contextModifier è importante: permette a uno strumento di modificare il contesto di esecuzione per gli strumenti successivi nello stesso turno. È così che strumenti come EnterWorktree cambiano la directory di lavoro per tutto ciò che segue. Fondamentalmente, i modificatori di contesto sono consentiti solo per strumenti non sicuri per la concorrenza. Se uno strumento viene eseguito in parallelo, non può modificare lo stato condiviso.

Il Registro degli Strumenti

Tutti gli strumenti sono registrati in un'unica funzione: getAllBaseTools() in tools.ts. Restituisce un array piatto. Alcuni strumenti sono sempre presenti; altri sono vincolati da flag di funzionalità, variabili d'ambiente o controlli di piattaforma.

Sempre disponibili (16 strumenti)

Spandan Madan - inline image

Strumenti vincolati da flag di funzionalità (~27 strumenti)

I restanti strumenti sono inclusi condizionalmente. Alcuni sono vincolati da variabili d'ambiente (USER_TYPE=ant per strumenti interni ad Anthropic come config e tungsten). Alcuni sono vincolati da flag di funzionalità tramite Statsig (web_browser, sleep, monitor). Alcuni sono specifici della piattaforma (powershell su Windows). Alcuni sono vincolati da condizioni composte: lo strumento repl richiede sia USER_TYPE=ant che un flag di funzionalità REPL.

Elenco completo degli strumenti vincolati da flag di funzionalità

Solo Ant: config, tungsten, suggest_background_pr, repl (richiede anche il flag REPL)

Flag di funzionalità: web_browser, web_search, sleep, monitor, overflow_test, ctx_inspect, terminal_capture, list_peers, workflow, snip

Trigger per agenti: cron_create, cron_delete, cron_list, remote_trigger

Kairos (agente proattivo): sleep, send_user_file, push_notification, subscribe_pr

Sciami multi-agente: 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 (modalità worktree), powershell (Windows)

Scoperta di strumenti: tool_search (quando il pool di strumenti è grande)

Solo test: testing_permission (NODE_ENV=test)

Strumenti MCP

Oltre agli strumenti integrati, Claude Code supporta i server Model Context Protocol (MCP): processi esterni che espongono i propri strumenti tramite un protocollo standardizzato. Gli strumenti MCP vengono registrati dinamicamente in fase di esecuzione dai server connessi e incapsulati nella stessa interfaccia Tool. Dal punto di vista della pipeline di dispatch, uno strumento MCP è indistinguibile da uno strumento integrato.

Ogni strumento MCP porta con sé metadati sul server di origine (mcpInfo: { serverName, toolName }), che vengono utilizzati per le regole di autorizzazione, la gestione degli errori e l'autenticazione. Quando uno strumento MCP fallisce con un errore di autenticazione, il sistema aggiorna automaticamente lo stato del server a needs-auth e segnala il problema all'utente.

Assemblaggio del Pool di Strumenti

Tre funzioni assemblano il set finale di strumenti:

  1. getAllBaseTools() — restituisce l'elenco grezzo di oltre 43 strumenti integrati con i flag di funzionalità applicati
  2. getTools(permissionContext) — filtra in base alle regole di diniego e a isEnabled()
  3. assembleToolPool(permissionContext, mcpTools) — unisce strumenti integrati e MCP

La strategia di unione in assembleToolPool() è deliberata:

Gli strumenti integrati vengono prima, quindi in caso di collisione di nomi, quello integrato vince. L'ordinamento alfabetico all'interno di ogni partizione mantiene l'ordine stabile tra le sessioni, il che è importante per la memorizzazione nella cache del prompt: l'array degli strumenti fa parte della richiesta API e riordinarlo invaliderebbe la cache.

La strategia di unione in assembleToolPool() è deliberata:

text
1// Ordina ogni partizione 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)

Serializzazione API

Prima che gli strumenti raggiungano l'API di Claude, toolToAPISchema() converte lo schema Zod di ogni strumento nel formato JSON Schema dell'API Anthropic.

La Pipeline di Dispatch

Quando Claude risponde, il suo messaggio può contenere blocchi tool_use: richieste strutturate per invocare strumenti. La pipeline di dispatch elabora questi blocchi attraverso sette fasi. Ogni chiamata a uno strumento attraversa ogni fase, in ordine.

Fase 1: Estrazione

Nel ciclo di query principale (query.ts), i blocchi tool_use vengono filtrati dal messaggio dell'assistente:

text
1const msgToolUseBlocks = message.message.content.filter(
2 content => content.type === 'tool_use',
3) as ToolUseBlock[]

Ogni blocco ha un name, un oggetto input e un id univoco. L'id è fondamentale: il risultato dello strumento deve fare riferimento allo stesso id quando viene rispedito all'API, altrimenti la conversazione si interrompe.

Fase 2: Convalida dell'Input

Lo schema Zod dello strumento convalida l'input grezzo usando safeParse(): una variante che non solleva eccezioni e restituisce dati validi o un errore strutturato. Se la convalida fallisce, il modello riceve un messaggio di errore formattato con suggerimenti sullo schema e l'esecuzione si arresta per quello strumento. Nessun codice viene eseguito su input non validi.

text
1const parsedInput = tool.inputSchema.safeParse(input)
2if (!parsedInput.success) {
3 let errorContent = formatZodValidationError(tool.name, parsedInput.error)
4 // Restituisce errore al modello, salta l'esecuzione
5}

Dopo la convalida Zod, alcuni strumenti eseguono un secondo controllo validateInput() per una convalida semantica che non può essere espressa in uno schema, ad esempio verificando che un percorso file sia assoluto e non relativo.

Fase 3: Hook Pre-Strumento

Prima dei controlli di autorizzazione, vengono eseguiti gli hook configurati dall'utente. Questi sono comandi shell o script esterni che si attivano sulle invocazioni degli strumenti. Un hook pre-strumento può:

  • Consentire la chiamata allo strumento, bypassando il prompt di autorizzazione interattivo
  • Negare la chiamata allo strumento direttamente
  • Modificare l'input prima dell'esecuzione
  • Bloccare l'esecuzione con un messaggio di errore
  • Fornire contesto aggiuntivo all'utente

Un invariante critico: l'allow di un hook non bypassa le regole di diniego dalle impostazioni. Il codice sorgente ha un commento esplicito a riguardo: "Hook 'allow' does NOT bypass settings.json deny/ask rules." L'intento è che gli hook possono aprire porte, ma non sovrascrivere serrature.

Fase 4: Controllo delle Autorizzazioni

Il sistema di autorizzazioni è la parte più complessa della pipeline. Si risolve attraverso più livelli, in ordine:

  1. Regole di diniego: controllate per prime. Se una regola di diniego corrisponde, l'esecuzione si arresta immediatamente. Le regole di diniego sono definitive e non possono essere sovrascritte da nessun altro livello.
  2. Regole di richiesta: se corrispondono, all'utente viene chiesto di approvare (a meno che l'auto-consenso sandbox non si applichi per Bash).
  3. Autorizzazioni specifiche dello strumento: viene eseguito il metodo checkPermissions() dello strumento stesso. BashTool, ad esempio, analizza il comando per verificare le regole a livello di sottocomando.
  4. Controlli di sicurezza: protezioni hardcoded per percorsi sensibili (.git/, .claude/, configurazioni della shell). Questi sono immuni al bypass: anche in modalità di bypass totale, richiedono l'approvazione interattiva.
  5. Modalità di autorizzazione: la modalità configurata dall'utente determina il comportamento predefinito.
  6. Regole di consenso: controllate per ultime. Se una regola di consenso corrisponde e nessuna regola di diniego/richiesta è stata attivata, lo strumento procede.

Modalità di autorizzazione

default: Richiede sempre all'utente per le decisioni "ask".

acceptEdits: Auto-consente operazioni sicure sui file (letture, modifiche), richiede per tutto il resto.

bypassPermissions: Auto-consente tutto tranne le regole di diniego e i controlli di sicurezza.

plan: Approva prima un piano, poi segue la modalità precedente per l'esecuzione.

auto: Utilizza un classificatore AI per decidere se consentire o richiedere.

dontAsk: Converte tutte le decisioni "ask" in "deny": non richiedere mai, rifiuta e basta.

Le regole di autorizzazione provengono da più fonti, risolte in ordine di priorità: policySettings, localSettings, projectSettings, userSettings, flagSettings, cliArg, command, session. Ciò consente alle policy organizzative di sovrascrivere le preferenze dell'utente e agli argomenti CLI di sovrascrivere entrambi.

Fase 5: Esecuzione

Se l'autorizzazione è concessa, viene invocato il metodo call() dello strumento:

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)

Cinque argomenti: l'input convalidato, un contesto di esecuzione (directory di lavoro, controller di annullamento, stato dell'app), un callback di autorizzazione (per strumenti che necessitano di richiedere autorizzazioni aggiuntive durante l'esecuzione), il messaggio dell'assistente genitore e un callback di avanzamento per aggiornamenti in tempo reale. La durata viene tracciata a livello globale.

Un dettaglio sottile: l'input passato a call() è l'input originale del modello, non la versione modificata che hook e autorizzazioni hanno visto. Ciò preserva la coerenza della trascrizione: la chiamata allo strumento registrata nella conversazione corrisponde esattamente a ciò che il modello ha generato.

Fase 6: Hook Post-Strumento

Dopo l'esecuzione, vengono attivati gli hook post-strumento. Questi possono modificare l'output dello strumento MCP, fornire contesto aggiuntivo o bloccare la prosecuzione della conversazione. Esiste anche un hook PostToolUseFailure separato che si attiva solo sugli errori, dando ai sistemi esterni la possibilità di registrare i fallimenti o suggerire rimedi.

Fase 7: Mappatura del Risultato

Ogni strumento implementa mapToolResultToToolResultBlockParam() per convertire il proprio output nel formato ToolResultBlockParam dell'API Anthropic: un blocco tool_result con un riferimento tool_use_id e contenuto stringa o strutturato.

Se il risultato supera una soglia di dimensione, viene salvato su disco in sessionDir/tool-results/{toolUseId}.txt e un'anteprima con un riferimento al file viene inviata all'API. Ciò impedisce che output di grandi dimensioni (la lettura di un file di 10.000 righe, l'output di un comando verboso) appesantiscano il contesto della conversazione.

Lo Scheduler di Concorrenza

Quando il modello emette più chiamate a strumenti in un singolo messaggio, non vengono eseguite tutte contemporaneamente. Uno scheduler le partiziona in batch in base alla sicurezza di concorrenza.

L'algoritmo è semplice. Analizza le chiamate agli strumenti in ordine. Per ognuna, controlla isConcurrencySafe(input). Se è sicura e il batch precedente era anch'esso sicuro, aggiungila al batch. Altrimenti, avvia un nuovo batch.

text
1// Semplificato da toolOrchestration.ts
2for (const toolUse of toolUseMessages) {
3 const isSafe = tool.isConcurrencySafe(parsedInput)
4 if (isSafe && lastBatch.isConcurrencySafe) {
5 lastBatch.blocks.push(toolUse) // Unisci nel batch concorrente
6 } else {
7 batches.push({ isConcurrencySafe: isSafe, blocks: [toolUse] })
8 }
9}

I batch sicuri vengono eseguiti contemporaneamente (fino a un limite di 10, configurabile tramite CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY). I batch non sicuri vengono eseguiti in serie, uno strumento alla volta. I modificatori di contesto vengono applicati solo tra un batch e l'altro, non al loro interno.

In pratica, ciò significa che un messaggio come "leggi questi 5 file" produce un batch concorrente, mentre "leggi questo file, poi modificalo" produce due batch seriali. Il modello può persino attivare entrambi i modelli in un singolo turno: le chiamate consecutive di sola lettura vengono raggruppate e la prima scrittura interrompe il batch.

L'Executor in Streaming

Esiste un secondo percorso di esecuzione: lo StreamingToolExecutor. Quando lo streaming è abilitato, gli strumenti iniziano a essere eseguiti mentre il modello sta ancora generando la risposta. Non appena un blocco tool_use viene completato nello stream, viene immediatamente accodato per l'esecuzione, invece di attendere la risposta completa.

L'executor in streaming utilizza le stesse regole di concorrenza ma aggiunge un comportamento: la cascata di errori di Bash. Se un comando Bash fallisce mentre gli strumenti fratelli sono in esecuzione in parallelo, l'executor interrompe tutti i fratelli. La logica è che un comando Bash fallito probabilmente invalida il contesto su cui operano gli altri strumenti: continuarli farebbe perdere tempo e potrebbe causare errori confusi.

text
1if (isErrorResult && tool.block.name === BASH_TOOL_NAME) {
2 this.hasErrored = true
3 this.siblingAbortController.abort('sibling_error')
4}

Un Esempio Pratico

Per rendere il tutto concreto, tracciamo cosa succede quando il modello decide di leggere un file. Il modello emette:

text
1{
2 "type": "tool_use",
3 "id": "toolu_01XYZ",
4 "name": "read",
5 "input": { "file_path": "/src/index.ts" }
6}

Estrazione: query.ts filtra questo dal contenuto del messaggio dell'assistente.

  1. Ricerca dello strumento: findToolByName(tools, "read") trova FileReadTool.
  2. Convalida dell'input: Zod analizza { file_path: "/src/index.ts" } rispetto a z.object({ file_path: z.string(), offset: z.number().optional(), limit: z.number().optional(), pages: z.string().optional() }). Supera la convalida.
  3. Hook pre-strumento: eventuali hook configurati dall'utente vengono attivati. Nessuno modifica l'input.
  4. Controllo delle autorizzazioni: checkPermissions() di FileReadTool chiama checkReadPermissionForTool(). Gli strumenti di lettura sono generalmente consentiti nella maggior parte delle modalità di autorizzazione.
  5. Esecuzione: FileReadTool.call() legge il file, applica la numerazione delle righe (formato cat -n), gestisce PDF/immagini/notebook come casi speciali.
  6. Mappatura del risultato: Il contenuto del file diventa un blocco tool_result che fa riferimento a "toolu_01XYZ".
  7. Restituzione: Il risultato viene aggiunto alla conversazione come messaggio utente e inviato nella successiva chiamata API.

Poiché FileReadTool dichiara isConcurrencySafe: () => true e isReadOnly: () => true, se il modello avesse emesso cinque chiamate di lettura nello stesso messaggio, tutte e cinque sarebbero state eseguite in parallelo.

Riepilogo

Il sistema degli strumenti è la spina dorsale esecutiva di Claude Code. Prende l'intenzione del modello, espressa come blocchi strutturati tool_use, e la trasforma in azioni reali sulla tua macchina, con convalida, autorizzazioni e controllo della concorrenza in ogni fase.

Il design è a strati: una factory buildTool() conservativa garantisce valori predefiniti sicuri, un registro basato su flag di funzionalità controlla cosa è disponibile, una pipeline di dispatch in sette fasi convalida e verifica le autorizzazioni di ogni chiamata, e uno scheduler di concorrenza massimizza il parallelismo preservando la correttezza. L'executor in streaming aggiunge un'ottimizzazione delle performance: gli strumenti iniziano a essere eseguiti prima che il modello finisca di pensare.

Rispetto al sistema di memoria (5 percorsi, una directory di file markdown e prompt engineering), il sistema degli strumenti è un vero e proprio runtime. È la differenza tra un archivio e un sistema operativo.

Cosa è Interessante

Il Modello come Scheduler

Lo scheduler di concorrenza è reattivo: raggruppa in batch tutto ciò che il modello emette. Ma il modello stesso è il vero scheduler. Il prompt di sistema gli dice di "eseguire tutte le chiamate a strumenti indipendenti in parallelo" e di "usare una singola chiamata Bash con && per concatenare comandi dipendenti". Il runtime si fida di questo. Se il modello emette cinque letture seguite da una scrittura, lo scheduler parallelizzerà le letture e serializzerà la scrittura. Ma è il modello che ha deciso quell'ordine. Lo scheduler sta imponendo il piano del modello, non creandone uno proprio.

Fallimento Bloccato di Default

Il principio di progettazione più coerente: tutto fallisce in modalità bloccata. Strumento sconosciuto? Errore. Input non valido? Errore. Nessuna dichiarazione di concorrenza? Esecuzione seriale. Nessuna dichiarazione di autorizzazione? Chiedi all'utente. Nessun flag di funzionalità? Lo strumento non esiste. Questo è insolito per un sistema in cui l'utente principale è un modello AI che potrebbe allucinare nomi di strumenti o malformare input. Il sistema è progettato per contenere gli errori del modello, non per assecondarli.

Gli Hook come Punto di Estensione

Il sistema di hook (pre-strumento, post-strumento e post-fallimento) è il punto di estensione principale. È il modo in cui le organizzazioni applicano le policy (regole di diniego negli hook pre), i sistemi di logging catturano l'uso degli strumenti (hook post) e le pipeline CI/CD si integrano (hook di fallimento). È importante notare che gli hook possono solo restringere le restrizioni, non allentarle. Un hook può negare uno strumento che le impostazioni consentono, ma non può consentire uno strumento che le impostazioni negano.

43 Strumenti, 1 Interfaccia

Forse la cosa più sorprendente è l'uniformità. Un comando bash, un web_fetch, la creazione di un sotto-agente, la creazione di un cron job e una notifica push implementano tutti la stessa interfaccia a 30 metodi, attraversano la stessa pipeline in sette fasi e rispettano lo stesso sistema di autorizzazioni. Non ci sono casi speciali nel dispatcher. La complessità risiede nelle implementazioni dei singoli strumenti e nelle regole di autorizzazione, non nell'instradamento.


Salva con un clic

Leggi in profondità gli articoli virali con l’AI di YouMind

Salva la fonte, fai domande mirate, riassumi l’argomentazione e trasforma un articolo virale in note riutilizzabili in un unico spazio di lavoro AI.

Scopri YouMind
Per i creator

Trasforma il tuo Markdown in un articolo 𝕏 pulito

Quando pubblichi i tuoi testi lunghi, formattare immagini, tabelle e blocchi di codice per 𝕏 è una seccatura. YouMind trasforma un'intera bozza Markdown in un articolo 𝕏 pulito e pronto da pubblicare.

Prova Markdown verso 𝕏

Altri pattern da decodificare

Articoli virali recenti

Esplora altri articoli virali