La arquitectura de herramientas de Claude Code

@spandan_madan
INGLÉShace 4 semanas · 17 jun 2026
552K
416
115
86
479

TL;DR

Este análisis técnico explora la arquitectura de herramientas de Claude Code, detallando su pipeline de despacho de siete fases, su programador de concurrencia y su sistema de permisos de cierre seguro (fail-closed) que permite acciones complejas de agentes de IA.

Un análisis profundo de cómo Claude Code descubre, despacha y ejecuta herramientas

Introducción

En una publicación anterior, hicimos ingeniería inversa del sistema de memoria de Anthropic Harness a partir de su código fuente filtrado. La memoria resultó ser sorprendentemente simple: archivos Markdown, frontmatter e ingeniería de prompts. El sistema de herramientas es todo lo contrario. Es el subsistema más elaborado de todo el código base: más de 43 herramientas, un pipeline de ejecución en streaming, un sistema de permisos por capas, un framework de hooks y un planificador de concurrencia, todo conectado para convertir un modelo de lenguaje sin estado en algo que puede leer archivos, ejecutar comandos de shell, buscar en la web y generar subagentes.

Esta publicación recorre el ciclo de vida de una herramienta, desde cómo se define, hasta cómo se envían las llamadas a herramientas del modelo, y cómo los resultados vuelven a la conversación. En términos generales, el sistema se compone de cuatro capas: una interfaz de herramienta que toda herramienta implementa, un registro que ensambla el conjunto de herramientas, un pipeline de despacho que valida, verifica permisos y ejecuta cada llamada, y un planificador de concurrencia que decide qué se ejecuta en paralelo.

La arquitectura de un vistazo

Spandan Madan - inline image

La interfaz de herramienta

Toda herramienta en Claude Code implementa la misma interfaz, definida en Tool.ts. El tipo es genérico sobre tres parámetros: Input (un esquema Zod), Output (el tipo de resultado) y P (datos de progreso). En la práctica, una herramienta es un objeto con unos 30 métodos, pero solo unos pocos importan para entender el sistema.

La forma central:

text
1type Tool<Input, Output, P> = {
2 name: string
3 inputSchema: ZodType // Esquema Zod para validación de entrada
4 call(input, context, canUseTool,
5 parentMessage, onProgress): Promise<ToolResult>
6
7 // Declaraciones de comportamiento
8 isConcurrencySafe(input): boolean // ¿Puede ejecutarse en paralelo?
9 isReadOnly(input): boolean // ¿Operación de solo lectura?
10 isDestructive(input): boolean // ¿Acción destructiva?
11
12 // Permisos y validación
13 checkPermissions(input, context): Promise<PermissionResult>
14 validateInput(input, context): Promise<ValidationResult>
15
16 // Integración con API
17 description(input, options): Promise<string>
18 prompt(options): Promise<string> // Texto del prompt del sistema para esta herramienta
19 mapToolResultToToolResultBlockParam(result, toolUseId): ToolResultBlockParam
20
21 // Renderizado UI (React)
22 renderToolUseMessage(input, options): ReactNode
23 renderToolResultMessage(content, ...): ReactNode
24}

Ninguna herramienta implementa esto desde cero. Una función de fábrica llamada buildTool() completa valores predeterminados seguros:

Spandan Madan - inline image

Los valores predeterminados son deliberadamente conservadores. Un autor de herramienta que olvide declarar seguridad de concurrencia obtiene ejecución en serie. Un autor que olvide implementar verificaciones de permisos obtiene el flujo de permisos predeterminado. El sistema falla de forma segura.

El tipo ToolResult vale la pena destacarlo:

text
1type ToolResult<T> = {
2 data: T // La salida real
3 newMessages?: Message[] // Mensajes de seguimiento opcionales
4 contextModifier?: (ctx) => ToolUseContext // Modificar contexto para la siguiente herramienta
5 mcpMeta?: { ... } // Metadatos del protocolo MCP
6}

El contextModifier es importante: permite que una herramienta cambie el contexto de ejecución para herramientas posteriores en el mismo turno. Así es como herramientas como EnterWorktree cambian el directorio de trabajo para todo lo que sigue. De manera crítica, los modificadores de contexto solo están permitidos para herramientas que no son seguras para concurrencia. Si una herramienta se ejecuta en paralelo, no puede modificar el estado compartido.

