← volver a Planos

agentic-dev-core · progressive disclosure

Las instrucciones, en capas

El agente no necesita todas las reglas del repo en cada turno. Necesita siempre las que lo atan, y el resto cuando el pedido lo pide. Por eso AGENTS.md es un núcleo chico que carga en cada sesión, cada tema vive en su propio archivo bajo .agents/instructions/, y un router fijo, reforzado por un hook, dice cuál leer y cuándo.

AGENTS.md · L0 .agents/instructions/ · L1 .agents/hooks/personality-reinject.mjs scripts/lint-instructions.ts ADR-0009

01Por qué partir el archivo

Antes, AGENTS.md era un solo archivo que todas las sesiones cargaban entero, hiciera falta o no. Tenía dos costos, y el segundo no se veía.

Costo visible

Cada sesión pagaba el archivo completo

Una charla sobre un color pagaba la política de git, el caché de Jira y el contrato multi-harness. Las secciones condicionales cargaban siempre, aunque la mayoría de las sesiones no las usara.

Costo invisible

Codex lo cortaba en el medio

Codex junta los AGENTS.md del proyecto en un solo presupuesto, project_doc_max_bytes, y corta el texto en el byte que lo cruza, con apenas un aviso en el log. El archivo viejo lo pasaba largo: en Codex, todo lo que venía después del corte nunca llegaba al modelo.

Lo que no servía

Un import no ahorra nada

En Claude Code un @archivo se expande entero al arrancar, y ni OpenCode ni Codex expanden imports. Lo único perezoso que comparten los tres hosts es que el modelo lea un archivo porque una instrucción se lo pidió, las skills, y un hook que inyecta texto en cada pedido. El diseño usa esas tres cosas.

02Tres capas

Cada capa carga en un momento distinto. L0 está siempre; L1 entra a pedido, una sección por tema; L2 es lo que ya existía, las references/ de cada skill.

L0 AGENTS.md carga siempre y su router apunta a las secciones de L1, que a su vez nombran las references de las skills en L2 L0 · AGENTS.md siempre, en cada host · reglas que atan, §2 entero, router, LOAD PROTOCOL L1 · una sección agent-git.md L1 · otra sección agent-local-context-pbi.md L1 · del proyecto agent-project.md L2 · references/ de las skills a pedido, nombradas por la sección o la skill

Las flechas son lecturas a pedido: L0 nunca importa una sección. Las que muestra el dibujo son ejemplos; la lista real es la carpeta.

Capa Archivo Cuándo carga
L0 AGENTS.md cada sesión, en cada host. Claude Code llega por CLAUDE.md, que es exactamente @AGENTS.md más un salto de línea
L1 un archivo por sección en .agents/instructions/ a pedido: el router de L0 dice cuándo leer cada uno, y el hook lo recuerda con una línea ROUTE:
L2 references/ de cada skill a pedido, nombradas por la sección o la skill que las necesita
Nombre
Progressive disclosure es el término que Anthropic usa para el mecanismo de las skills (la descripción al arrancar, el cuerpo al usarla). Esto lo extiende a las instrucciones del proyecto. No hay una capa L2 nueva: cada sección ya tenía su profundidad en las references/ que cita.

03El router fijo

Una tabla en L0, entre <!-- router:start --> y <!-- router:end -->, con tres columnas: Kind, Load y Also. Las filas son tipos de pedido, no features, así que la tabla no crece con el proyecto: crece la sección y sus triggers:. Lo de fijo lo hace cumplir un candado: el comentario <!-- router:lock <huella> <ADR-NNNN> --> guarda la huella de la tabla y el ADR que la decidió, y cualquier cambio de fila falla hasta que un ADR lo cubra.

Columna Qué dice Ejemplo (fila de git)
Kind el tipo de pedido que dispara la fila git: branch, commit, push, PR, merge, conflict
Load los archivos a leer, como rutas entre backticks; el primero es el ancla de la fila .agents/instructions/agent-git.md
Also la skill o el archivo que se carga junto git-flow-master
Una sola tabla
El hook lee esta misma tabla en cada pedido, junto con el frontmatter de cada sección. No hay copia generada: la tabla que lee el modelo y la que usa el clasificador no pueden divergir. La lista real de filas está en AGENTS.md; este deck no la copia.

