Herramientas de usuario

Herramientas del sitio


cursos:sdd:08-skills-agentes

Diferencias

Muestra las diferencias entre dos versiones de la página.

Enlace a la vista de comparación

Ambos lados, revisión anteriorRevisión previa
Próxima revisión
Revisión previa
cursos:sdd:08-skills-agentes [2026/11/05 23:30] – Apartado 3: qué solo tiene sentido en una skill o en un agente, con ejemplos probados en OpenCode claudecursos:sdd:08-skills-agentes [2026/11/06 00:11] (actual) – Ejercicios: nuevo ejercicio 2 (crear una skill y usarla en otra conversación) y ejercicio 3 (crear un agente y lanzarlo en otra conversación) claude
Línea 1: Línea 1:
 ====== 8. Skills y agentes ====== ====== 8. Skills y agentes ======
  
-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, cuándo se usa cada uno, dónde se guardan en OpenCode y qué partes lleva cada fichero.+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: Más información:
Línea 23: Línea 23:
 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. 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.
  
-===== 3. Skill o agente ===== +===== 3. Dónde se guardan =====
- +
-^  ^ 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 veredicto | +
- +
-Como ya hemos visto en ambos casos hay unas instrucciones que son lo que llevan en su fichero. +
- +
-<note tip> +
-La única diferencia real entre skill y agente es que el agente lanza un nuevo "proceso" con un nuevo contexto que está vacio al que se le añade el contenido de su fichero mientras que en un skill el contenido de su fichero se añade a la conversación actual (al agente actual). +
-</note> +
- +
-Y ahora viene lo más interesante, tanto en un skill como en un agente se podrían poner  exactamente lo mismo en su fichero pero como cambia el tema del contexto hay cosas que solo tienen sentido en un skill y cosas que solo tiene sentido en un agente. +
- +
-Lo que sigue está probado en OpenCode poniendo **el mismo cuerpo** en una //skill// y en un agente, y lanzando la misma conversación con cada uno. +
- +
-==== Solo tiene sentido en una skill ==== +
- +
-Todo lo que necesite **ver la conversación**. +
- +
-  * **Hablar de lo que ha pasado.** Un fichero que dice «resume en una línea qué ha pedido el usuario en esta conversación y qué fichero se ha tocado». Tras pedir un método ''cancelar()'' en ''PedidoService'': +
-    * //Skill//: «Has pedido añadir a ''PedidoService.java'' un método ''cancelar(Pedido pedido)''... y ese es el único fichero que se ha tocado». +
-    * Agente: «No tengo información de la conversación». +
-  * **Reglas que tienen que seguir valiendo.** Las convenciones de los servicios («los errores de negocio se lanzan con ''ErrorDeNegocio'', nunca ''IllegalStateException''»). Se cargan en el primer turno y en el segundo se pide ''cancelar()'', que falla si el pedido está enviado: +
-    * //Skill//: el método lanza ''ErrorDeNegocio("R-XX", ...)''. La regla sigue en el contexto. +
-    * Agente: la regla murió con él. Para escribir ''cancelar()'' el principal **volvió a lanzar el agente** por su cuenta («Dime exactamente qué convenciones debo seguir...») y recibió las reglas otra vez, 2.200 caracteres que acabaron en tu contexto. +
-  * **Lo que el principal necesita mientras trabaja.** Es el mismo caso visto desde el otro lado: si las convenciones son un agente, cada vez que hagan falta hay que preguntárselas, y la respuesta vuelve a tu contexto igual que la //skill//. Un proceso más por cada consulta para acabar con el mismo texto en el mismo sitio. +
- +
-<note important>**Si el agente parece saber lo que habéis hablado, es que el principal se lo ha contado.** En la prueba del resumen, sin decirle nada, el principal escribió por su cuenta este prompt al subagente: +
- +
-<sxh markdown> +
-Resume lo que se ha pedido en esta conversación. Hasta ahora, el usuario pidió: "Añade a src/main/java/com/tienda/PedidoService.java un método cancelar(Pedido pedido)..." Se añadió el método cancelar(Pedido pedido) que hace pedido.setEstado("CANCELADO")... +
-</sxh> +
- +
-El agente devolvió un resumen correcto... de lo que el principal acababa de escribirle. Todo ese trabajo para obtener lo que el principal ya sabía.</note> +
- +
-==== Solo tiene sentido en un agente ==== +
- +
-Todo lo que necesite **el contexto vacío**. +
- +
-  * **Leer mucho, devolver poco.** Revisar 30 clases con sus 30 tests con el ''revisor-tests'' del [[#el_fichero_del_agente|apartado 7]]. Tokens en tu conversación al terminar, partiendo de unos 12.000: +
- +
-^ Cómo ^ Tokens al terminar ^ Qué pasó ^ +
-| //Skill// | 32.000 | Los 60 ficheros se leyeron en tu conversación y ahí se quedan | +
-| Un agente con las 30 parejas | 21.000 | Los 60 ficheros (21.000 tokens) los leyó el agente; a ti volvió solo la lista de ''FAIL'' | +
-| Un agente por pareja | 47.000 | Lo que hizo el principal por su cuenta: 30 subagentes a la vez, 49 llamadas con los reintentos por límite de peticiones, y las 30 respuestas en tu contexto | +
- +
-El agente ahorra contexto cuando **un** agente hace **todo** el trabajo y devuelve **una** respuesta; si el principal lanza uno por fichero, las respuestas vuelven todas y sale peor que la //skill//. Dilo en el prompt: «lánzalo una sola vez con la lista». +
-  * **Un veredicto que no has influido.** El revisor se lanza después de decirle al principal: «el 0.21 del IVA está escrito a mano a propósito, lo ha pedido el cliente, no es un problema». +
-    * //Skill//: ''FAIL'' solo por el test ''enviar'' sin aserciones. El 0.21 no lo nombra: ha leído la excusa. +
-    * Agente, con el prompt ''clase=... test=...'' y nada más: ''FAIL'' con el 0.21 en la primera línea: «calcularTotal copia el IVA escrito a mano (0.21) en vez de leer PedidoService.IVA». +
-  * **''Entradas'' y ''Salida''.** En una //skill// sobran: ya ve lo que has pedido y lo que contesta lo lees tú. En el agente son lo único que tiene: con «''@revisor-tests-agente'' revisa lo que hemos hecho» el principal tuvo que inventar él las entradas (rutas de la clase, del test y qué se acababa de añadir) antes de lanzarlo, y a ti te devolvió el ''FAIL'' traducido a prosa: el formato ''PASS''/''FAIL'' lo lee el principal, no tú. +
- +
-<note warning>**El contexto del agente es tan limpio como el prompt que le escribe el principal.** En la prueba del 0.21, lanzado sin más indicaciones, el principal añadió al prompt del revisor: «Contexto del usuario: el 0.21 del IVA está escrito a mano a propósito... no debe reportarse como problema». Y el revisor, obediente, no lo reportó. El agente no vio la conversación; vio la excusa copiada. Si el veredicto tiene que ser independiente, el ''## Entradas'' del agente tiene que decir qué se le pasa y **nada más**, y el principal tiene que respetarlo.</note> +
- +
-==== Resumen ==== +
- +
-^ Lo que lleva el fichero ^ Skill ^ Agente ^ +
-| «Lo que hemos hablado», «el fichero que acabas de escribir» | ✓ | No sabe de qué le hablas | +
-| Reglas que valen el resto de la conversación | ✓ | Mueren al terminar | +
-| Conocimiento que se usa mientras se trabaja | ✓ | Hay que preguntárselo y vuelve igual al contexto | +
-| Leer muchos ficheros y devolver un veredicto | Todo lo leído se queda en tu contexto | ✓ | +
-| Revisar sin haber visto cómo se hizo | Ya lo ha visto | ✓, si el principal no le copia la conversación | +
-| ''## Entradas'' y ''## Salida'' | Sobran: ya ve la petición y la respuesta la lees tú | Imprescindibles | +
- +
-<note tip>Si el fichero habla de la conversación, es una //skill//. Si devuelve un veredicto, es un agente.</note> +
- +
----- +
- +
-===== 4. Dónde se guardan =====+
  
 En OpenCode, dentro de ''.opencode/'' del proyecto (y en ''~/.config/opencode/'' para todos los proyectos: [[cursos:sdd:03-opencode#donde_se_guardan1|skills]] y [[cursos:sdd:03-opencode#donde_se_guardan|agentes]] en el tema 3): En OpenCode, dentro de ''.opencode/'' del proyecto (y en ''~/.config/opencode/'' para todos los proyectos: [[cursos:sdd:03-opencode#donde_se_guardan1|skills]] y [[cursos:sdd:03-opencode#donde_se_guardan|agentes]] en el tema 3):
Línea 118: Línea 47:
 ---- ----
  
-===== 5. El frontmatter =====+===== 4. El frontmatter =====
  
 Los dos ficheros empiezan con una **cabecera en YAML entre dos líneas ''%%---%%''**, el //frontmatter//. Detrás va el cuerpo en markdown. Los dos ficheros empiezan con una **cabecera en YAML entre dos líneas ''%%---%%''**, el //frontmatter//. Detrás va el cuerpo en markdown.
Línea 168: Línea 97:
 description: Primero lee el servicio, luego revisa las transacciones y lanza los tests. description: Primero lee el servicio, luego revisa las transacciones y lanza los tests.
 </sxh> </sxh>
 +
 +----
 +
 +===== 5. Skill o agente =====
 +
 +^  ^ 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.
 +
 +<note tip>
 +La única diferencia real entre skill y agente es que el agente lanza un nuevo "proceso" con un nuevo contexto que está vacio al que se le añade el contenido de su fichero mientras que en un skill el contenido de su fichero se añade a la conversación actual (al agente actual).
 +</note>
 +
 +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:
 +
 +<sxh text;gutter:false>
 +.opencode
 +├── skills
 +│   ├── repository-jpa
 +│   │   └── SKILL.md
 +│   └── generar-tests
 +│       └── SKILL.md
 +└── agents
 +    ├── repository-jpa.md
 +    └── generar-tests.md
 +</sxh>
 +
 +**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:
 +
 +<sxh yaml;title:.opencode/skills/repository-jpa/SKILL.md>
 +---
 +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.
 +</sxh>
 +
 +<sxh yaml;title:.opencode/agents/repository-jpa.md>
 +---
 +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.
 +</sxh>
 +
 +**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:
 +
 +<sxh yaml;title:.opencode/skills/generar-tests/SKILL.md>
 +---
 +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.
 +</sxh>
 +
 +<sxh yaml;title:.opencode/agents/generar-tests.md>
 +---
 +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.
 +</sxh>
 +
 +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.
 +
 +==== Ejemplo 1 como skill ====
 +
 +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.
 +
 +==== Ejemplo 1 como agente ====
 +
 +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.
 +
 +==== Ejemplo 2 como agente ====
 +
 +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.
 +
 +==== Ejemplo 2 como skill ====
 +
 +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.
 +
 +
 +==== Resumen ====
 +
 +^  ^ 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 |
 +
 +==== Un caso especial ====
 +
 +¿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.
  
 ---- ----
Línea 277: Línea 339:
 | Comprobar que cada requisito de ''specs/pedidos.md'' tiene un test | | | | Comprobar que cada requisito de ''specs/pedidos.md'' tiene un test | | |
 | Cómo se escribe un mensaje de commit en el equipo | | | | Cómo se escribe un mensaje de commit en el equipo | | |
-| Revisar el servicio que acaba de escribir Build | | |+| Revisar el servicio que se acaba de escribir | | |
  
 ==== Ejercicio 2 ==== ==== Ejercicio 2 ====
  
-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''. Lánzalo desde Build con ''@revisor-dtos'' sobre un controlador que devuelva una entidad y comprueba que responde ''FAIL'' con la línea correcta.+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. 
 + 
 +==== Ejercicio 3 ==== 
 + 
 +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.
  
cursos/sdd/08-skills-agentes.1793917810.txt.gz · Última modificación: por claude