El registro de herramientas

Todas las herramientas se registran en una única función: getAllBaseTools() en tools.ts. Devuelve un array plano. Algunas herramientas siempre están presentes; otras están protegidas por banderas de funcionalidad, variables de entorno o verificaciones de plataforma.

Siempre disponibles (16 herramientas)

Spandan Madan - inline image

Herramientas con protección por banderas (~27 herramientas)

Las herramientas restantes se incluyen condicionalmente. Algunas están protegidas por variables de entorno (USER_TYPE=ant para herramientas internas de Anthropic como config y tungsten). Otras están protegidas por banderas de funcionalidad a través de Statsig (web_browser, sleep, monitor). Algunas son específicas de plataforma (powershell en Windows). Otras están protegidas por condiciones compuestas: la herramienta repl requiere tanto USER_TYPE=ant como una bandera de funcionalidad REPL.

Lista completa de herramientas con protección por banderas

Solo para Ant: config, tungsten, suggest_background_pr, repl (también necesita bandera REPL)

Banderas de funcionalidad: web_browser, web_search, sleep, monitor, overflow_test, ctx_inspect, terminal_capture, list_peers, workflow, snip

Disparadores de agente: cron_create, cron_delete, cron_list, remote_trigger

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

Enjambres multiagente: team_create, team_delete, send_message

Todo v2: task_create, task_get, task_update, task_list

Entorno: lsp (ENABLE_LSP_TOOL), enter_worktree / exit_worktree (modo worktree), powershell (Windows)

Descubrimiento de herramientas: tool_search (cuando el conjunto de herramientas es grande)

Solo pruebas: testing_permission (NODE_ENV=test)

Herramientas MCP

Además de las herramientas integradas, Claude Code admite servidores Model Context Protocol (MCP): procesos externos que exponen sus propias herramientas a través de un protocolo estandarizado. Las herramientas MCP se registran dinámicamente en tiempo de ejecución desde servidores conectados y se envuelven en la misma interfaz Tool. Desde la perspectiva del pipeline de despacho, una herramienta MCP es indistinguible de una herramienta integrada.

Cada herramienta MCP lleva metadatos sobre su servidor de origen (mcpInfo: { serverName, toolName }), que se utilizan para reglas de permisos, manejo de errores y autenticación. Cuando una herramienta MCP falla con un error de autenticación, el sistema actualiza automáticamente el estado del servidor a needs-auth y muestra el problema al usuario.

Ensamblaje del conjunto de herramientas

Tres funciones ensamblan el conjunto final de herramientas:

  1. getAllBaseTools(): devuelve la lista bruta de más de 43 herramientas integradas con las banderas de funcionalidad aplicadas
  2. getTools(permissionContext): filtra por reglas de denegación e isEnabled()
  3. assembleToolPool(permissionContext, mcpTools): fusiona herramientas integradas y MCP

La estrategia de fusión en assembleToolPool() es deliberada:

Las herramientas integradas van primero, por lo que en caso de colisión de nombres, la integrada gana. La ordenación alfabética dentro de cada partición mantiene el orden estable entre sesiones, lo que importa para el almacenamiento en caché de prompts: el array de herramientas es parte de la solicitud a la API, y reordenarlo rompería la caché.

La estrategia de fusión en assembleToolPool() es deliberada:

text
1// Ordenar cada partición alfabéticamente, concatenar, deduplicar
2const byName = (a, b) => a.name.localeCompare(b.name)
3return uniqBy(
4 [...builtInTools].sort(byName).concat(allowedMcpTools.sort(byName)),
5 'name',
6)

Serialización para la API

Antes de que las herramientas lleguen a la API de Claude, toolToAPISchema() convierte el esquema Zod de cada herramienta al formato JSON Schema de la API de Anthropic.

El pipeline de despacho

Cuando Claude responde, su mensaje puede contener bloques tool_use (solicitudes estructuradas para invocar herramientas). El pipeline de despacho procesa estos bloques a través de siete fases. Cada llamada a herramienta pasa por todas las fases, en orden.

Fase 1: Extracción

En el bucle principal de consulta (query.ts), los bloques tool_use se filtran del mensaje del asistente:

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