04El LOAD PROTOCOL

Cinco líneas en la cabecera de L0 convierten el router en obligación. Sin ellas, la tabla sería una sugerencia.

  1. Antes de actuar, el pedido se compara contra el router y se lee cada archivo que coincide y todavía no está en la conversación.
  2. Una línea ROUTE: que inyecta el hook es obligatoria y gana sobre el criterio propio del modelo: se lee su archivo antes de actuar. Una ROUTE-OPTIONAL: no obliga (se lee uno de sus archivos solo si la tarea lo pide) y una ROUTE-PENDING: nombra un archivo obligatorio que sigue sin leerse.
  3. Un archivo leído no se relee, salvo que una compactación lo haya sacado del contexto.
  4. Ante la duda, se lee. Las secciones son chicas; una sección salteada es justo la falla que este diseño evita.
  5. Una sección se edita en su archivo, nunca pegando su prosa en AGENTS.md. Una regla que solo tiene este proyecto va a .agents/instructions/agent-project.md.

05El hook y la línea ROUTE:

El router en L0 depende de que el modelo lo consulte. El hook no: clasifica cada pedido con código y deja escrito qué leer. Es el mismo emisor que ya ponía el contrato de salida y la línea AGENT IDENTITY:.

El pedido pasa por el clasificador del hook, que lee el router y los triggers, y sale una línea ROUTE que el modelo lee antes de actuar El pedido texto + rutas que nombra Clasificador del hook router de L0 + triggers / paths El modelo lee la sección antes de actuar Estado por sesión qué ya se ruteó: lo repetido cuesta 0
# una línea por archivo nuevo, después de AGENT IDENTITY: y ORCA:
ROUTE: read .agents/instructions/agent-git.md (git, <n> lines) before acting on this prompt
ROUTE: read package.json (<n> lines) before acting on this prompt
# lo que pasó el tope, en una sola línea que no obliga
ROUTE-OPTIONAL: the prompt also touches .agents/instructions/agent-harnesses.md (harnesses); read one only if the task needs it.
Regla Qué hace
Ancla Una fila se dispara cuando el pedido coincide con su primer archivo de Load; entonces se rutean todos los archivos de la fila. Así un archivo que comparten dos filas no arrastra los de la otra.
Disparadores Cada sección declara en su frontmatter triggers: (regex sin distinguir mayúsculas, en inglés y en español) y paths: (prefijos del repo). Ajustar el ruteo es editar esas listas, nunca L0.
Alcance Una línea ROUTE-SCOPE: en el pedido reemplaza al pedido para clasificar: cada ítem es un id de sección (se rutea directo) o palabras (se clasifican); none no rutea nada. Sin ella, un pedido que trae el preámbulo de un orquestador se clasifica solo por el bloque de tarea que sigue a su marcador. Las rutas absolutas se neutralizan antes: el nombre de una carpeta no es una intención.
Ranking y tope Las filas disparadas se ordenan por la fuerza de su ancla (un id del alcance, después hits de paths:, después triggers distintos, después la coincidencia más temprana). Solo las primeras MAX_ROUTED_SECTIONS secciones reciben un ROUTE: obligatorio; el resto va a una sola línea ROUTE-OPTIONAL:. Medido: un agente lee una ruta sola mucho más que una de cinco en el mismo turno (ADR-0018).
Recordatorio (Claude Code) Un hook PostToolUse corre el mismo emisor: la primera herramienta que no lee ninguna sección ruteada, mientras alguna sigue sin leerse, recibe UNA línea ROUTE-PENDING:. No se repite en el turno y calla para los subagentes.
Dedupe por sesión Un archivo de estado en el temp del sistema, por checkout y por sesión, recuerda qué se ruteó. Un pedido que no necesita nada nuevo no agrega nada.
Re-armado tras compactar o /clear Un SessionStart con matcher: "compact" o matcher: "clear" vuelve a correr el emisor, que borra el estado y no imprime nada: lo que la compactación o el /clear sacó del contexto se vuelve a rutear la próxima vez que haga falta.
Host Cómo llega la línea
Claude Code .claude/settings.json: UserPromptSubmit ejecuta el emisor; SessionStart con compact o clear lo re-arma; PostToolUse sin matcher recuerda una ruta sin leer.
Codex .codex/hooks.json: lo mismo, solo en un proyecto de confianza (uno sin confianza no carga ni AGENTS.md ni hooks).
OpenCode 1 .opencode/plugins/personality-reinject.js clasifica en chat.message, empuja la línea al system prompt y re-arma solo al compactar (no registra nada para /clear).
OpenCode 2 Solo router: ningún hook documentado le pasa el texto del pedido, así que manda el router de L0 con el LOAD PROTOCOL. Es una degradación declarada.

