Claude Skills Avanzados: Cómo Construir Custom Agents con Herramientas Reales
Aprende a construir claude skills custom agents con contratos explícitos, composición de skills y error handling real. Arquitectura que escala en producción.
Tu Claude Agent Tiene Skills. Pero Probablemente los Está Usando Mal
Un skill genérico que hace «todo lo relacionado con el usuario» no es un skill. Es un monolito disfrazado de agente.
*El problema real no está en Claude. Está en cómo defines las unidades de responsabilidad.*
La mayoría de developers que construyen claude skills custom agents copian el ejemplo de la documentación, añaden tres tools más, y llaman al resultado «agente en producción».
Eso no es un agente. Es un prompt con switch/case.
Este artículo te muestra la arquitectura que separa los custom agents que escalan de los que explotan en la primera semana.
---
El Error Conceptual que Arruina el 90% de los Custom Agents
La creencia común: un skill es simplemente una función que Claude puede llamar.
La realidad: un skill es un contrato de comportamiento con inputs tipados, outputs predecibles, y manejo de errores explícito.
Si tu skill no tiene esas tres cosas, no tienes un skill. Tienes un callback.
❌ Enfoque habitual:
```typescript
const tools = [
{
name: "do_user_stuff",
description: "Does things with users",
input_schema: {
type: "object",
properties: {
data: { type: "string" }
}
}
}
];
```
✅ Enfoque correcto — skill con contrato explícito:
```typescript
const getUserProfileSkill = {
name: "get_user_profile",
description: [
"Retrieves a verified user profile by ID.",
"Returns: { id, email, plan, createdAt }.",
"Fails with NOT_FOUND if user does not exist.",
"Fails with INVALID_ID if format is wrong."
].join(" "),
input_schema: {
type: "object",
required: ["user_id"],
properties: {
user_id: {
type: "string",
pattern: "^usr_[a-zA-Z0-9]{12}$",
description: "User ID with prefix 'usr_' followed by 12 alphanumeric characters"
}
}
}
};
```
La diferencia no es estética. Claude usa la descripción para decidir cuándo y cómo invocar el skill. Un contrato vago produce invocaciones incorrectas. Un contrato preciso produce comportamiento predecible.
---
Arquitectura de Skills en Capas: La Estructura que Funciona
Los claude skills custom agents bien construidos tienen tres capas:
→ Capa de interfaz: El tool definition que ve Claude
↳ Capa de ejecución: La función real que corre en tu servidor
↳ Capa de contrato: El schema de validación de inputs y outputs
Si fusionas las tres capas en una sola función, tienes un sistema que nadie puede depurar a las 2 de la mañana.
1. Define el Tool Definition Separado de la Lógica
```typescript
// skills/definitions/search-products.ts
export const searchProductsDefinition = {
name: "search_products",
description: [
"Searches the product catalog using semantic similarity.",
"Use this when the user wants to find, browse, or discover products.",
"Do NOT use for order status or inventory checks — use different skills for those.",
"Returns up to 10 results ordered by relevance score."
].join(" "),
input_schema: {
type: "object",
required: ["query"],
properties: {
query: {
type: "string",
minLength: 2,
maxLength: 200,
description: "Natural language search query from the user"
},
category_filter: {
type: "string",
enum: ["electronics", "clothing", "home", "sports"],
description: "Optional category to restrict search scope"
},
limit: {
type: "number",
minimum: 1,
maximum: 10,
default: 5
}
}
}
};
```
2. Implementa la Ejecución con Error Handling Explícito
```typescript
// skills/handlers/search-products.ts
import { z } from "zod";
const SearchProductsInput = z.object({
query: z.string().min(2).max(200),
category_filter: z.enum(["electronics", "clothing", "home", "sports"]).optional(),
limit: z.number().min(1).max(10).default(5)
});
export type SkillResult =
| { success: true; data: unknown }
| { success: false; error: string; code: string };
export async function searchProductsHandler(
rawInput: unknown
): Promise<SkillResult> {
const parsed = SearchProductsInput.safeParse(rawInput);
if (!parsed.success) {
return {
success: false,
error: "Invalid input parameters",
code: "VALIDATION_ERROR"
};
}
try {
const results = await vectorSearch({
query: parsed.data.query,
category: parsed.data.category_filter,
topK: parsed.data.limit
});
return { success: true, data: results };
} catch (err) {
return {
success: false,
error: "Search service unavailable",
code: "SERVICE_ERROR"
};
}
}
```
Este patrón tiene un beneficio crítico: Claude recibe siempre una respuesta estructurada, tanto en éxito como en error. Nunca recibe una excepción sin formato que interrumpe el flujo del agente.
---
Composición de Skills: El Patrón que Nadie Documenta
Un custom agent potente no tiene 20 skills independientes. Tiene skills orquestados en pipelines.
El truco: algunos skills son «primarios» (Claude los llama directamente) y otros son «internos» (solo los llaman otros skills).
```typescript
// Skill primario: Claude lo ve y lo invoca
export const analyzeUserIntentDefinition = {
name: "analyze_and_respond",
description: "Main entry point. Understands user intent and coordinates the response pipeline.",
// ...
};
// Handler del skill primario — orquesta skills internos
export async function analyzeAndRespondHandler(input: unknown): Promise<SkillResult> {
// 1. Clasificar intent
const intent = await classifyIntent(input);
// 2. Según intent, ejecutar pipeline específico
if (intent === "product_search") {
const searchResult = await searchProductsHandler({ query: input });
const enriched = await enrichWithInventory(searchResult);
return { success: true, data: enriched };
}
if (intent === "order_status") {
return await getOrderStatusHandler(input);
}
return { success: false, error: "Unknown intent", code: "UNKNOWN_INTENT" };
}
```
Este patrón reduce el número de tool calls de Claude a la mitad. Menos tool calls significa menos latencia y menos tokens consumidos.
---
El Registro de Skills: Cómo Montar el Agente Final
Una vez tienes tus skills definidos y sus handlers separados, necesitas un registro centralizado.
```typescript
// agent/skill-registry.ts
import { searchProductsDefinition, searchProductsHandler } from "../skills";
import { analyzeAndRespondDefinition, analyzeAndRespondHandler } from "../skills";
import { getUserProfileDefinition, getUserProfileHandler } from "../skills";
type SkillRegistry = {
[key: string]: (input: unknown) => Promise<SkillResult>;
};
export const toolDefinitions = [
searchProductsDefinition,
analyzeAndRespondDefinition,
getUserProfileDefinition
];
export const skillHandlers: SkillRegistry = {
search_products: searchProductsHandler,
analyze_and_respond: analyzeAndRespondHandler,
get_user_profile: getUserProfileHandler
};
// El dispatcher: una función, cero switch/case
export async function dispatchSkill(
toolName: string,
toolInput: unknown
): Promise<SkillResult> {
const handler = skillHandlers[toolName];
if (!handler) {
return { success: false, error: `Unknown skill: ${toolName}`, code: "SKILL_NOT_FOUND" };
}
return handler(toolInput);
}
```
Este registry hace que añadir un nuevo skill sea un cambio de tres líneas, no una refactorización.
---
El Loop del Agente: Conectando Todo con la API de Claude
```typescript
// agent/run-agent.ts
import Anthropic from "@anthropic-ai/sdk";
import { toolDefinitions, dispatchSkill } from "./skill-registry";
const client = new Anthropic();
export async function runAgent(userMessage: string): Promise<string> {
const messages: Anthropic.MessageParam[] = [
{ role: "user", content: userMessage }
];
while (true) {
const response = await client.messages.create({
model: "claude-opus-4-5",
max_tokens: 4096,
tools: toolDefinitions,
messages
});
if (response.stop_reason === "end_turn") {
const textBlock = response.content.find(b => b.type === "text");
return textBlock?.text ?? "No response generated";
}
if (response.stop_reason === "tool_use") {
const toolResults: Anthropic.ToolResultBlockParam[] = [];
for (const block of response.content) {
if (block.type !== "tool_use") continue;
const result = await dispatchSkill(block.name, block.input);
toolResults.push({
type: "tool_result",
tool_use_id: block.id,
content: JSON.stringify(result),
is_error: !result.success
});
}
messages.push({ role: "assistant", content: response.content });
messages.push({ role: "user", content: toolResults });
continue;
}
break;
}
return "Agent loop ended unexpectedly";
}
```
Fíjate en `is_error: !result.success`. Ese campo le indica a Claude que el skill falló. Sin esa señal, Claude asume que el resultado es válido y construye respuestas incorrectas encima de datos rotos.
---
Lo que Más Falla en Producción: Tres Patrones Críticos
Skill descriptions demasiado ambiguas
Si dos skills tienen descripciones similares, Claude elegirá el incorrecto en el 30-40% de los casos. Especifica explícitamente cuándo NO usar cada skill.
No limitar el número de iteraciones del loop
Un agente sin límite de iteraciones puede entrar en un bucle infinito consumiendo tokens sin parar. Añade siempre un contador máximo:
```typescript
const MAX_ITERATIONS = 10;
let iterations = 0;
while (iterations < MAX_ITERATIONS) {
iterations++;
// ... resto del loop
}
```
Devolver objetos complejos sin serializar
Claude procesa el contenido del tool_result como texto. Un objeto anidado de 50 campos confunde al modelo. Devuelve solo los campos que el agente necesita para continuar.
---
Resumen: Los Principios que Separan los Skills que Escalan
→ Un skill = un contrato: inputs tipados, outputs predecibles, errores explícitos
↳ Separa la definición del handler: el tool definition es documentación para Claude, el handler es código para tu servidor
↳ Usa un registry centralizado: elimina los switch/case y hace el sistema extensible
↳ Señaliza los errores con `is_error`: Claude necesita saber cuándo un skill falló para replantear su estrategia
↳ Limita las iteraciones: un agente sin techo de iteraciones no está en producción, está en modo debug permanente
Los claude skills custom agents que sobreviven producción no son más complejos. Son más precisos en sus contratos y más honestos con sus errores.
El siguiente nivel es la orquestación multi-skill con memoria persistente entre sesiones. Pero eso solo funciona si primero tienes los skills bien construidos como unidades atómicas.
Lee el artículo completo en brianmenagomez.com
Más sobre mis servicios en brianmenagomez.com
Herramientas: Conversor IAE CNAE · Gestorias cerca de ti · Calculadora IRPF