Cada bloque tiene un name, un objeto input y un id único. El id es fundamental: el resultado de la herramienta debe hacer referencia al mismo id cuando se envíe de vuelta a la API, o la conversación se rompe.

Fase 2: Validación de entrada

El esquema Zod de la herramienta valida la entrada bruta usando safeParse(), una variante que no lanza excepciones y devuelve datos válidos o un error estructurado. Si la validación falla, el modelo recibe un mensaje de error formateado con pistas del esquema, y la ejecución se detiene para esa herramienta. No se ejecuta código con entrada no válida.

text
1const parsedInput = tool.inputSchema.safeParse(input)
2if (!parsedInput.success) {
3 let errorContent = formatZodValidationError(tool.name, parsedInput.error)
4 // Devolver error al modelo, saltar ejecución
5}

Después de la validación de Zod, algunas herramientas ejecutan una segunda verificación validateInput() para validación semántica que no se puede expresar en un esquema, por ejemplo, verificar que una ruta de archivo sea absoluta, no relativa.

Fase 3: Hooks previos a la herramienta

Antes de las verificaciones de permisos, se ejecutan los hooks configurados por el usuario. Estos son comandos o scripts de shell externos que se activan en las invocaciones de herramientas. Un hook previo puede:

  • Permitir la llamada a la herramienta, omitiendo el mensaje de permiso interactivo
  • Denegar la llamada a la herramienta directamente
  • Modificar la entrada antes de la ejecución
  • Bloquear la ejecución con un mensaje de error
  • Proporcionar contexto adicional al usuario

Un invariante crítico: un allow de un hook no omite las reglas de denegación de la configuración. El código fuente tiene un comentario explícito al respecto: "El 'allow' del hook NO omite las reglas deny/ask de settings.json". La intención es que los hooks puedan abrir puertas, pero no anular cerraduras.

Fase 4: Verificación de permisos

El sistema de permisos es la parte más intrincada del pipeline. Resuelve a través de múltiples capas, en orden:

  1. Reglas de denegación: se verifican primero. Si alguna regla de denegación coincide, la ejecución se detiene inmediatamente. Las reglas de denegación son definitivas y no pueden ser anuladas por ninguna otra capa.
  2. Reglas de pregunta: si coinciden, se solicita la aprobación del usuario (a menos que se aplique la autoautorización sandbox para Bash).
  3. Permisos específicos de herramienta: se ejecuta el método checkPermissions() de la propia herramienta. BashTool, por ejemplo, analiza el comando para verificar reglas a nivel de subcomando.
  4. Verificaciones de seguridad: protecciones codificadas para rutas sensibles (.git/, .claude/, configuraciones de shell). Estas son inmunes a omisiones: incluso en modo de omisión total, requieren aprobación interactiva.
  5. Modo de permisos: el modo configurado por el usuario determina el comportamiento predeterminado.
  6. Reglas de permiso: se verifican al final. Si una regla de permiso coincide y no se activó ninguna regla de denegación o pregunta, la herramienta procede.

Modos de permisos

default: Siempre preguntar al usuario para decisiones "ask".

acceptEdits: Autoautorizar operaciones seguras con archivos (lecturas, ediciones), preguntar para todo lo demás.

bypassPermissions: Autoautorizar todo excepto reglas de denegación y verificaciones de seguridad.

plan: Aprobar un plan primero, luego seguir el modo anterior para la ejecución.

auto: Usar un clasificador de IA para decidir si permitir o preguntar.

dontAsk: Convertir todas las decisiones "ask" en "deny": nunca preguntar, solo rechazar.

Las reglas de permisos provienen de múltiples fuentes, resueltas en orden de prioridad: policySettings, localSettings, projectSettings, userSettings, flagSettings, cliArg, command, session. Esto permite que las políticas de la organización anulen las preferencias del usuario, y que los argumentos de la CLI anulen ambos.

Fase 5: Ejecución

Si se concede el permiso, se invoca el método call() de la herramienta:

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: la entrada validada, un contexto de ejecución (directorio de trabajo, controlador de aborto, estado de la aplicación), un callback de permisos (para herramientas que necesitan solicitar permisos adicionales durante la ejecución), el mensaje del asistente principal y un callback de progreso para actualizaciones en tiempo real. La duración se rastrea globalmente.