06Las reglas críticas: la frase que ata, y el texto entero aparte

Una regla que obliga tiene que estar donde el agente siempre lee. Pero su fundamento, sus casos borde y su mecánica no necesitan ocupar cada turno.

# cada regla en L0: su frase obligatoria, textual, y un puntero
6. **QUALITY VERIFICATION**: After code changes, verify in order: tests → types → lint. No skip steps. Full: agent-critical-rules.md#6
En L0

La frase obligatoria, textual

Cada regla conserva su frase que ata, palabra por palabra, con el mismo número y nombre. Ninguna regla pierde fuerza: lo que obliga sigue cargando en cada sesión.

En L1

El texto entero en un archivo

.agents/instructions/agent-critical-rules.md tiene cada regla bajo ## N. NOMBRE, con su fundamento y sus casos borde. El router tiene una fila para leerlo cuando el agente está por romper una regla, duda o le preguntan por ella.

Al editar

Dos lugares, a propósito

Cambiar una regla es tocar su texto completo en agent-critical-rules.md y su frase en AGENTS.md, que tiene que seguir textual. bun run instructions:check compara las dos.

07Dos imports para Claude, una instrucción reforzada para los otros

package.json y .agents/project.yaml son los dos archivos de datos que más se consultan. Las filas del router que los nombran hacen dos cosas a la vez.

Host Qué pasa
Claude Code La fila escribe @package.json y @.agents/project.yaml como texto plano, sin backticks: Claude Code expande un import al arrancar y salta los que están dentro de código. Los dos archivos entran a la sesión enteros.
OpenCode y Codex No expanden imports. Leen la instrucción reforzada de la misma fila ("whenever any of these apply, read ...") y abren el archivo cuando la condición se cumple.
Costo
En Claude Code los imports cargan siempre, así que suman al arranque de la sesión aunque no cuenten contra el presupuesto de L0, que mide el archivo en sí. La medición en bytes y tokens está en .context/ADR/ADR-0009-progressive-disclosure-of-instructions.md. bun run instructions:check rechaza cualquier otro @ suelto en L0: la lista permitida es ALLOWED_IMPORTS en scripts/lint-instructions.ts.

08Lo que nunca sale de L0

L0 guarda lo que tiene que atar en cada turno, lo pida o no el pedido. Todo lo demás vive en una sección.

Acceso

LOAD PROTOCOL y router

Sin ellos ninguna sección es alcanzable.

§1

La frase obligatoria de cada regla crítica

Lo que prohíbe tiene que estar donde el agente siempre lee.

§2

La capa comportamental, entera

Butler, PM Voice, Visual Mapping, el reparto de capas y cómo se le pide al humano que decida. El comportamiento tiene que ser determinista, así que no se recorta.

§3

El núcleo de orquestación

Subagentes y conversación principal, el briefing de siete componentes, el protocolo de error. Gobierna cada despacho, sea cual sea el pedido.

§12

Los disparadores de memoria

Valen en cada turno y ocupan poco.

09Cómo se edita

