Skip to content

Una skill que no sabe nada: la arquitectura de Modern Web Guidance

Published:

La forma habitual de dar conocimiento experto a un agente de código es escribir un Markdown y confiar en que el agente lo lea. Funciona hasta que el documento crece: cada token de guía es un token que no está disponible para el código, para el historial de la conversación o para la salida. El contexto es un recurso escaso y las skills grandes lo consumen aunque la tarea no las necesite.

Modern Web Guidance, el proyecto del equipo de Chrome para que los agentes dejen de escribir patrones obsoletos, invierte ese patrón. Su skill principal no contiene conocimiento: contiene instrucciones para ir a buscarlo. Y el buscador es un modelo de embeddings que se ejecuta en local, sin red y sin API keys.

Es un catálogo de guías interesante, pero lo que me atrapa es la arquitectura que hay por debajo, porque es replicable en cualquier proyecto que necesite aportar conocimiento de dominio a un agente.

El patrón invertido

Los números del paquete explican bien la decisión de implementar una arquitectura así. La distribución de modern-web-guidance incluye 139 guías en Markdown que suman unos 975 KB de texto, del orden de un cuarto de millón de tokens. Cargarlas todas no es una opción: no caben, y aunque cupieran, saturar la ventana con 138 guías irrelevantes degrada la atención del modelo sobre la que sí importa.

La SKILL.md que se instala ocupa 5,6 KB. De esos, el bloque description del frontmatter son unos 1.000 caracteres, del orden de 250 tokens, y es la única parte que está permanentemente en contexto. El cuerpo de la skill se carga solo cuando el agente decide activarla, y lo que contiene no es una guía, son dos comandos:

# Paso 1: buscar casos de uso relevantes
npx -y modern-web-guidance@latest search "<query>"

# Paso 2: recuperar la guía completa por ID
npx -y modern-web-guidance@latest retrieve "<id>"

Aquí ya podemos ver que tenemos una dependencia de Node y npm.

El agente utiliza unos 250 tokens siempre, unos 1.400 cuando activa la skill, y solo entonces decide qué guía concreta merece entrar en contexto. La búsqueda devuelve el tokenCount de cada candidata, así que el propio agente puede razonar sobre el coste antes de pedirla.

El proyecto mantiene también una megaskill, un documento único con todo el contenido concatenado, para agentes que no pueden ejecutar comandos. Es el plan B, no la solución principal.

Búsqueda semántica local, sin red y sin API keys

El comando search no llama a ninguna API: ejecuta un modelo de embeddings dentro del proceso de Node.

El modelo es all-MiniLM-L6-v2, un BERT pequeño de la familia sentence-transformers que produce vectores de 384 dimensiones. Está convertido a TensorFlow.js Graph Model y está dentro del paquete npm: 22,5 MB de pesos en group1-shard1of1.bin más 576 KB de topología. Sin cuantizar, en float32, una decisión que el repositorio justifica como paridad máxima de precisión con el modelo original.

Dos detalles de la conversión importan. El grafo lleva embebidas las capas de mean pooling y normalización L2, así que el tensor de salida ya es el vector final normalizado y no hay post-proceso en JavaScript. Y el tokenizador BERT se resuelve con local_files_only contra una caché que también está incluida en el paquete, de modo que no hay ni una petición a Hugging Face en tiempo de ejecución.

// serving/lib/tfjs-embedder.ts (simplified)
await setBackend("cpu");
this.model = await loadGraphModel(ioHandler);
this.tokenizer = await BertTokenizer.from_pretrained(
  "Xenova/all-MiniLM-L6-v2",
  { local_files_only: true }
);

El índice se precalcula en build, no en tiempo de consulta. Cada guía se divide por encabezados y cada fragmento se embebe con un prefijo que le da contexto de dónde vive:

const embeddingText = `${id} (${category})\nFeatures: ${featuresUsed.join(", ")}\n\n${chunk}`;
const vector = await embedder.embed(embeddingText);

El resultado se serializa a un use-cases.vectors.gen.json.gz de 5,6 MB que se distribuye con el paquete. En cada búsqueda, el CLI calcula el embedding de la query, recorre los vectores en memoria calculando similitud coseno, descarta lo que baje de 0,3 y devuelve cinco resultados. Como hay varios fragmentos por guía, agrupa por ID y se queda con la similitud máxima de cada una: los fragmentos compiten entre sí, pero la guía aparece una sola vez.

No hay base de datos vectorial ni índice aproximado. Con este volumen, un bucle sobre un array y un producto escalar bastan.

¿Por qué embeddings y no grep? Porque el agente pregunta por intención y las guías están escritas con vocabulario de especificación. Esta consulta no comparte ni una palabra significativa con la guía que la responde:

$ npx -y modern-web-guidance@latest search "close a popup when the user clicks outside of it"
[{"id":"light-dismiss-a-dialog","description":"Create a modal dialog that can be closed via light dismiss (i.e. clicking or tapping outside of the dialog)","category":"ui-behaviors","featuresUsed":["<dialog closedby>"],"tokenCount":1085,"similarity":0.6284},
...

Quien escribe la guía dice light dismiss y <dialog closedby>; quien pregunta dice popup y clicks outside. Una búsqueda léxica falla; el espacio vectorial acierta con 0,63 de similitud.

Cambia de consulta para comparar los dos métodos sobre las mismas 139 guías. Los resultados son reales: la columna de significado sale de ejecutar search, y la de palabras de contar ocurrencias en los ficheros del paquete.

El tercer caso es el que más me gusta, porque no sale bien del todo: los embeddings mejoran la precisión, no la garantizan. La latencia se mueve alrededor de 1,2 a 1,5 segundos por consulta en CPU (MacBook Air M4), arranque del proceso incluido. Para una operación que ocurre una vez al principio de una tarea, es un precio razonable a cambio de no gastar red ni contexto.

Conviene matizar el “local”: la inferencia nunca sale de la máquina, pero el CLI sí envía telemetría de uso, y ahí va el texto de la query junto con los IDs devueltos y la latencia. Está activada por defecto y se desactiva con DISABLE_TELEMETRY=1.

La guía como unidad evaluable

El segundo acierto del diseño es que una guía no es un documento, es un directorio con contrato. Cada caso de uso reúne cinco piezas:

Las expectativas son verificables y deterministas, no valoraciones:

- Cards with class name `.off-screen-card` must set `content-visibility: auto`.
- Cards with class name `.off-screen-card` must set `contain-intrinsic-size`.
- The `.debug-panel` must be hidden using `content-visibility: hidden`.

La pieza que cierra el sistema es la calibración: el grader debe dar 100% sobre el demo bueno y 0% sobre el negativo. Si no lo consigue, el pipeline lo borra, lo regenera pasándole como contexto los tests que fallaron y reintenta. Un grader que aprueba las dos implementaciones no mide nada, y sin esta comprobación nadie lo notaría: seguiría dando 100% en todas las evaluaciones y la guía parecería perfecta.

Sobre el contenido, las guías no repiten los datos de compatibilidad a mano. Los interpolan con macros que se expanden en build contra web-features y los datos de compatibilidad de MDN:

{{ BASELINE_STATUS("content-visibility") }}

Que produce, con los datos de hoy:

Baseline status for content-visibility: Newly available. It's been Baseline since 2025-09-15.
Supported by: Chrome 108 (Nov 2022), Edge 108 (Dec 2022), Firefox 130 (Sep 2024), and Safari 26 (Sep 2025).

El cuidado por la densidad llega hasta el formato de esa línea. El código que la genera consolida las versiones de escritorio y móvil en una sola etiqueta cuando coinciden, y solo las separa cuando divergen, justificándolo con un análisis sobre los datos de compatibilidad. Parece un detalle de formato, pero es una decisión de ingeniería de contexto.

Calibrar el contenido con evaluaciones

Con graders fiables, la pregunta “¿esta guía sirve para algo?” deja de ser una opinión. El harness ejecuta cada tarea dos veces, sin guía y con guía, y compara la tasa de aserciones que pasan.

El proyecto usa dos métricas para decidir. La opportunity es 100% menos la tasa sin guía: si el modelo ya resuelve bien la tarea por su cuenta, no hay nada que ganar y la guía sobra. El uplift es la diferencia entre ambas tasas, y es lo que se optimiza. Las guías se deshacen de todo aquello que los modelos ya saben, porque ese contenido ocupa contexto sin cambiar el resultado.

El repositorio publica resultados agregados sobre 130 tareas y más de mil aserciones. Con distintos modelos, los agentes sin guía se sitúan en torno al 54-57% de aserciones superadas, y con guía entre el 82% y el 89%. El salto ronda los 30 puntos porcentuales y se mantiene bastante estable entre agentes. Conviene leerlo con la cautela habitual: es un benchmark diseñado por quienes escriben las guías, sobre tareas que ellos mismos eligen. Lo relevante no es la cifra, es que exista el circuito cerrado.

Qué podemos reutilizar en nuestro propio proyecto

La arquitectura se replica con piezas pequeñas y ninguna de ellas necesita infraestructura.

Podemos separar el índice del contenido, dejando en la skill solo las instrucciones para buscar. Podemos precalcular embeddings en build y distribuirlos como un artefacto comprimido, porque unas decenas o cientos de documentos no justifican una base de datos vectorial. Podemos dividir por encabezados y prefijar cada fragmento con su identidad, que es lo que hace que la similitud no dependa de que el fragmento repita su propio contexto. Y podemos exigirle a cada documento un test que demuestre que la guía cambia el resultado.

Esa última parte es la más exigente y la que más valor aporta. Escribir la documentación de un agente sin medir su efecto es escribir a ciegas, y el sesgo natural es añadir siempre más texto. Con un grader delante, quitar contenido se convierte en una decisión defendible.

Conclusión

Me encanta el enfoque que da Modern Web Guidance al contexto, lo trata como un presupuesto. Cuesta unos 250 tokens fijos tener la skill presente, la recuperación se delega en un buscador semántico que corre en la máquina de quien programa, y solo entonces se gasta contexto en la guía concreta que hace falta.

Si estamos escribiendo skills para nuestros equipos, el patrón de buscar primero y recuperar después con embeddings locales es más barato de montar de lo que parece y escala mucho mejor que el Markdown que crece sin control. El código está disponible en modern-web-guidance-src y una guía real, la de navigation-drawer, muestra el formato final que acaba llegando al agente.

Glosario

Términos que aparecen en el artículo y que conviene tener claros:


Next Post
La ventana móvil de 28 días de CrUX