Un detalle sutil: la entrada que se pasa a call() es la entrada original del modelo, no la versión modificada que vieron los hooks y los permisos. Esto preserva la coherencia de la transcripción: la llamada a la herramienta registrada en la conversación coincide exactamente con lo que generó el modelo.

Fase 6: Hooks posteriores a la herramienta

Después de la ejecución, se disparan los hooks posteriores a la herramienta. Estos pueden modificar la salida de la herramienta MCP, proporcionar contexto adicional o bloquear la continuación de la conversación. También hay un hook separado PostToolUseFailure que se dispara solo en errores, dando a los sistemas externos la oportunidad de registrar fallos o sugerir correcciones.

Fase 7: Mapeo de resultados

Cada herramienta implementa mapToolResultToToolResultBlockParam() para convertir su salida al formato ToolResultBlockParam de la API de Anthropic: un bloque tool_result con una referencia tool_use_id y contenido de cadena o estructurado.

Si el resultado supera un umbral de tamaño, se persiste en disco en sessionDir/tool-results/{toolUseId}.txt y se envía a la API una vista previa con una referencia al archivo. Esto evita que las salidas grandes (lectura de un archivo de 10,000 líneas, salida de un comando verboso) inflen el contexto de la conversación.

El planificador de concurrencia

Cuando el modelo emite múltiples llamadas a herramientas en un solo mensaje, no todas se ejecutan a la vez. Un planificador las divide en lotes según la seguridad de concurrencia.

El algoritmo es simple. Recorre las llamadas a herramientas en orden. Para cada una, verifica isConcurrencySafe(input). Si es segura y el lote anterior también era seguro, la agrega al lote. De lo contrario, inicia un nuevo 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) // Fusionar en lote concurrente
6 } else {
7 batches.push({ isConcurrencySafe: isSafe, blocks: [toolUse] })
8 }
9}

Los lotes seguros se ejecutan de forma concurrente (hasta un límite de 10, configurable mediante CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY). Los lotes no seguros se ejecutan en serie, una herramienta a la vez. Los modificadores de contexto solo se aplican entre lotes, no dentro de ellos.

En la práctica, esto significa que un mensaje como "lee estos 5 archivos" produce un lote concurrente, mientras que "lee este archivo, luego edítalo" produce dos lotes en serie. El modelo puede incluso activar ambos patrones en un solo turno: las llamadas de solo lectura consecutivas se agrupan, y la primera escritura rompe el lote.

El ejecutor en streaming

Hay una segunda ruta de ejecución: el StreamingToolExecutor. Cuando el streaming está habilitado, las herramientas comienzan a ejecutarse mientras el modelo aún está generando su respuesta. A medida que cada bloque tool_use se completa en el flujo, se pone en cola inmediatamente para su ejecución en lugar de esperar la respuesta completa.

El ejecutor en streaming usa las mismas reglas de concurrencia pero agrega un comportamiento: cascada de errores de Bash. Si un comando de Bash falla mientras las herramientas hermanas se ejecutan en paralelo, el ejecutor aborta todas las hermanas. La razón es que un comando de Bash que falla probablemente invalida el contexto en el que otras herramientas están operando; continuar con ellas pierde tiempo y puede causar errores confusos.

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

Un ejemplo práctico

Para hacer esto concreto, rastreemos lo que sucede cuando el modelo decide leer un archivo. El modelo emite:

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

Extracción: query.ts filtra esto del contenido del mensaje del asistente.

  1. Búsqueda de herramienta: findToolByName(tools, "read") encuentra FileReadTool.
  2. Validación de entrada: Zod analiza { file_path: "/src/index.ts" } contra z.object({ file_path: z.string(), offset: z.number().optional(), limit: z.number().optional(), pages: z.string().optional() }). Pasa.
  3. Hooks previos a la herramienta: Se disparan los hooks configurados por el usuario. Ninguno modifica la entrada.
  4. Verificación de permisos: checkPermissions() de FileReadTool llama a checkReadPermissionForTool(). Las herramientas de lectura generalmente están permitidas en la mayoría de los modos de permisos.
  5. Ejecución: FileReadTool.call() lee el archivo, aplica numeración de líneas (formato cat -n), maneja PDFs/imágenes/notebooks como casos especiales.
  6. Mapeo de resultados: El contenido del archivo se convierte en un bloque tool_result que hace referencia a "toolu_01XYZ".
  7. Retorno: El resultado se añade a la conversación como un mensaje de usuario y se envía en la siguiente llamada a la API.