Las secciones son doctrina compartida: bun sync las mantiene al día con el boilerplate. agent-project.md es lo propio de cada proyecto y ninguna sincronización lo toca; un proyecto que no lo tiene lo recibe una sola vez desde .agents/instructions/agent-project.md.template. La regla de oro: el texto va al archivo dueño, nunca de vuelta a AGENTS.md.

Quiero cambiar Dónde
el detalle de un tema su archivo en .agents/instructions/
cuándo se rutea una sección sus triggers: y paths: en el frontmatter
una regla crítica su texto en agent-critical-rules.md y su frase textual en AGENTS.md
una regla que solo tiene este proyecto .agents/instructions/agent-project.md
una línea con NEVER o MUST en una sección que nombre quién la sostiene cuando la sección no está cargada (una regla crítica, las Compact Rules de una skill) o que se declare local con <!-- binds-in-section: <motivo> -->
CLAUDE.md nada: es el shim generado (Critical Rule #15)
Gate
bun run instructions:check revisa presupuesto, router, frontmatter, frases de las reglas, quién sostiene cada NEVER o MUST, e imports. Corre dentro de bun run repo:check. Además sostiene tres candados (.context/ADR/ADR-0014-instructions-maintenance-locks.md): el router fijado a su ADR (--accept-router ADR-NNNN registra una decisión nueva), la evaluación del clasificador contra los prompts etiquetados en cada corrida, y que cada sección venga completa (frontmatter, fila en el router, prompts propios). La otra mitad la mide bun run instructions:audit: de las rutas que inyectó el hook, cuántas leyó de verdad el agente. La guía de edición completa está en .agents/instructions/README.md.

10Presupuestos

L0 tiene un techo porque Codex corta sin avisar. Los valores son constantes con nombre en scripts/lint-instructions.ts, fijadas por .context/ADR/ADR-0009-progressive-disclosure-of-instructions.md: cambiar una es una decisión nueva, con su ADR.

Nivel Constante Si se pasa
Objetivo L0_TARGET aviso: hay detalle que podría vivir en una sección
Techo L0_BUDGET error: el L0 del boilerplate no entra
Con agregados del proyecto L0_PROJECT_BUDGET error: el texto del proyecto va a agent-project.md o a una context skill
Tope de Codex CODEX_PROJECT_DOC_MAX_BYTES error siempre, digan lo que digan los otros: es el valor por defecto de project_doc_max_bytes
Ojo
El objetivo avisa a propósito. §2 entra entero por regla del dueño, y eso deja a L0 por encima del objetivo y por debajo del techo. El aviso queda a la vista para que nadie sume texto a L0 sin pensarlo. El tamaño actual lo imprime bun run instructions:check.

11Cómo se mide que funciona

Dos preguntas distintas: si el clasificador rutea lo que tiene que rutear, y si el modelo después lee lo que le rutearon.

Qué se mide Dónde
Recall, recall obligatorio y precisión del clasificador Pedidos etiquetados a mano, en inglés y en español, en cli/lib/fixtures/instruction-router-eval.json; los umbrales son su campo targets y los verifica cli/lib/instruction-router.test.ts dentro de bun run test. El recall obligatorio cuenta solo las líneas ROUTE: (que el tope no haya empujado una sección esperada a la opcional) y la precisión también: una línea opcional no obliga a nada.
Que no tarde el mismo test exige que rutear un pedido quede muy por debajo de 50 ms
Que hook y lint lean igual scripts/lib/hook-router-parity.test.ts ata el parser del hook al de scripts/lib/instructions.ts
Que el modelo obedezca auditoría de transcripciones: cada ROUTE: tendría que ir seguido de una lectura del archivo ruteado antes de la primera acción; también cuenta cuántas lecturas siguieron a un ROUTE-PENDING:
Si falla
Un pedido que no rutea lo que debía se arregla en los triggers: de la sección, y el pedido entra al set etiquetado para que no vuelva a pasar. Etiquetar de nuevo para esconder un falso positivo no vale.

12Dónde leer más