Las skills y los agentes son los dos ficheros markdown con instrucciones, pero sirven para cosas distintas y por eso se escriben distinto. Vamos a ver qué es cada uno, dónde se guardan en OpenCode, qué lleva su frontmatter, cuándo se usa cada uno y qué partes lleva el cuerpo de cada fichero.
Más información:
Una skill es un fichero SKILL.md con lo que el agente tiene que saber o hacer para una tarea concreta: cómo son los servicios, cómo se revisa un PR, cómo se crea un endpoint o como se hacen los test.
Se carga dentro de tu conversación: el agente lee el skill y sigue trabajando con esas instrucciones. Es decir, llamar a un skill añade más prompt a tu conversación (contexto).,
Un agente es un fichero con las instrucciones para hacer un trabajo cerrado y devolver un resultado. En OpenCode, Plan y Build son los agentes principales, los que hablan contigo; los que escribimos nosotros son casi siempre subagentes: los lanza el principal (qué es un subagente).
El subagente empieza con el contexto vacío: solo tiene su fichero y el prompt que le escribe el principal. Al terminar, al principal solo le vuelve su respuesta.
En OpenCode, dentro de .opencode/ del proyecto (y en ~/.config/opencode/ para todos los proyectos: skills y agentes en el tema 3):
.opencode
├── skills
│ ├── servicios
│ │ └── SKILL.md
│ └── revisar-pr
│ ├── SKILL.md
│ └── checklist.md
└── agents
├── revisor-tests.md
└── verificador-spec.md
SKILL.md. La carpeta es suya: puede llevar al lado los ficheros que use (checklist.md)..opencode/agents/, un fichero .md por agente. El nombre del agente es el del fichero: revisor-tests.md es el agente revisor-tests..opencode/agents/ no se deja ningún otro .md. OpenCode convierte en agente todo .md que haya ahí, también en subcarpetas: una plantilla agents/plantillas/informe.md aparece como el agente plantillas/informe.
Los dos ficheros empiezan con una cabecera en YAML entre dos líneas ---, el frontmatter. Detrás va el cuerpo en markdown.
--- name: servicios description: Convenciones de los servicios de Pedidos (transacciones, validación, errores de negocio). Usar al escribir, revisar o probar una clase *Service. ---
--- description: Revisa los tests de una clase de servicio y dice qué falta o está mal. Usar después de escribir o cambiar los tests de una clase *Service, antes del commit. mode: subagent permission: edit: deny ---
En la skill:
name: obligatorio, el mismo que el nombre de la carpeta.description: obligatoria.
En el agente no hay name, porque lo da el fichero:
description: obligatoria.mode: subagent para que solo lo lance otro agente. Si no se pone, también aparece entre los principales al pulsar Tab.permission: lo que no puede hacer. Un revisor con edit: deny, porque si puede editar acaba arreglando en vez de revisar.
La description es lo único que el LLM lee siempre. De todas las skills y agentes tiene en contexto solo el nombre y la descripción; el cuerpo no lo ve hasta que decide usarlo. Así que, si la descripción no dice cuándo usarlo, no lo usa nunca.
Tiene que decir qué hace y cuándo usarlo, con las palabras con las que se pedirá la tarea:
# ✅ qué cubre, cuándo usarla y con qué palabras se pide description: Convenciones de los servicios de Pedidos (transacciones, validación, errores de negocio). Usar al escribir, revisar o probar una clase *Service. # ❌ no dice cuándo: no se carga nunca description: Utilidades para servicios. # ❌ cuenta el proceso, que va en el cuerpo, y no dice cuándo description: Primero lee el servicio, luego revisa las transacciones y lanza los tests.
| Skill | Agente | |
|---|---|---|
| Dónde trabaja | En tu conversación | En un contexto nuevo, vacío |
| Qué ve | Todo lo que habéis hablado | Solo su fichero y el prompt que le pasan |
| Qué queda en tu contexto | Todo lo que lee y hace | Solo su respuesta |
| Para qué | Saber cómo se hace algo en el proyecto o seguir un procedimiento contigo | Encargar un trabajo y recibir un resultado |
Como ya hemos visto en ambos casos hay unas instrucciones que son lo que llevan en su fichero.
Y ahora viene lo más interesante: en una skill y en un agente se puede poner exactamente lo mismo en el fichero, pero como cambia dónde se carga, el resultado no es el mismo. Para verlo vamos a coger dos ejemplos, uno que debería ser skill y otro que debería ser agente, y a escribir cada uno de las dos formas. Son cuatro ficheros:
.opencode
├── skills
│ ├── repository-jpa
│ │ └── SKILL.md
│ └── generar-tests
│ └── SKILL.md
└── agents
├── repository-jpa.md
└── generar-tests.md
Ejemplo 1: cómo se hace un Repository con JPA. El fichero dice cómo se hace algo en el proyecto. Es conocimiento que se usa mientras se programa. La skill y el agente llevan el mismo cuerpo; solo cambia el frontmatter:
--- name: repository-jpa description: Cómo se escriben los repositorios JPA de este proyecto. Usar al crear o modificar una interfaz *Repository. --- # Repositorios JPA - La interfaz extiende `JpaRepository<Entidad, Long>` y se llama `<Entidad>Repository`. - Los métodos derivados se llaman `findBy...`; si la consulta no sale con el nombre, se escribe con `@Query` en JPQL. Nunca SQL nativo. - Las búsquedas que pueden devolver muchas filas reciben `Pageable` y devuelven `Page<Entidad>`. - Las consultas usan `JOIN FETCH` para las relaciones que se van a leer.
--- description: Cómo se escriben los repositorios JPA de este proyecto. Usar al crear o modificar una interfaz *Repository. mode: subagent --- # Repositorios JPA - La interfaz extiende `JpaRepository<Entidad, Long>` y se llama `<Entidad>Repository`. - Los métodos derivados se llaman `findBy...`; si la consulta no sale con el nombre, se escribe con `@Query` en JPQL. Nunca SQL nativo. - Las búsquedas que pueden devolver muchas filas reciben `Pageable` y devuelven `Page<Entidad>`. - Las consultas usan `JOIN FETCH` para las relaciones que se van a leer.
Ejemplo 2: generar los tests de una clase. El fichero dice cómo hacer un trabajo cerrado del que interesa el resultado: el test terminado y si pasa. Otra vez el mismo cuerpo en los dos:
--- name: generar-tests description: Genera la clase de test de una clase de servicio con JUnit 5 y Mockito y la deja pasando. Usar cuando se pida crear los tests de una clase. --- # Generar tests 1. Lee la clase y apunta sus métodos públicos. 2. Escribe `<Clase>Test` en `src/test/java`, con JUnit 5 y Mockito para las dependencias: al menos un test por método público y uno por cada `ErrorDeNegocio` que lance. 3. Ejecuta `mvn test -Dtest=<Clase>Test`. 4. Si algún test falla, corrígelo y vuelve a ejecutar hasta que pasen todos. 5. Termina diciendo el fichero creado, cuántos tests tiene y si pasan.
--- description: Genera la clase de test de una clase de servicio con JUnit 5 y Mockito y la deja pasando. Usar cuando se pida crear los tests de una clase. mode: subagent --- # Generar tests 1. Lee la clase y apunta sus métodos públicos. 2. Escribe `<Clase>Test` en `src/test/java`, con JUnit 5 y Mockito para las dependencias: al menos un test por método público y uno por cada `ErrorDeNegocio` que lance. 3. Ejecuta `mvn test -Dtest=<Clase>Test`. 4. Si algún test falla, corrígelo y vuelve a ejecutar hasta que pasen todos. 5. Termina diciendo el fichero creado, cuántos tests tiene y si pasan.
El cuerpo del fichero es el prompt que se carga, igual en la skill que en el agente: en la skill se añade a tu conversación y en el agente va a un contexto nuevo. Lo que escribes tú para que arranque también es lo mismo en los dos casos: para el ejemplo 1, «Crea el repository de Pedido con una consulta que busque por cliente y estado»; para el ejemplo 2, «Genera los tests de PedidoService». Veamos qué pasa en cada uno de los cuatro casos.
Es lo que debería ser. El fichero se carga en tu conversación y el agente principal escribe PedidoRepository siguiendo esas reglas. Y las reglas se quedan en el contexto: cuando dos mensajes después pidas el repository de Cliente saldrá igual, sin volver a cargar nada. Es conocimiento, y el conocimiento sirve mientras se sigue trabajando.
No pasa absolutamente nada. El principal lanza el agente, que arranca con el contexto vacío y lee su fichero. Y en ese fichero no se le dice que haga nada: son cuatro reglas sobre cómo son los repositorios, ni una tarea. El agente no puede crear PedidoRepository ni devolver código, porque nadie se lo ha pedido a él; lo único que puede hacer es contestar con lo que sabe, es decir, repetir sus reglas, o decir que no tiene nada que hacer. Y al terminar, las reglas mueren con él: a tu conversación no ha llegado nada, y tu agente principal sigue sin saber cómo se hace un repository en este proyecto.
En el mejor de los casos, el agente devuelve las reglas en su respuesta y el principal las usa: has pagado un proceso aparte para traer al contexto el mismo texto que la skill habría puesto directamente. En el peor, cada vez que el principal necesite saber cómo va un repository tendrá que lanzarlo otra vez para que vuelva a leer el mismo fichero. Conocimiento en un agente es conocimiento que nadie conserva.
Es lo que debería ser. El principal lanza el agente con «Genera los tests de PedidoService», y el agente, en su contexto, lee la clase, escribe PedidoServiceTest, lanza mvn test, lee la salida, corrige el test que falla, lo vuelve a lanzar… y al terminar devuelve una respuesta: «PedidoServiceTest.java con 6 tests, todos pasan». A tu conversación llega solo esa línea. Todo lo que ha leído y todas las vueltas que ha dado se han quedado en su contexto, que desaparece.
El trabajo también se hace, y se hace igual de bien: el principal carga el fichero y hace lo mismo que haría el agente. La diferencia es dónde se queda todo eso: la clase leída, el test escrito, la salida de mvn test con su traza de error, el test corregido, la segunda salida… todo en tu conversación, cuando a ti solo te interesaba el resultado. Y ese relleno se paga en cada mensaje que viene después: en cada turno se vuelve a enviar el contexto entero, así que cuantos más tokens hay dentro, más caro sale cada mensaje nuevo y antes se llena el contexto, que es lo que hace que el modelo empiece a olvidar lo del principio.
| Como skill | Como agente | |
|---|---|---|
| Ejemplo 1: cómo se hace un repository JPA | ✓ Las reglas se quedan y valen para el resto de la conversación | No hay nada que hacer: el fichero son reglas, no una tarea. Como mucho las recita, y mueren con él |
| Ejemplo 2: generar los tests de una clase | Funciona, pero los ficheros leídos, las salidas de mvn test y los reintentos se quedan en tu contexto y se gastan más tokens (Más dinero) | ✓ Se hace la misma tareas y solo devuelve el resultado al contexto principal |
¿Y si lanzas como skill algo que debería ser agente, el generador de tests, nada más empezar la conversación, y cuando acaba cierras la conversación?
Piensa qué tienes: un contexto vacío al que se le añade el fichero y tu mensaje, trabaja, y lo que haya acumulado desaparece al cerrar porque nadie sigue hablando ahí. Es exactamente un agente, con la diferencia de que el resultado se lo has dado tú al principal en vez de leerlo él. La skill no ha llenado nada que vayas a pagar después. Así que la pregunta de fondo no es «¿esto es una skill o un agente?», sino «¿voy a seguir trabajando en esta conversación con lo que quede en el contexto?». Si no, da igual cuál de los dos uses.
Una skill no lleva entradas ni salida: trabaja dentro de tu conversación, así que ya ve lo que has pedido, y lo que hace se queda ahí. El cuerpo es solo lo que tiene que saber o hacer:
---
name: servicios
description: Convenciones de los servicios de Pedidos (transacciones, validación,
errores de negocio). Usar al escribir, revisar o probar una clase *Service.
---
# servicios
Reglas de las clases `*Service` del servidor.
## Convenciones
- Un servicio por agregado: `PedidoService`, no `PedidoLineaService`.
- Valida al entrar y lanza `ErrorDeNegocio` con el código del requisito de `specs/`.
- No devuelve entidades: devuelve DTOs.
## Ejemplos
- ✅ `throw new ErrorDeNegocio("R-12", "El pedido ya está enviado")`
- ❌ `throw new IllegalStateException("No se puede")` (no dice qué requisito incumple)
Cómo se organiza por dentro una skill grande está en Estructura de un skill.
Un agente sí necesita entradas y salida, porque no ve tu conversación y lo que devuelve lo lee otro agente. Así que su cuerpo lleva siempre estas tres secciones, en este orden:
## Entradas: lo que le llega en el prompt, con nombre. Es todo lo que sabe de la tarea.## Proceso: lo que tiene que hacer, en pasos numerados.## Salida: qué devuelve y con qué formato exacto.
--- description: Revisa los tests de una clase de servicio y dice qué falta o está mal. Usar después de escribir o cambiar los tests de una clase *Service, antes del commit. mode: subagent permission: edit: deny --- # Revisor de tests Revisas los tests de una clase. No arreglas nada: solo dices qué está mal. ## Entradas Te llegan en el prompt: - `clase`: ruta de la clase que se prueba. - `test`: ruta de su clase de test. ## Proceso 1. Lee `clase` y apunta sus métodos públicos. 2. Lee `test` y busca al menos un test por cada método público. 3. Comprueba que cada test tiene aserciones y que no copia valores del código de producción (el IVA escrito a mano en vez de leerlo). 4. En la duda, cuéntalo como problema. ## Salida La primera línea es exactamente `PASS` o `FAIL`. Con `PASS` no se escribe nada más. Con `FAIL`, debajo, una línea por problema con el formato `fichero:inicio-fin:descripcion`: - `fichero`: ruta relativa a la raíz del proyecto. - `inicio-fin`: las líneas donde está el problema. Si es una sola, `42-42`. - `descripcion`: qué está mal, en una sola línea.
Entradas el agente no lo sabe. «Revisa lo que hemos hecho» no funciona; el principal tiene que pasarle clase y test con sus rutas.
El agente termina con una sola palabra en la primera línea: PASS si todo está bien, FAIL si no. El principal compara esa palabra tal cual para decidir qué hace; con «parece que los tests están bastante bien» no puede.
Con FAIL, una línea por problema con el formato fichero:inicio-fin:descripcion, que dice dónde ir a arreglarlo:
FAIL src/test/java/com/tienda/PedidoServiceTest.java:28-28:el nombre del test no cita el requisito src/test/java/com/tienda/PedidoServiceTest.java:30-45:escribe 21 en vez de leer el IVA del código de producción src/main/java/com/tienda/PedidoService.java:52-60:cancelar() no tiene ningún test
PASS
Di si cada tarea se hace con una skill o con un agente, y por qué:
| Tarea | Skill o agente | Por qué |
|---|---|---|
| Las convenciones de los controladores REST del proyecto | ||
Comprobar que cada requisito de specs/pedidos.md tiene un test | ||
| Cómo se escribe un mensaje de commit en el equipo | ||
| Revisar el servicio que se acaba de escribir |
Escribe la skill .opencode/skills/controladores-rest/SKILL.md con las convenciones de los controladores REST del proyecto: un controlador por agregado, rutas en plural (/pedidos), nunca devuelve una entidad JPA sino un DTO, y los errores de negocio se devuelven con 409 y el código del requisito. Pon una description que diga cuándo usarla.
Abre una conversación nueva y pide «crea el controlador de Cliente», sin nombrar la skill. Comprueba que la ha cargado y que el controlador cumple las cuatro reglas.
Escribe el agente .opencode/agents/revisor-dtos.md, que comprueba que ningún controlador devuelve una entidad JPA. Tiene que tener el frontmatter con description, mode y permission, y las secciones Entradas, Proceso y Salida con PASS/FAIL y el formato fichero:inicio-fin:descripcion.
Abre una conversación nueva y lánzalo con @revisor-dtos sobre un controlador que devuelva una entidad. Comprueba que responde FAIL con la línea correcta y que en tu conversación solo ha quedado esa respuesta, no el controlador que ha leído.