Debido a que FileReadTool declara isConcurrencySafe: () => true e isReadOnly: () => true, si el modelo hubiera emitido cinco llamadas de lectura en el mismo mensaje, las cinco se ejecutarían en paralelo.

Resumen

El sistema de herramientas es la columna vertebral de ejecución de Claude Code. Toma la intención del modelo, expresada como bloques estructurados tool_use, y la convierte en acciones reales en tu máquina, con validación, permisos y control de concurrencia en cada paso.

El diseño está por capas: una fábrica buildTool() conservadora garantiza valores predeterminados seguros, un registro con banderas de funcionalidad controla lo que está disponible, un pipeline de despacho de siete fases valida y verifica permisos de cada llamada, y un planificador de concurrencia maximiza el paralelismo mientras preserva la corrección. El ejecutor en streaming añade una optimización de rendimiento adicional: las herramientas empiezan a ejecutarse antes de que el modelo termine de pensar.

En comparación con el sistema de memoria (5 rutas, un directorio de archivos Markdown e ingeniería de prompts), el sistema de herramientas es un runtime propiamente dicho. Es la diferencia entre un archivador y un sistema operativo.

Qué es interesante

El modelo como planificador

El planificador de concurrencia es reactivo: agrupa en lotes lo que sea que emita el modelo. Pero el modelo mismo es el verdadero planificador. El prompt del sistema le indica que "haga todas las llamadas independientes a herramientas en paralelo" y que "use una sola llamada a Bash con && para encadenar comandos dependientes". El runtime confía en esto. Si el modelo emite cinco lecturas seguidas de una escritura, el planificador paralelizará las lecturas y serializará la escritura. Pero el modelo decidió ese orden. El planificador está imponiendo el plan del modelo, no creando el suyo propio.

Fallar de forma segura por defecto

El principio de diseño más consistente: todo falla de forma segura. ¿Herramienta desconocida? Error. ¿Entrada no válida? Error. ¿Sin declaración de concurrencia? Ejecución en serie. ¿Sin declaración de permisos? Preguntar al usuario. ¿Sin bandera de funcionalidad? La herramienta no existe. Esto es inusual para un sistema donde el usuario principal es un modelo de IA que podría alucinar nombres de herramientas o malformar entradas. El sistema está diseñado para contener los errores del modelo, no para adaptarse a ellos.

Los hooks como punto de extensión

El sistema de hooks (previo a la herramienta, posterior a la herramienta y posterior al fallo) es el principal punto de extensión. Así es como las organizaciones aplican políticas (reglas de denegación en hooks previos), cómo los sistemas de registro capturan el uso de herramientas (hooks posteriores) y cómo se integran los pipelines de CI/CD (hooks de fallo). Es importante destacar que los hooks solo pueden endurecer las restricciones, no aflojarlas. Un hook puede denegar una herramienta que la configuración permite, pero no puede permitir una herramienta que la configuración deniega.

43 herramientas, 1 interfaz

Quizás lo más llamativo es la uniformidad. Un comando bash, un web_fetch, la creación de un subagente, la creación de un trabajo cron y una notificación push implementan todos la misma interfaz de 30 métodos, pasan por el mismo pipeline de siete fases y respetan el mismo sistema de permisos. No hay casos especiales en el despachador. La complejidad está en las implementaciones individuales de las herramientas y en las reglas de permisos, no en el enrutamiento.

Guardar con un clic

Lee artículos virales en profundidad con IA en YouMind

Guarda la fuente, haz preguntas concretas, resume el argumento y convierte un artículo viral en notas reutilizables en un único espacio de trabajo con IA.

Explora YouMind
Para creadores

Convierte tu Markdown en un artículo de 𝕏 impecable

Cuando publicas tus propios textos largos, dar formato en 𝕏 a imágenes, tablas y bloques de código es un fastidio. YouMind convierte un borrador completo en Markdown en un artículo de 𝕏 impecable y listo para publicar.

Prueba Markdown a 𝕏

Más patrones por descifrar

Artículos virales recientes

Explorar más artículos virales