Tutorial del Claude Agent SDK: ejemplos en Python y TypeScript
Instala el Claude Agent SDK, ejecuta tu primera consulta en Python y TypeScript y luego añade herramientas personalizadas, subagentes, hooks, límites de gasto y sesiones que se pueden retomar. Cada ejemplo está revisado con la documentación actual y listo para pegar en un proyecto.
El Claude Agent SDK te permite ejecutar el mismo bucle de agente que impulsa Claude Code desde dentro de tu propio programa en Python o TypeScript. Envías un prompt y Claude lee archivos, ejecuta comandos, edita código y llama a tus propias funciones hasta terminar el trabajo, mostrándote cada paso mientras avanza. Este tutorial construye el SDK por capas: una primera consulta en cada lenguaje, herramientas personalizadas, subagentes, hooks, límites de gasto y sesiones que se pueden retomar. Cada fragmento sigue la documentación actual, así que puedes pegarlo, definir una variable de entorno y ejecutarlo.
Qué hace el Agent SDK
Un agente es un programa que decide su propio siguiente paso. Lee una petición, elige una herramienta, observa el resultado y sigue hasta terminar el trabajo. El Agent SDK te entrega ese bucle ya hecho: las mismas herramientas integradas, el mismo sistema de permisos, el mismo manejo de contexto y los mismos hooks que funcionan dentro de Claude Code, expuestos como biblioteca para Python y TypeScript.
La diferencia práctica con llamar a la API directamente está en quién escribe el bucle. Con el Client SDK envías un mensaje, compruebas si Claude pidió una herramienta, la ejecutas, devuelves el resultado y repites. Con el Agent SDK llamas a query() una sola vez y recorres los mensajes que llegan en streaming mientras Claude hace el trabajo.
Cada vuelta de ese bucle sigue el mismo ritmo. Claude recibe el prompt y las definiciones de las herramientas, y decide si responde o llama a una herramienta. El SDK ejecuta la herramienta y devuelve el resultado, y el ciclo se repite hasta que Claude no tiene nada más que hacer o un límite lo detiene. Cada paso llega a tu código como un mensaje, por eso los ejemplos siguientes se parecen al procesamiento de un flujo y no a una única petición y respuesta. También significa que puedes mostrar el progreso a un usuario, registrar cada llamada a una herramienta o detenerlo antes de tiempo.
SDK, Client SDK o CLI
Son cuatro opciones que suenan parecido, así que esta tabla las ordena según quién ejecuta el agente.
Quieres
Usa
Qué obtienes
Integrar un agente en tu propia aplicación de Python o TypeScript
Agent SDK
El bucle de agente de Claude Code como biblioteca, con herramientas integradas, permisos, sesiones y hooks
Trabajar de forma interactiva desde una terminal
Claude Code CLI
Una interfaz de terminal pensada para el uso diario y tareas puntuales
Llamar a la API de Claude desde tu propio código
Client SDK
Acceso directo a la API, donde tú escribes el bucle de herramientas
Dejar que Anthropic aloje el agente
Managed Agents
Un entorno alojado que ejecuta el bucle en un sandbox gestionado
💡 Consejo: Para usar el mismo bucle desde otro lenguaje, ejecuta la CLI como subproceso con la marca -p y --output-format json.
Lo que necesitas antes
Antes de escribir la primera línea de código, deben estar listas tres cosas:
Python 3.10+ o Node.js 18+
Una cuenta de Anthropic con una credencial de API obtenida en la Claude Console
Una carpeta con el código sobre el que va a trabajar el agente, porque por defecto puede leer archivos en su directorio de trabajo y en los subdirectorios
Ambos paquetes incluyen un binario nativo de Claude Code, así que una instalación normal no necesita nada más. Hay dos situaciones que lo rompen: cuando pip recurre a la distribución de código fuente (el caso habitual es Windows ARM64) y cuando una instalación con npm omite las dependencias opcionales. En ambos casos, instala Claude Code de forma nativa y el SDK lo encontrará.
Instalar y autenticarse
Instalar el paquete
Para Python, crea un entorno virtual e instala el paquete:
Definir "type": "module" permite usar await de nivel superior en tu script, y tsx ejecuta archivos TypeScript sin necesidad de compilarlos antes.
Configurar tus credenciales
El SDK lee tu credencial desde una variable de entorno en la terminal que ejecuta el agente:
export ANTHROPIC_API_KEY=your-api-key
$env:ANTHROPIC_API_KEY = "your-api-key"
El SDK no carga por sí solo archivos .env. Si guardas la credencial en uno, cárgalo antes con python-dotenv o con el paquete dotenv. Los proveedores en la nube también funcionan: define CLAUDE_CODE_USE_BEDROCK=1 para Amazon Bedrock, CLAUDE_CODE_USE_VERTEX=1 para Google Cloud o CLAUDE_CODE_USE_FOUNDRY=1 para Microsoft Foundry, y luego configura las credenciales de ese proveedor.
💡 Nota de política: Salvo que Anthropic lo haya aprobado previamente, los productos de terceros creados con el SDK pueden no ofrecer el inicio de sesión con claude.ai ni sus límites de uso. Usa credenciales de API para cualquier cosa que publiques.
Tu primer agente en dos lenguajes
Crea un archivo llamado utils.py con dos errores intencionados. Una lista vacía hace fallar el promedio, y un usuario que no existe hace fallar la búsqueda del nombre:
def calculate_average(numbers):
total = 0
for num in numbers:
total += num
return total / len(numbers)
def get_user_name(user):
return user["name"].upper()
El agente de abajo solo puede leer. Puede revisar el archivo, pero no tiene permiso para cambiar nada.
La versión en Python
import asyncio
from claude_agent_sdk import (
query,
ClaudeAgentOptions,
AssistantMessage,
ResultMessage,
TextBlock,
)
async def main():
options = ClaudeAgentOptions(
system_prompt="You review Python code and report crash risks in plain language.",
allowed_tools=["Read", "Glob", "Grep"],
max_turns=8,
)
async for message in query(
prompt="Look at utils.py and list every input that would crash it.",
options=options,
):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(block.text)
elif isinstance(message, ResultMessage):
print(f"Finished: {message.subtype}, cost: ${message.total_cost_usd}")
asyncio.run(main())
Esto es lo que hace cada parte:
query() devuelve un iterador asíncrono, así que recorres los mensajes con async for mientras Claude piensa, llama a herramientas y lee resultados
allowed_tools aprueba de antemano Read, Glob y Grep, tres herramientas que pueden inspeccionar archivos pero nunca modificarlos
Los bloques AssistantMessage contienen el texto de Claude y sus llamadas a herramientas, así que filtrar por TextBlock te da una salida legible
ResultMessage llega al final, con subtype, total_cost_usd, num_turns y session_id
La versión en TypeScript
Guárdalo como agent.ts y ejecútalo con npx tsx agent.ts:
import { query } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({
prompt: "Look at utils.py and list every input that would crash it.",
options: {
systemPrompt: "You review Python code and report crash risks in plain language.",
allowedTools: ["Read", "Glob", "Grep"],
maxTurns: 8
}
})) {
if (message.type === "assistant" && message.message?.content) {
for (const block of message.message.content) {
if ("text" in block) console.log(block.text);
}
} else if (message.type === "result") {
console.log(`Finished: ${message.subtype}, cost: $${message.total_cost_usd}`);
}
}
Ejecuta cualquiera de los dos scripts y deberías ver cómo Claude lee utils.py, describe los dos fallos (la división por cero en una lista vacía y el TypeError en un usuario que no existe) y termina con una línea como Finished: success. Si ves un error de autenticación como Not logged in, la variable de entorno no está definida en la terminal que lanzó el script, que es el error más común en la primera ejecución.
La lógica es idéntica en ambos lenguajes, pero los nombres de las opciones cambian de formato. Ten esta tabla a mano, porque explica la mayoría de los momentos de "¿por qué no funciona?" cuando pasas código de un SDK al otro.
Ajuste
Python
TypeScript
Herramientas aprobadas de antemano
allowed_tools
allowedTools
Modo de permisos
permission_mode
permissionMode
Límite de turnos
max_turns
maxTurns
Límite de gasto
max_budget_usd
maxBudgetUsd
Servidores de herramientas personalizadas
mcp_servers
mcpServers
Retomar una sesión
resume
resume
Subagentes
agents
agents
Las listas de herramientas son la forma de subir o bajar la autonomía:
Herramientas
Lo que puede hacer el agente
Read, Glob, Grep
Revisar código sin cambiar nada
Read, Edit, Glob
Revisar código y modificarlo
Read, Edit, Bash, Glob, Grep
Ejecutar de principio a fin, incluidos los comandos de shell
💡 Dos comportamientos que debes esperar: Una llamada query() de un solo disparo lanza una excepción después de devolver un resultado de error, como alcanzar el límite de turnos, así que envuelve el bucle en try/except o try/catch cuando el script deba seguir ejecutándose. Y por defecto el SDK lee la carpeta .claude/ del proyecto y ~/.claude/, igual que la CLI, así que los ajustes, skills y hooks definidos allí también se aplican a tu agente.
Crear herramientas personalizadas
Las herramientas integradas se encargan de los archivos y del shell. Tu propia lógica, como una consulta a una base de datos o una llamada a una API interna, va en una herramienta personalizada: una función envuelta en un servidor MCP en proceso que se ejecuta dentro de tu aplicación, no como un proceso aparte.
Una herramienta tiene cuatro partes: un nombre, una descripción que Claude lee para decidir cuándo llamarla, un esquema de entrada y un handler asíncrono que devuelve un array content. Escribe la descripción como le explicarías el trabajo a un compañero nuevo.
Registras el servidor mediante mcp_servers (mcpServers en TypeScript). El nombre que das al servidor en ese diccionario forma parte del nombre completo de la herramienta, con el patrón mcp__{server}__{tool}. Incluye ese nombre completo en las herramientas permitidas y la llamada se ejecuta sin pedir permiso.
Una herramienta en Python
import asyncio
from typing import Any
from claude_agent_sdk import (
tool,
create_sdk_mcp_server,
query,
ClaudeAgentOptions,
ResultMessage,
)
ORDERS = {"A100": "shipped", "A101": "packing"}
@tool("get_order_status", "Look up the status of an order by its id", {"order_id": str})
async def get_order_status(args: dict[str, Any]) -> dict[str, Any]:
status = ORDERS.get(args["order_id"])
if status is None:
return {
"content": [{"type": "text", "text": f"No order {args['order_id']}"}],
"is_error": True,
}
return {"content": [{"type": "text", "text": f"Order {args['order_id']}: {status}"}]}
shop_server = create_sdk_mcp_server(
name="shop", version="1.0.0", tools=[get_order_status]
)
async def main():
options = ClaudeAgentOptions(
mcp_servers={"shop": shop_server},
allowed_tools=["mcp__shop__get_order_status"],
)
async for message in query(prompt="Where is order A100?", options=options):
if isinstance(message, ResultMessage) and message.subtype == "success":
print(message.result)
asyncio.run(main())
Una herramienta en TypeScript
TypeScript describe las entradas de las herramientas con Zod, así que instala npm install zod primero:
import { query, tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";
const orders: Record<string, string> = { A100: "shipped", A101: "packing" };
const getOrderStatus = tool(
"get_order_status",
"Look up the status of an order by its id",
{ order_id: z.string().describe("Order id such as A100") },
async (args) => {
const status = orders[args.order_id];
if (!status) {
return {
content: [{ type: "text", text: `No order ${args.order_id}` }],
isError: true
};
}
return { content: [{ type: "text", text: `Order ${args.order_id}: ${status}` }] };
}
);
const shopServer = createSdkMcpServer({
name: "shop",
version: "1.0.0",
tools: [getOrderStatus]
});
for await (const message of query({
prompt: "Where is order A100?",
options: {
mcpServers: { shop: shopServer },
allowedTools: ["mcp__shop__get_order_status"]
}
})) {
if (message.type === "result" && message.subtype === "success") {
console.log(message.result);
}
}
💡 Consejo: Para fallos previsibles, como un id de pedido que no existe, devuelve is_error: True (isError: true en TypeScript) con tu propio mensaje. Claude lee ese mensaje y puede reintentar o explicarlo, en lugar de recibir una cadena de excepción sin contexto.
Subagentes, hooks y límites
Un agente que funciona es un buen comienzo. Tres funciones mantienen predecible a uno más grande: los subagentes reparten el trabajo, los hooks vigilan cada llamada a una herramienta y los límites detienen una sesión descontrolada.
Delegar en subagentes
Un subagente es una instancia de agente independiente que tu agente principal crea para una tarea concreta. Cada uno empieza con un contexto nuevo, así que un revisor puede leer decenas de archivos sin inflar la conversación principal, y solo su mensaje final vuelve al agente padre. Los defines con la opción agents y añades Agent a las herramientas permitidas.
Cada definición necesita un description (cuándo debe usarlo Claude) y un prompt (su comportamiento). Entre los campos opcionales están tools, para restringir lo que puede tocar, y model, que acepta los alias sonnet, opus, haiku, fable y inherit, o un ID completo de modelo.
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition
async def main():
options = ClaudeAgentOptions(
allowed_tools=["Read", "Grep", "Glob", "Agent"],
agents={
"code-reviewer": AgentDefinition(
description="Reviews code for security and maintainability problems.",
prompt="You are a careful reviewer. Report concrete problems with file names.",
tools=["Read", "Grep", "Glob"],
model="sonnet",
),
"test-runner": AgentDefinition(
description="Runs the test suite and summarizes failures.",
prompt="Run the tests, then list each failing test with its error.",
tools=["Bash", "Read", "Grep"],
),
},
)
async for message in query(
prompt="Use the code-reviewer agent to check the auth module",
options=options,
):
if hasattr(message, "result"):
print(message.result)
asyncio.run(main())
La versión en TypeScript usa objetos simples:
import { query } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({
prompt: "Use the code-reviewer agent to check the auth module",
options: {
allowedTools: ["Read", "Grep", "Glob", "Agent"],
agents: {
"code-reviewer": {
description: "Reviews code for security and maintainability problems.",
prompt: "You are a careful reviewer. Report concrete problems with file names.",
tools: ["Read", "Grep", "Glob"],
model: "sonnet"
}
}
}
})) {
if ("result" in message) console.log(message.result);
}
Nombrar al subagente en tu prompt, como en el ejemplo anterior, garantiza que Claude lo use. Si no lo haces, Claude compara la tarea con cada description, así que las descripciones vagas significan delegaciones perdidas.
💡 Nota de versión: La herramienta aparece como Agent en los bloques de uso de herramientas. Las versiones anteriores la llamaban Task, y la lista de herramientas del mensaje de inicio sigue usando ese nombre, así que compara ambos nombres cuando detectes llamadas a subagentes en el flujo de mensajes.
Bloquear llamadas arriesgadas con hooks
Los hooks son callbacks que se ejecutan en puntos fijos del bucle del agente. El más útil es PreToolUse, que se dispara antes de que se ejecute una herramienta y puede denegarla. Un matcher filtra por nombre de herramienta, por ejemplo Bash o Write|Edit. Devuelve {} para permitir la llamada.
Este hook de Python rechaza cualquier comando de shell que contenga un borrado recursivo:
import asyncio
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, HookMatcher
async def block_destructive_bash(input_data, tool_use_id, context):
command = input_data["tool_input"].get("command", "")
if "rm -rf" in command:
return {
"hookSpecificOutput": {
"hookEventName": input_data["hook_event_name"],
"permissionDecision": "deny",
"permissionDecisionReason": "Recursive deletes are blocked",
}
}
return {}
async def main():
options = ClaudeAgentOptions(
allowed_tools=["Bash", "Read"],
hooks={
"PreToolUse": [HookMatcher(matcher="Bash", hooks=[block_destructive_bash])]
},
)
async with ClaudeSDKClient(options=options) as client:
await client.query("Clean up the build folder")
async for message in client.receive_response():
print(message)
asyncio.run(main())
Y la misma protección en TypeScript:
import { query, HookCallback, PreToolUseHookInput } from "@anthropic-ai/claude-agent-sdk";
const blockDestructiveBash: HookCallback = async (input) => {
const pre = input as PreToolUseHookInput;
const toolInput = pre.tool_input as Record<string, unknown>;
const command = String(toolInput?.command ?? "");
if (command.includes("rm -rf")) {
return {
hookSpecificOutput: {
hookEventName: pre.hook_event_name,
permissionDecision: "deny",
permissionDecisionReason: "Recursive deletes are blocked"
}
};
}
return {};
};
for await (const message of query({
prompt: "Clean up the build folder",
options: {
allowedTools: ["Bash", "Read"],
hooks: { PreToolUse: [{ matcher: "Bash", hooks: [blockDestructiveBash] }] }
}
})) {
if (message.type === "result") console.log(message.subtype);
}
Cuando coinciden varios hooks, se ejecutan en paralelo y gana la respuesta más restrictiva. Un solo deny bloquea la llamada sin importar lo que devuelvan los demás, así que escribe cada hook para que funcione por sí mismo.
Limitar turnos y gasto
Los modos de permisos fijan el nivel de confianza por defecto. Elige uno con permission_mode o permissionMode:
Modo
Comportamiento
default
Comportamiento estándar: las herramientas no aprobadas pasan por el flujo de permisos
acceptEdits
Las ediciones de archivos se aprueban automáticamente
plan
Solo planificación: el agente investiga sin editar
dontAsk
Todo lo que no esté aprobado de antemano se deniega
bypassPermissions
Se omiten las comprobaciones de permisos, úsalo con mucho cuidado
auto
Un clasificador de modelo revisa cada acción
Dos límites numéricos protegen tu presupuesto. max_turns (maxTurns) limita los turnos del agente, y max_budget_usd (maxBudgetUsd) limita el gasto estimado. Al alcanzarlos, la consulta termina con los subtipos de resultado error_max_turns o error_max_budget_usd. Las peticiones de los subagentes cuentan para el mismo total_cost_usd, así que un prompt que se reparte en muchos subagentes sigue sujeto al mismo tope.
💡 Consejo para trabajos desatendidos: Combina dontAsk con una lista explícita de herramientas permitidas. Lo que quede fuera de la lista se deniega de inmediato, en lugar de esperar a una persona que no está presente.
Tres errores aparecen una y otra vez en los primeros agentes:
Conceder Bash demasiado pronto. Empieza con herramientas de solo lectura y añade Edit o Bash solo cuando la tarea lo exija.
Descripciones vagas de subagentes. Claude delega según el texto de description, así que "agente auxiliar" se ignora, mientras que "Revisa el código en busca de problemas de seguridad" sí se usa.
Ningún límite en la primera ejecución. Define max_turns y max_budget_usd antes de apuntar el agente a un repositorio grande.
Mantener el contexto entre sesiones
Cada llamada a query() inicia una sesión nueva. Cuando una pregunta de seguimiento debe recordar lo que el agente ya leyó y decidió, necesitas una forma de dar continuidad a la sesión.
Varios turnos en Python
ClaudeSDKClient lleva el control de la sesión por ti. Cada client.query() continúa la misma conversación:
import asyncio
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions
async def main():
options = ClaudeAgentOptions(allowed_tools=["Read", "Edit", "Glob", "Grep"])
async with ClaudeSDKClient(options=options) as client:
await client.query("Inspect the auth module")
async for message in client.receive_response():
print(message)
# Same session, so the agent remembers the first answer
await client.query("Now refactor it to use JWT")
async for message in client.receive_response():
print(message)
asyncio.run(main())
Retomar en TypeScript
TypeScript no tiene un objeto cliente, así que capturas el session_id del mensaje de resultado y lo devuelves mediante resume:
import { query } from "@anthropic-ai/claude-agent-sdk";
let sessionId: string | undefined;
for await (const message of query({
prompt: "Inspect the auth module",
options: { allowedTools: ["Read", "Glob", "Grep"] }
})) {
if (message.type === "result") sessionId = message.session_id;
}
for await (const message of query({
prompt: "Now propose a refactor based on what you found",
options: { resume: sessionId, allowedTools: ["Read", "Glob", "Grep"] }
})) {
if (message.type === "result" && message.subtype === "success") {
console.log(message.result);
}
}
Python acepta la misma idea como resume=session_id. Ambos lenguajes también admiten continue_conversation=True / continue: true para retomar la sesión más reciente de la carpeta, y fork_session=True / forkSession: true para bifurcar una copia del historial y probar otro enfoque sin perder el original.
Hay dos límites que importan en la práctica. Las sesiones guardan la conversación, no tus archivos, así que un agente bifurcado que edita código sí cambia los archivos reales. Y los archivos de sesión viven en la máquina que los creó, así que retomarla en otro equipo requiere un adaptador de almacenamiento de sesiones o una copia de la transcripción.
El Agent SDK se ejecuta en tu equipo con tus propias credenciales, así que PicassoIA no lo sustituye. Donde PicassoIA ayuda es en la fase de redacción. La formulación de un system prompt, de la descripción de un subagente o de la descripción de una herramienta determina lo bien que se comporta un agente, y probar la redacción en un chat es más rápido que volver a ejecutar un script diez veces.
Redacta los prompts antes de programar
Claude Sonnet 5 está diseñado para tareas de programación de varios pasos y uso de herramientas, lo que lo convierte en un buen sustituto para ensayar las instrucciones del agente. Sigue estos pasos:
Pega tu borrador de instrucciones del agente en el campo System Prompt.
Escribe una tarea realista en Prompt, por ejemplo el contenido de utils.py seguido de "enumera cada entrada que pudiera hacerlo fallar".
Ajusta effort. El valor por defecto es low, que desactiva el razonamiento para obtener la respuesta más rápida y barata. Súbelo a high o max para errores que abarquen varios archivos.
Deja max_tokens en el valor por defecto de 8192, suficiente para código o texto detallado en una sola respuesta.
Opcionalmente, adjunta una captura de pantalla de un error mediante la entrada image, ya que el modelo puede leerla.
Ejecútalo, ajusta la redacción hasta que la respuesta coincida con lo que quieres y luego copia el texto final en system_prompt o en el prompt de un subagente.
💡 Recuerda: Esto solo ensaya la redacción. Las herramientas de archivos, los hooks y el bucle del agente siguen viniendo del SDK en tu equipo.
Elige el modelo de Claude adecuado
PicassoIA ofrece varios modelos de Claude, y cada uno sirve para una tarea de redacción distinta:
En el propio SDK, el campo model acepta los alias descritos antes, así que puedes usar un modelo económico para los subagentes rutinarios y uno más potente para el hilo principal.
Crea tus propias imágenes con PicassoIA
Ahora tienes un agente funcional y el hábito de probar cada pieza antes de publicarla. Ese mismo hábito mejora la generación de imágenes, y PicassoIA es un lugar rápido para practicarlo.
Abre Seedream 4.5 o GPT Image 2 y escribe un prompt con la misma estructura que usamos para las fotos de este artículo: el sujeto y su acción, el escenario, la dirección de la luz, el objetivo y una o dos texturas de superficie. Una línea como "manos escribiendo en un escritorio de madera, luz suave de ventana desde la derecha, objetivo de 85 mm, profundidad de campo reducida, polvo visible en la veta de la madera" le da al modelo mucho más con lo que trabajar que "un programador escribiendo".
Pruébalo en la próxima cabecera del blog, foto de producto o banner de documentación que necesites. Cambia un detalle en cada ejecución, compara los resultados uno al lado del otro y guarda el prompt que funcione. Cuando quieras ver qué más hay disponible, explora todos los modelos en picassoia.com/en/all-models y empieza tu primera generación hoy mismo.