Skip to content

10 min read

Metadata always, body on selection, files on request

On this page

A support-tooling team at a payments company has thirty-four skills installed on the agent that answers engineers' questions about delivery failures. A customer-success engineer types: the webhook retries for acct_8812 stopped after the third attempt, why? Nine seconds later the agent comes back with a correct, specific answer that names the endpoint's health state, the backoff schedule, and the exact reason the fourth attempt was never scheduled. The whole exchange cost 6,900 prompt tokens.

The same agent has 128,000 tokens of skill content installed. Thirty-four skill bodies, sixty-one bundled reference files, four executable scripts. The request that produced that answer touched 3,428 tokens of it. Everything else sat on disk and was never read, never prefilled, never paid for. That gap between what is installed and what is loaded is the whole subject, and it exists because skill content is not one thing that is either in context or out of it. It is three things, each of which enters context at a different moment, under a different trigger.

That three-way split has a name. Progressive disclosure is the practice of dividing a capability's text into layers and putting each layer in front of the model only at the moment it could be needed: a one-line description that is always present so the model can judge whether the capability is relevant, a fuller set of instructions that arrives once the model has judged that it is, and supporting files that are read only if those instructions send the agent to them. It exists because context is a fixed budget with a price and a latency attached to every token in it, and the straightforward alternative — paste all 128,000 tokens of installed skill content into the system prompt so the agent certainly has whatever it might need — would have charged that webhook question for thirty-seven times the skill content it actually used, and charged the same again to a question about the deploy freeze that needed none of it. An engineer meets the mechanism as the layout of a skill directory: the name and description in the frontmatter of SKILL.md, the body of that same file underneath, and whatever sits beside it in reference/ or scripts/. The term is borrowed from interface design, where it describes showing someone the few controls the current task needs and keeping the rest one click away; what changes here is that the reader whose attention is being rationed is a model, and the click is a file read the model has to decide to make.

What is resident before the user has typed anything

The skill that answered the question looks like this on disk:

text
skills/webhook-diagnostics/
├── SKILL.md                          780 tokens
├── reference/
│   ├── retry-policy.md             1,900 tokens
│   └── error-codes.md              4,400 tokens
└── scripts/
    └── replay_delivery.py            —

Its SKILL.md opens with the frontmatter block you met when skills were introduced:

markdown
---
name: webhook-diagnostics
description: Diagnose failed or stalled outbound webhook deliveries — retries
  that stop early, 4xx/5xx delivery errors, signature mismatches, endpoints
  marked unhealthy. Use when a customer reports missing or duplicated events.
---

## Before you start
...

That frontmatter is tier one. The name and the description — and nothing else from the file — are assembled into the system prompt on every single request the agent serves, whether or not webhooks are involved. Across thirty-four skills those pairs come to 748 tokens, averaging 22 tokens each. They are there when the user asks about webhooks, and they are there when the user asks what time the deploy freeze starts. The model cannot choose a skill it cannot see, so the routing information has to be resident before there is anything to route.

Tier two is the body of SKILL.md, the 780 tokens below the frontmatter. It enters context only after the model has decided this skill is the relevant one, and it arrives as the result of a read the model itself requested. Tier three is everything else in the directory: the two files under reference/, the script under scripts/. Those enter context only if something in the body tells the agent to read them and the agent does.

The tiers are not sized by importance. They are sized by how many requests need them. The description is needed by every request in order to be ruled out; the body is needed by the small fraction of requests that are about webhooks; error-codes.md is needed by the smaller fraction of those that hit an error code the body does not already explain.

Following one request from question to answer

Here is the same request as a sequence of model calls, with the prompt size at each step. The agent runtime is doing the file reads; the model is deciding when.

Sequence: engineer → agent runtime (retries stopped after the third attempt); agent runtime → model (call 1 — 4,120 tok (system + 34 descriptions, 748 tok)); model → agent runtime (use skill webhook-diagnostics); agent runtime → model (call 2 — 4,920 tok (+ SKILL.md body, 780 tok)); model → agent runtime (read reference/retry-policy.md); agent runtime → model (call 3 — 6,850 tok (+ retry policy, 1,900 tok)); model → agent runtime (answer, 210 tok); agent runtime → engineer (answer).

Three prefills, each one larger than the last, each one triggered by a decision the previous call produced. Nothing arrives speculatively. The 4,400-token error-codes.md never enters the trace, because the body's section on stalled retries explains the health-state rule directly and only points at the error-code table for delivery errors in the 4xx range, which this request did not involve.

The costs of the arrangement are legible in that diagram, and they are worth naming now because later chapters turn on them. Two extra round trips is the price of not carrying 128,000 tokens; each of those hops has its own time to first token and its own decode. And every step after the first depends on a judgment the model made with less information than it will eventually have. The selection in call 1 was made from 22 tokens of description, not from the 780-token body that would have told it definitively whether the skill fits.

The step from tier two to tier three deserves a mechanical look, because it is a different kind of event from the step from tier one to tier two. Once the model names a skill, the runtime loads that body; the model asked for it and there is no further decision to make. A bundled file arrives instead through an ordinary file read that the model issues as a tool call, indistinguishable in form from any other tool call it might make:

json
{
  "type": "tool_use",
  "name": "Read",
  "input": { "file_path": "skills/webhook-diagnostics/reference/retry-policy.md" }
}

Nothing in the runtime obliges the model to emit that block. The body can say read reference/retry-policy.md before answering in the plainest language available, and on some proportion of runs the model will go straight to the answer because it judged that it already had enough. From the runtime's side, a run that skipped the read deliberately and a run that never needed it look identical, so neither produces a warning. The guarantee at the second boundary is a runtime guarantee; the guarantee at the third is a behavioural tendency.

