Cómo hacer que un skill de Claude no cambie de resultado cada vez
Cinco controles de carpeta, del input a los tests, para que un skill de Claude Code produzca el mismo resultado en cada ejecución.
La mayoría de la gente trata un skill como un archivo markdown bien escrito, un prompt con buenas instrucciones. Pero un skill es una carpeta, y esa carpeta te da control sobre cinco partes distintas de la ejecución. Úsalas y el resultado deja de depender del día en que lo lances.
Lo vemos con un caso real, un skill llamado ai-news pensado para encontrar y resumir noticias de IA. Lo seguimos desde su primera versión, un único archivo que “decide” todo de nuevo en cada ejecución, hasta convertirlo en un sistema casi determinista.
No hay ningún truco de por medio. Solo cinco decisiones pequeñas sobre dónde vive cada tipo de trabajo.
Un archivo markdown no es un sistema
Así es como se suele escribir un skill la primera vez. Una carpeta, un archivo. Le dice a Claude qué queremos, y nada sobre cómo.
.claude/skills/
└── ai-news/
└── SKILL.md ← un solo archivo, solo instrucciones
---
name: ai-news
description: Encuentra las 50 noticias de IA
más relevantes de los últimos 7 días.
---
# AI News
Encuentra las 50 noticias de IA más relevantes de
los últimos 7 días. Resume cada una y presenta el
resultado como un informe limpio.
Cada hueco en el “cómo” es una decisión que Claude vuelve a tomar, de forma distinta, en cada ejecución. El mismo skill lanzado el lunes, el viernes, o por otra persona del equipo, produce tres informes distintos. Claude no se equivoca, simplemente la ejecución nunca quedó fijada.
Merece la pena poner las dos versiones del mismo skill una al lado de la otra, la que cambia en cada ejecución y la que ya tiene los cinco controles puestos.
El desorden:
- Las fuentes cambian en cada ejecución: a veces un medio de referencia, a veces un blog de SEO cualquiera.
- El método cambia: búsqueda web una vez, un script scraper la siguiente, un MCP la de después.
- La forma del resultado cambia: una tabla, luego prosa, luego una lista de puntos.
- Es lento: las fuentes se recorren una detrás de otra.
- No hay forma de saber si una noticia es real, reciente o inventada.
El sistema:
- Un conjunto de fuentes fijo y verificado, decidido una vez.
- Un único proceso, capturado en scripts, que se repite en cada ejecución.
- Una sola plantilla de salida: el informe se ve igual semana tras semana.
- Las fuentes se recorren en paralelo: la ejecución dura lo que tarda la más lenta.
- Cada noticia se comprueba en precisión, relevancia y actualidad antes de publicarse.
El salto de la primera lista a la segunda es de lo que trata todo esto. Cada uno de los cinco controles elimina una fuente de variación, decidida una vez y aplicada en cada ejecución posterior. El prompt más largo no fija nada por sí solo. Lo que fija la ejecución es darle a Claude un artefacto que seguir en vez de una decisión que tomar.
Los cinco controles
Una carpeta de skill te deja controlar cinco partes distintas de una ejecución. Cuatro de ellas aumentan el determinismo, es decir, cuánto se repite el resultado. Una, la velocidad, aumenta la eficiencia.
Cada control es un artefacto dentro de una subcarpeta. Ninguno de esos nombres es especial para Claude Code, el único archivo que el cargador de skills lee automáticamente es SKILL.md. El resto de carpetas se leen porque SKILL.md le dice a Claude que las lea.
Este es el orden habitual, de la más barata a la más laboriosa:
- Input ·
references/: decide de dónde leer, una vez. Mundo cerrado, sin fuentes sorpresa. - Proceso ·
scripts/: captura el método como código, para que no se reinvente en cada ejecución. - Output ·
assets/: dale a Claude una plantilla literal que rellenar, no un diseño que inventar. - Velocidad ·
agents/: reparte trabajo independiente entre subagentes en paralelo y mantén limpio el contexto principal. - Tests ·
tests/: evalúa el fondo con un juez fresco (LLM como juez) contra una rúbrica escrita.
Casi nunca hace falta usar los cinco a la vez. La regla es añadir un control para arreglar un fallo que estés viendo de verdad, no de forma preventiva. El skill ai-news solo se ganó los cinco porque cada uno cerraba un hueco real y observado.
Los nombres references/, scripts/, assets/, agents/ y tests/ son convenciones, las mismas que usa el skill-creator oficial de Anthropic, así que merece la pena seguirlas. Pero podrías renombrar cualquiera, siempre que lo conectes desde SKILL.md.
Control 1: el input, con references/
La primera causa de variación es también la más barata de arreglar. El skill nunca dice dónde mirar, así que Claude elige las fuentes él solo, y elige distinto cada vez.
Una semana son medios de calidad, la siguiente un blog de marketing que ese día posicionó bien. La cobertura no se puede repetir, y una fuente sin verificar es exactamente por dónde se cuela una noticia inventada.
La solución es decidir las fuentes una vez. Pones un archivo markdown por fuente en references/sources/, y haces que SKILL.md diga: lee todos los archivos de esa carpeta, y usa solo esas fuentes. La ejecución pasa a operar en un mundo cerrado que tú definiste.
references/ es el sitio habitual para el material que Claude debe leer antes o durante una ejecución: frameworks, esquemas, listas blancas. Claude no escanea la carpeta por arte de magia, el control ocurre en SKILL.md, que apunta a la carpeta. Los archivos de referencia solo guardan el detalle para que la instrucción se quede corta.
.claude/skills/ai-news/
├── SKILL.md
└── references/
└── sources/
├── techcrunch-ai.md
├── the-verge-ai.md
├── arxiv-cs-ai.md
├── hacker-news.md
└── anthropic-blog.md
Cada fuente tiene un archivo markdown que describe cómo leerla. Trátalo como una pequeña especificación, con detalle suficiente para que alguien nuevo (o Claude) pueda cogerla en frío:
# Fuente: TechCrunch : AI
url: techcrunch.com/category/artificial-intelligence
idioma: en
paginas_a_scrapear: 3
selector_lista: article.post-block
seguir_al_articulo: si
ignorar: posts patrocinados, promos de eventos,
bloques de suscripción a newsletter
actualidad: cada tarjeta muestra una fecha relativa,
quedarse solo con items de <= 7 días
navegacion: paginar con ?page=N
confianza: medio tier-1, reporting primario
Y así se conecta en SKILL.md:
## Paso 1 : Cargar las fuentes
Lee todos los archivos de references/sources/.
Cada archivo define una fuente aprobada y cómo leerla.
Usa SOLO estas fuentes. No recurras a búsqueda web
abierta. No añadas una fuente que no esté en esta
carpeta. Si una fuente no está disponible, márcala
como omitida, no la sustituyas por otra.
La línea de “no la sustituyas” hace un trabajo importante. Sin ella, Claude arreglaría por su cuenta una fuente caída buscando algo parecido, y tu lista blanca dejaría de serlo en silencio.
El mundo cerrado gana en repetibilidad y también en confianza. Cuando la lista de fuentes es fija, Claude no puede acabar en una página de baja calidad o suplantada. Añadir una fuente pasa a ser una edición deliberada que revisas, no una suposición que se toma sola durante la ejecución.
Control 2: el proceso, con scripts/
Las fuentes ya están fijas, pero el método no. Claude reinventa cómo obtenerlas en cada ejecución, lo que cuesta tokens y cambia la cobertura.
Sin scripts, la misma tarea se resuelve de forma distinta cada vez: búsqueda web pura una ejecución, un scraper escrito a mano la siguiente, un MCP la de después. Método distinto significa errores distintos, cobertura distinta, y muchos tokens gastados en razonar otra vez el mismo “cómo”.
Aquí el arreglo es meter los scripts en scripts/ y hacer que SKILL.md le diga a Claude que los ejecute, en vez de improvisar. Generas los scripts una vez, con ayuda de Claude, los revisas, y a partir de ahí el skill se limita a ejecutarlos.
No todo puede ser un script. Algunos pasos son, por naturaleza, llamadas a una herramienta o un MCP, no hay código que escribir. El patrón es usar dos herramientas en paralelo:
- Scripts, para trabajo determinista. Obtener datos, deduplicar, rankear, validar: cualquier cosa con una respuesta correcta. Un script se ejecuta igual siempre y apenas cuesta tokens “pensarlo”.
- Pasos en texto plano, para trabajo de herramienta o MCP. Cuando el paso es “llama a este MCP” o “usa esta herramienta”, no se puede scriptear. Así que nombras la herramienta exacta y sus argumentos en
SKILL.mdpara que Claude no elija otra.
.claude/skills/ai-news/
├── SKILL.md
├── references/sources/ ← control 1
└── scripts/
├── fetch_source.py ← coge un archivo de fuente, la scrapea, emite JSON normalizado
├── dedupe.py ← agrupa la misma noticia entre fuentes
├── rank.py ← puntúa y se queda con el top 50
└── validate.py ← comprueba la forma: 50 items, fechas dentro del rango, URLs presentes
Antes, SKILL.md deja el método abierto. Claude tiene que decidir cómo hacerlo todo:
# vago : se decide de nuevo cada vez
Recorre las fuentes y recopila las noticias.
Elimina duplicados y quédate con las 50
más relevantes.
Después, el método queda nombrado explícitamente:
# fijo : mismo camino cada vez
Para cada archivo de fuente, ejecuta:
python scripts/fetch_source.py <archivo>
Después, sobre los resultados combinados:
python scripts/dedupe.py
python scripts/rank.py --top 50
python scripts/validate.py
Y un detalle curioso, la segunda versión es también más corta. Ser específico acaba reduciendo el prompt, no inflándolo.
Cuando Claude improvisa un proceso, razona cada paso dentro de la ventana de contexto, tokens gastados en pensar y más tokens gastados en hacer. Un script reduce eso a una línea, ejecutar y leer el resultado. Lo determinista no cuesta nada “pensarlo” porque ya se pensó una vez, al construir el skill.
Conviene dejar una vía de escape. El determinismo no debería significar fragilidad. Dile al skill que si un script falla o un sitio ha cambiado, Claude puede improvisar para recuperarse, y debe reportarlo. Luego actualizas el script. El script es el camino por defecto, pero no el único posible.
Control 3: el output, con assets/
Un HTML con marcadores {{SLOT}}, guardado en assets/output-template.html. Eso es todo el control 3, y arregla algo muy concreto: con las fuentes y el proceso ya fijos, el informe seguía saliendo distinto cada semana, una tabla HTML, luego prosa, luego una lista de puntos. Los datos eran estables, la forma no.
assets/ es el sitio habitual para archivos que un skill incluye pero sobre los que no razona como prosa: plantillas, logos, imágenes fijas, tipografías (aquí también viven la versión CSV y el esquema JSON del entregable). La diferencia que marca una plantilla es la de “describe el informe que quieres” frente a “rellena este informe exacto”.
.claude/skills/ai-news/
├── SKILL.md
├── references/sources/ ← control 1
├── scripts/ ← control 2
└── assets/
├── output-template.html ← el esqueleto del informe, el entregable
├── output-template.csv ← exportación plana, una fila por noticia
├── story.json ← el esquema que debe cumplir cada noticia
└── webprofits-logo.svg ← activo de marca fijo, se inserta sin cambios
La plantilla es solo HTML con marcadores {{SLOT}}, nada elaborado. Lo importante es que la estructura ya está decidida:
<!-- cabecera: no editar -->
<h1>AI News : semana del {{WEEK}}</h1>
<p class="meta">{{COUNT}} noticias ·
{{SOURCE_COUNT}} fuentes</p>
<!-- repetir este bloque por noticia -->
<article>
<span class="tag">{{CATEGORY}}</span>
<h3>{{HEADLINE}}</h3>
<p>{{SUMMARY}}</p>
<a href="{{URL}}">{{SOURCE}}</a>
</article>
En SKILL.md, el paso 4 queda así:
## Paso 4 : Renderizar el output
Carga assets/output-template.html.
Rellena cada {{SLOT}} con las noticias
ordenadas. Repite el bloque <article> una
vez por noticia, en orden de ranking.
No cambies la estructura, los encabezados,
las clases ni el estilo. La plantilla ES
el diseño, tú solo aportas el contenido.
Los huecos siguen conteniendo texto generado, la redacción de un resumen varía en cada ejecución, y eso es normal y esperable. Lo que la plantilla fija es todo lo que rodea al texto: estructura, orden, secciones, estilo. El contenedor queda fijo, y solo el contenido de dentro varía.
Control 4: la velocidad, con agents/
El skill hace ya bastante, y lo hace en fila. Recorrer cinco fuentes una detrás de otra es cinco veces más lento de lo necesario. Y cada página en bruto se apila en el contexto principal, que se encarece y pierde precisión a medida que se llena.
El arreglo es repartir el trabajo independiente entre subagentes y ejecutarlos en paralelo. El hilo principal pasa a ser un orquestador, despacha un subagente por fuente, todos corren a la vez, y solo vuelven sus resultados limpios.
Un subagente corre en su propia ventana de contexto, separada. Hace su trabajo (scrapear una fuente, decenas de llamadas a herramientas) y devuelve solo el resultado al hilo principal, no toda la transcripción de cómo llegó a él. El tiempo total del paso baja a la duración de la fuente más lenta, y el hilo principal se mantiene limpio porque el scraping en bruto nunca lo toca.
hilo principal (orquestador)
│
despacha en paralelo
│
┌─────────┬─────────┼─────────┬─────────┐
▼ ▼ ▼ ▼ ▼
scraper scraper scraper scraper scraper
TechCrunch Verge arXiv HN Anthropic
│ │ │ │ │
└─────────┴─────────┴─────────┴─────────┘
│
5 arrays JSON
▼
dedupe → rank → render
.claude/skills/ai-news/
├── SKILL.md ← el orquestador
├── references/sources/ ← control 1
├── scripts/ ← control 2
├── assets/ ← control 3
└── agents/
└── source-scraper.md ← definición de un worker, reutilizado por fuente
Un archivo define el subagente scraper. El frontmatter fija el modelo (Haiku va sobrado para scrapear) y las herramientas que puede usar. El cuerpo es corto a propósito: el worker hace una cosa y devuelve solo el resultado, sin comentario adicional:
---
name: source-scraper
description: Scrapea UNA fuente de ai-news y
devuelve JSON normalizado.
model: haiku
tools: Bash, Read, WebFetch
---
Recibes UN archivo de fuente desde
references/sources/.
Ejecuta scripts/fetch_source.py contra él.
Devuelve SOLO el array JSON normalizado.
No resumas, no rankees, no comentes.
El despacho en paralelo, en SKILL.md:
## Paso 2 : Scrapear, en paralelo
Despacha un subagente source-scraper por
cada archivo en references/sources/, todos
a la vez, no en secuencia.
Espera a que todos terminen. Cada uno
devuelve un array JSON. Concatena los
arrays y pásalos al paso 3 (dedupe + rank).
Las dos palabras que hacen el trabajo pesado son “todos a la vez”. Sin ellas, Claude despacha los workers en serie sin problema, lo que conserva el determinismo pero tira la velocidad por la borda.
Un matiz sobre la carpeta agents/. Los subagentes son un concepto de primera clase en Claude Code, pero Claude Code solo los registra automáticamente desde .claude/agents/ (o ~/.claude/agents/, o el agents/ de un plugin), no desde una carpeta anidada dentro de un skill. Así que hay dos montajes válidos. O pones la definición real del subagente en .claude/agents/ para poder despacharlo por nombre, o mantienes una carpeta agents/ dentro del skill con archivos de instrucciones planas, y haces que SKILL.md le diga al orquestador que lance un subagente general que lea ese archivo.
El segundo patrón es el que usa el skill-creator oficial de Anthropic, y mantiene el skill portable en un único directorio. Solo hay que tener claro que el frontmatter model o tools del archivo anidado no se aplica solo, Claude Code no lo lee. Cuando compartas workers entre varios skills, promuévelos a .claude/agents/.
Control 5: la calidad, con tests/
Un proceso determinista puede seguir publicando un informe erróneo. Un resumen puede afirmar algo que la fuente nunca dijo, una noticia de “últimos 7 días” puede tener tres semanas. Determinismo no es lo mismo que corrección.
Hace falta una capa de verificación que corre antes de publicar el informe, un LLM como juez. Un subagente fresco lee el output y una rúbrica que tú escribiste, y decide si cada elemento pasa. La independencia es lo que hace la prueba válida, el modelo que juzga el trabajo no es el que lo produjo.
Algunos tipos de error solo se detectan leyendo y razonando. ¿La afirmación está de verdad respaldada por la fuente? ¿El tono es el correcto? ¿La fecha es plausiblemente reciente? Un script no puede responder a eso, pero un modelo con una rúbrica clara sí. Poner la rúbrica por escrito convierte un criterio subjetivo en una comprobación repetible.
El patrón es un archivo markdown por criterio dentro de tests/. Cada archivo es una rúbrica con criterios de aprobado que el juez aplica a cada elemento del output.
.claude/skills/ai-news/
├── SKILL.md
├── references/ scripts/ assets/ agents/ ← controles 1-4
└── tests/
├── accuracy-test.md ← rúbrica: ¿cada afirmación remite a su fuente?
├── relevance-test.md ← rúbrica: ¿la noticia trata realmente de IA?
├── recency-test.md ← rúbrica: ¿la noticia está dentro de la ventana de 7 días?
└── safety-test.md ← rúbrica: sin datos personales, sin contenido inseguro o difamatorio
Una rúbrica de contenido:
# Rúbrica: Precisión
Evalúa cada noticia del informe final.
Apruébala solo si:
- cada afirmación del resumen está respaldada
por la fuente enlazada, ábrela y verifícalo
- no aparece ningún número, nombre o fecha que
no esté en el artículo fuente
- el titular no está editorializado más allá
de lo que dice la fuente
Por cada noticia, devuelve:
{ id, passed, evidence }
Marca los fallos, no los corrijas en silencio.
La puerta de calidad, en SKILL.md:
## Paso 5 : Puerta de calidad (antes de renderizar)
Por CADA archivo de rúbrica en tests/, despacha un subagente
juez distinto (contexto fresco, no puede ser el agente que
escribió el informe). Cada juez lee su rúbrica, evalúa el
output y devuelve un resultado por noticia: { id, passed, evidence }.
Reúne todas las evaluaciones. Si una noticia falla alguna
rúbrica, márcala en el informe con la evidencia del juez, o
elimínala y anota el hueco. Nunca publiques en silencio una
noticia que falla.
Un modelo revisando su propio trabajo en el mismo contexto es una comprobación débil, está predispuesto a estar de acuerdo con lo que acaba de escribir. Un subagente fresco, con solo la rúbrica y el output, sin memoria de haberlo producido, da una lectura independiente. La infraestructura de subagentes del control 4 hace que esto salga barato.
Un LLM como juez contra una rúbrica es la comprobación cualitativa que corre cada vez que el skill se ejecuta. Si además quieres evaluaciones cuantitativas (puntuación por aserciones, benchmarks de varianza, comparaciones con y sin skill), el plugin oficial skill-creator de Anthropic está pensado justo para eso. Son complementarios, no sustitutos, las rúbricas evitan que un informe erróneo se publique hoy y los benchmarks te dicen si un cambio de ayer bajó silenciosamente la tasa de aciertos.
El skill terminado
Cinco controles, cinco carpetas, un orquestador. El mismo skill que empezó como un único archivo markdown que cambiaba de resultado en cada ejecución ahora corre rápido, verificado, con las fuentes y el proceso ya decididos de antemano.
.claude/skills/ai-news/
├── SKILL.md ← el orquestador, conecta los cinco controles
├── references/ ← CONTROL 1 : input
│ └── sources/ ← un archivo por fuente aprobada
│ ├── techcrunch-ai.md
│ ├── the-verge-ai.md
│ ├── arxiv-cs-ai.md
│ ├── hacker-news.md
│ └── anthropic-blog.md
├── scripts/ ← CONTROL 2 : proceso
│ ├── fetch_source.py
│ ├── dedupe.py
│ ├── rank.py
│ └── validate.py
├── assets/ ← CONTROL 3 : output
│ ├── output-template.html
│ ├── output-template.csv
│ ├── story.json
│ └── webprofits-logo.svg
├── agents/ ← CONTROL 4 : velocidad
│ └── source-scraper.md
└── tests/ ← CONTROL 5 : tests (LLM como juez)
├── accuracy-test.md
├── relevance-test.md
├── recency-test.md
└── safety-test.md
Y el orquestador, seis pasos que se leen como una receta:
# SKILL.md : el orquestador
Paso 1 Lee todos los archivos de references/sources/. Usa SOLO estas fuentes.
Paso 2 Despacha un subagente source-scraper por fuente, todos en paralelo.
Cada uno ejecuta scripts/fetch_source.py y devuelve JSON normalizado.
Paso 3 Combina los resultados. Ejecuta scripts/dedupe.py y luego scripts/rank.py --top 50.
Paso 4 Ejecuta scripts/validate.py para confirmar la forma del output. Para si falla.
Paso 5 Despacha un subagente juez por rúbrica en tests/. Marca cualquier noticia que falle.
Paso 6 Rellena assets/output-template.html con las noticias ordenadas y verificadas.
Son esas cinco decisiones, aplicadas donde tocaba, sin nada más añadido.
Cuando un skill tiene agentes y llega a este punto, el siguiente paso, si tienes varios skills relacionados, es empaquetar comandos, skills y agentes en un plugin único, instalable y versionado. Eso es una decisión de empaquetado, no otro control. Para un skill suelto, los cinco controles de arriba son todo el kit.
Qué controles necesitas de verdad
El determinismo es un dial, no un valor por defecto. Casi nunca necesitas los cinco controles, y sobrecontrolar un skill que debería quedarse flexible solo lo vuelve frágil. Añade un control para arreglar un fallo que estás viendo, no de forma preventiva.
| Si notas esto… | Añade este control | Pero sáltatelo cuando… |
|---|---|---|
| Las fuentes o los datos varían, o vienen de sitios en los que no confías | Input | El skill debe explorar libremente: investigación abierta, trabajo de descubrimiento |
| Claude resuelve la misma tarea de forma distinta cada vez | Proceso | La tarea es genuinamente nueva cada vez, o los sitios de origen son hostiles y hace falta improvisar |
| La estructura del entregable cambia entre ejecuciones | Output | El formato de salida debe adaptarse a lo que se pida |
| Las ejecuciones son lentas, o el contexto principal se llena de ruido | Velocidad | El trabajo es pequeño, secuencial, o los pasos dependen unos de otros |
| El output parece correcto pero no puedes confiar en que lo sea | Calidad | El riesgo es bajo, o de todas formas una persona revisa cada ejecución |
Escribe primero el skill plano y lánzalo varias veces. Lo que observes que cambia te dice exactamente qué control añadir a continuación. Un skill que funciona bien como un solo archivo markdown no necesita una carpeta llena de andamiaje.
Cada control que añades cierra una fuente de variación concreta, y se queda cerrada en todas las ejecuciones siguientes.