The whole exchange is worth totalling. Of the 128,000 tokens installed on this agent, 3,428 entered the model's context — 2.7% of what was available. Of those, 748 would have been paid on any request whatsoever, including one about the deploy freeze, which leaves 2,680 tokens that were loaded because this particular question needed them. Those proportions are what the mechanism exists to produce, and they are also what makes the arrangement fragile in a specific way: the 97.3% that stayed on disk includes both the material this request genuinely did not need and any material it needed and did not get.

tierwhat enterstriggerpaid on
metadataname + descriptionnothing — always presentevery request, all skills
bodyrest of SKILL.mdmodel selects the skillrequests that select it
bundled filesreference/, scripts/, anything elsebody instructs a read, model performs itrequests whose body asks and complies

The third column is where most reasoning about this goes wrong. Metadata has no trigger; it is unconditional. The body's trigger is a model decision. A bundled file's trigger is a model decision about an instruction, which is a weaker guarantee than it sounds, and the difference is the subject of chapter five.

Two ways this goes wrong, in opposite directions

The 748 resident tokens are the agent's entire basis for choosing. That basis is thin enough to make false matches routine. This same installation carries a second skill:

markdown
---
name: event-replay
description: Re-send historical platform events to a customer endpoint after an
  outage or an endpoint fix. Use when events were missed and need redelivery.
---

Read those two descriptions as the model sees them, with no bodies and no examples, against the question the webhook retries stopped after the third attempt. Missed events, a customer endpoint, a delivery that did not complete — the event-replay description is not obviously wrong. On a 300-question replay of real traffic, the agent selected event-replay for 8% of questions that webhook-diagnostics should have handled, and the resulting answers were about how to trigger a redelivery rather than about why the retries stopped. Those answers were fluent, on-topic, and useless, and no error was logged anywhere, because from the runtime's point of view a skill was selected and a response was produced.

The opposite failure is quieter still. Consider what happens if the author of webhook-diagnostics had moved the health-state rule — the thing that actually explains why a fourth attempt is never scheduled — out of the body and into reference/retry-policy.md, leaving the body with a closing line that says consult reference/retry-policy.md for the full backoff and health-state rules. The skill still selects correctly. The body still loads. But the model now has to decide, mid-turn, whether that read is worth making, and on a plausible fraction of runs it decides it already knows enough. It then answers from what it does know about exponential backoff in general, which is enough to produce a paragraph that reads like an answer and does not contain the health-state rule at all. The skill is perfectly written; the material it needs sits one tier further out than the run's behaviour supports.

The general shape is that each tier boundary is a place where content can become invisible, and that invisibility never announces itself. A description that does not match how people phrase the problem makes the whole skill invisible. A prerequisite pushed into a bundled file makes that prerequisite invisible on the runs where the fetch does not happen. In both cases the agent produces an answer.

What deferral is not

The word that comes to mind for a backend engineer is lazy loading, and the analogy is close enough to be worth stating precisely so the differences show. A lazily imported Python module is loaded the first time something references it, by an interpreter that has no discretion: the reference is in the code, the module gets imported, and if it cannot be found you get an ImportError at that exact line. Deferred skill content is loaded when a language model decides to load it, and the model has discretion at both boundaries — it may not select the skill, and having selected it, it may not perform the read the body asks for. There is no ImportError. There is an answer that came from somewhere else.

It is also not retrieval in the sense of a vector search over a corpus. No embedding is computed, no similarity threshold is applied, no top-k is returned. The model reads thirty-four descriptions in its prompt the way it reads any other text and picks one. That has consequences in both directions: it means the selection can use reasoning that an embedding cannot, such as noticing that the user said "duplicated" and that only one of the two candidate skills mentions duplicates; and it means the selection cost is not a fixed index lookup but scales with how many descriptions are competing. Retrieval over descriptions is a thing you can build on top, and chapter three treats it as one of the architectures available when the resident set gets large.

Nor is the resident description a summary of the body in any mechanical sense. Nothing derives it, compresses it or keeps it in step: somebody wrote those two sentences by hand when the skill was created, and they stay exactly as written while the body underneath them is edited by other people over the following year. A skill whose body has quietly grown to cover signature verification will keep routing on a description that never mentions signatures, and the only symptom is that requests about signatures land somewhere else. The resident tier is authored content with a maintenance cost, and it is the tier least likely to be reviewed, because reviewers read the part of the file that contains the instructions.

Finally, progressive disclosure does not reduce the total token cost of a run that genuinely needs everything. If a question requires the body, both reference files and a look at the script, the disclosure boundary has bought nothing on that request and has cost three extra round trips. What it buys is that such requests are rare, and that the requests which are not about webhooks at all pay 22 tokens rather than 7,080.

The tier that never gets deferred

Two of the three tiers scale with what a request needs. The first scales with what is installed. Those 748 tokens are resident because thirty-four skills are installed, and the number would be identical if every one of them were about a subject nobody has asked about in six months. Nothing in the mechanism deletes an unused description; there is no eviction, because eviction would mean the model could not select the skill at all.

That makes the resident tier the one place where the argument "it only loads when relevant" stops being true, and it is the place where the cost of a growing skill library actually accumulates. Working out how fast it accumulates, and what it buys, is arithmetic worth doing before the library is large rather than after.

Progressive disclosure — read the current version at https://daviddominguez.dev/learn/en/progressive-disclosure/metadata-always-body-on-selection-files-on-request

Back to top