Interview prep
PatternContext and knowledge

Progressive Disclosure

Revelação Progressiva

Progressive Disclosure is an AI design pattern for LLM apps and agents, in the Context and knowledge group. Show the model only a summary of each skill and load the details when it asks.

Element
20/70
Level
Advanced
Read
6 min
Updated
On this page 17
  1. 01Intent
  2. 02Problem
  3. 03Solution
  4. 04Structure
  5. 05Pseudocode
  6. 06Real-world scenarios
  7. 07Applicability
  8. 08When not to use
  9. 09How to implement
  10. 10Variations
  11. 11Cost, latency, and failure modes
  12. 12How to evaluate
  13. 13In production
  14. 14Pros and cons
  15. 15Relations
  16. 16Code
  17. 17Check your understanding
  18. ··Quick answers

Intent

Show the model only a short summary of each available skill, tool, or document, and load the full details when it asks. It is for agents with many capabilities, where loading everything on every call fills the context and muddles the choice.

In one sentence
A book’s table of contents: you read the chapter titles and open only what interests you.

Problem

Gabriel maintains a back-office agent that can do many things: reconcile statements, issue invoices (including Brazil’s NF-e electronic invoice), generate commission reports, open tickets with carriers, update supplier records. Each capability has detailed instructions (step by step, exceptions, formats), examples, and its own tools.

With everything in the prompt, each task’s initial context exceeds 40,000 tokens. Most of it is irrelevant to the task at hand. The agent mixes up reconciliation rules with invoicing rules, and every call is expensive before it does anything.

That shows up in the trace as Prompt Bloat: pages of procedure that will not be used. It also shows up as the wrong tool pick, a sibling of Tool Sprawl.

A router that picks the group at the start helps, but some tasks discover midway that they need another capability. “Reconcile the card charges and, if an invoice is missing, issue the NF-e” starts in one topic and ends in another.

The question this pattern answers: how does an agent have access to many capabilities without carrying all of them in its context all the time?

Solution

Organize capabilities in two levels:

  • Index: for each capability, a name and one or two sentences on when to use it. That is all that goes into the initial context.
  • Detail: full instructions, examples, scripts, and tools, kept outside the context.

The agent receives the index and a tool to load the detail (load_skill(name)). When it decides it needs a capability, it loads it and follows it. Details can have more levels: the main file points to reference files, loaded only if needed.

The same applies to tools: the initial list carries names and short descriptions, and full definitions load on demand. Some providers offer this natively, with tool search.

Worked example

The index Gabriel puts in the system prompt fits on one screen:

Skills (load with load_skill before you run them):
- card-reconciliation: use to match card receipts to the statement and settle the receivable.
- invoice-issuing: use to issue, cancel, or look up NF-e invoices.
- commission: use to calculate and export salesperson commission.
- logistics: use to open a carrier ticket or look up tracking.

The task arrives: “Reconcile yesterday’s card receipts”. The agent calls load_skill("card-reconciliation") and only then sees the step by step, the acquirer’s occurrence codes, and the settle_receivable tool. The invoice skill never enters.

An illustrative account. Twelve skills at 3,000 tokens each add up to 36,000 tokens on every turn. The twelve-line index takes about 400 tokens. Loading one skill adds 3,000 once. On a six-turn loop, you go from about 216,000 tokens of instruction to about 22,000. The price is one extra turn, the load_skill turn.

The short description is the contract. If every line says “useful in back office”, the agent loads all twelve. If each line states the task’s verb (“match card receipts”), it picks right.

Check +20 XP on first tryGabriel's prompt is 40,000 tokens and the agent mixes NF-e rules with reconciliation. He moves to a 12-skill index with two sentences each. What needs to happen on the 'reconcile yesterday's card' task?

Structure

flowchart TD
  T(["1. Task"]):::io --> A(["2. Agent with the index"]):::llm
  IDX[("Index: name and when to use")]:::store --> A
  A -->|"I need reconciliation"| L["3. load_skill"]:::tool
  SK[("Full skills")]:::store --> L
  L --> A
  A --> TL["4. The skill's tools"]:::tool
  TL --> A
  A --> OUT(["5. Result"]):::io
  1. The task arrives.
  2. The agent starts with the short index of every capability.
  3. When it needs one, it loads the full instructions.
  4. That capability’s tools become available.
  5. The result goes out, with the context carrying only what was used.

Pseudocode

schema SkillIndexEntry { name: string, when_to_use: string }

INDEX = skills.index()                                  # a few lines per skill
tool load_skill(name) -> { instructions: string, tools: list[Tool] }

function run(task):
    state.system = BASE_PROMPT + render(INDEX)          # 2
    tools = [load_skill]
    while budget.check():
        reply = llm_with_tools(state.system, tools, state)
        if reply.is_final: return reply.text            # 5
        for call in reply.tool_calls:
            if call.name == "load_skill":
                skill = skills.load(call.args.name)     # 3
                state.add(observation(call, skill.instructions))
                tools += skill.tools                    # 4
                trace.span("skill_loaded", call.args.name)
            else:
                state.add(execute(call))

Real-world scenarios

  • Coding agent. Instructions for running tests, repository conventions, and release procedures live in files loaded only when the task touches that topic.
  • Back-office automation with durable execution. Each process (reconciliation, NF-e invoicing, collections) is a skill with its own instructions and tools, loaded when the agent reaches that stage.

Applicability

  • The agent has many capabilities, each with long instructions.
  • Most tasks use few capabilities.
  • A task can discover midway that it needs another capability.
  • The initial context is too large and too expensive.

When not to use

  • Few capabilities with short instructions. Put everything in the prompt.
  • The topic is always known at the start. A Tool Router decides once, without relying on the model to ask.
  • What needs loading is factual knowledge for answering questions. That is RAG.
Check +20 XP on first tryThe agent has 5 tools, short descriptions, and picks wrong because two of them do almost the same thing. A colleague wants load_skill for each. What is the first step?

How to implement

  1. Package each capability: instructions, examples, tools, references.
  2. Write a short description for each, focused on when to use it.
  3. Put the index in the agent’s prompt, along with the load tool.
  4. Make loading append instructions and tools to the context, without removing what is there (so the cache stays valid).
  5. Record on the trace which capabilities each task loaded.
  6. Evaluate: does the agent load the right capability? Does it load things it does not need?
  7. Revise index descriptions when the eval shows wrong choices.

Common traps

  • A generic description. “Useful in back office” does not discriminate. The task’s verb does.
  • Loading by replacing the prompt. If you swap the system prompt, the prefix cache dies. Append at the end.
  • A stale index. A new skill on disk with no index line is an invisible skill. Generate the index from the files.
  • Treating a skill as RAG. Search returns a paragraph. A procedure has to be followed in full. RAG is for facts; disclosure is for a way of operating.

Variations

  • Skills as files. Folders with a main file and references, read by the agent with read tools. Simple and versionable.
  • Provider tool search. Tools are marked for deferred loading and the model searches for the ones it needs.
  • Several levels. The index points to skills, which point to references, loaded one at a time.
  • Preloading by route. A router loads the most likely skill at the start, and the agent can still ask for others.

Cost, latency, and failure modes

Aspect What happens How it shows up in the trace
Context Small at the start, grows only with what is loaded input tokens on the first step
Calls One extra turn to load each skill skill_loaded spans
Cache Loading appends at the end; the prefix stays cached stable cache_read_input_tokens
Failure: skill not loaded The agent tries without the instructions a capability’s typical error with no prior skill_loaded
Failure: loads everything Vague descriptions lead the agent to load several high skills per task
Failure: stale index A new skill with no index description a capability that is never used

How to evaluate

  • Correct choice: on tasks with a known required skill, was it loaded?
  • Unnecessary loads per task.
  • Quality and cost compared with the prompt that loads everything.
{ "task": "Reconcile yesterday's card receipts", "expected_skills": ["card-reconciliation"], "unexpected_skills": ["invoice-issuing"] }

Include tasks that cross skills on purpose (“reconcile and, if needed, issue the invoice”). The right agent loads the second one midway, not both on the first step.

Check +20 XP on first tryThe agent issues an NF-e without loading invoice-issuing. The trace has no skill_loaded for that skill. What do you look at first?

In production

  • One span per load, with the skill’s name and size.
  • A per-skill usage dashboard: never-loaded skills can leave the index or get a better description.
  • An alert for tasks that load many skills.

Pros and cons

Pros Cons
A small, cheap initial context One extra turn per load
Many capabilities without confusion Depends on good index descriptions
Skills versioned and testable separately The agent may not load when it should
A stable prefix for the cache More structure to organize

Relations

  • Smells it treats: Prompt Bloat and, in part, Tool Sprawl, when the volume is instructions and definitions.
  • Nearby refactoring: Consolidate Tools comes first if there is a duplicate. Tool Router picks the group at the start; disclosure lets the agent itself open more along the way.
  • Do not confuse with: RAG, which retrieves facts. What you disclose here is a procedure the agent must follow.

Code

The Python tab builds the skill index from files and gives the agent a tool to load the details; the TypeScript tab uses the provider’s tool search for large tool sets.

Python. Use this when skills live in folders in the repository, each with a main file.

from pathlib import Path

import yaml

SKILLS_DIR = Path("skills")  # skills/card-reconciliation/SKILL.md, skills/invoice-issuing/SKILL.md, ...


def read_skill(path: Path) -> tuple[dict, str]:
    raw = path.read_text(encoding="utf-8")
    _, front, body = raw.split("---", 2)  # YAML header with name and when_to_use
    return yaml.safe_load(front), body.strip()


def skill_index() -> str:  # 2. only name and when to use
    lines = []
    for f in sorted(SKILLS_DIR.glob("*/SKILL.md")):
        meta, _ = read_skill(f)
        lines.append(f"- {meta['name']}: {meta['when_to_use']}")
    return "Available skills (load with load_skill before using):\n" + "\n".join(lines)


LOAD_SKILL = {
    "name": "load_skill",
    "description": "Loads the full instructions of a skill from the index. Use it before doing that skill's task.",
    "input_schema": {"type": "object", "properties": {"name": {"type": "string"}}, "required": ["name"]},
}


def load_skill(name: str) -> str:  # 3
    path = SKILLS_DIR / name / "SKILL.md"
    if not path.exists():
        return f"Skill '{name}' does not exist. Check the index."
    _, body = read_skill(path)
    return body  # the skill's tools are added to the list by the loop

SYSTEM = "You are the back-office agent.\n\n" + skill_index()

What you gained: skills as versioned files, with the index generated automatically. What you lost: adding the skill’s tools to the list is up to your loop.

TypeScript. Use this when there are many tool definitions and you want the provider to do the search.

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

// Dozens of tools: only the search tool is visible from the start;
// the rest load when the model finds them through search.
const tools = [
  { type: "tool_search_tool_bm25_20251119", name: "tool_search_tool_bm25" },
  ...ALL_BACKOFFICE_TOOLS.map((t) => ({ ...t, defer_loading: true })),
];

export async function step(messages: Anthropic.MessageParam[]) {
  return client.messages.create({
    model: "claude-opus-5-5",
    max_tokens: 8000,
    system: "You are the back-office agent. Find the right tool before acting.",
    tools: tools as Anthropic.Messages.ToolUnion[],
    messages,
  });
}

What you gained: the provider handles searching and loading definitions. What you lost: search covers only tools; long per-skill instructions still need the file scheme.

Check your understanding

Check +20 XP on first tryThe agent loads all 12 skills on every task. The index descriptions say 'useful in back office'. Which change attacks the cause?

Check +20 XP on first tryYou added a new skill on disk. It is never loaded. The index is generated from the files. What most likely is missing?

Check +20 XP on first tryA colleague wants to index the skill files in a RAG instead of using load_skill. Why does that usually fail?

Quick answers

What is Progressive Disclosure?

Progressive Disclosure is an AI design pattern for LLM apps and agents, in the Context and knowledge group. Show the model only a summary of each skill and load the details when it asks.

Which smells does the Progressive Disclosure pattern help cure?

Tool Sprawl and Unused Tools.

Where does Progressive Disclosure show up in practice?

In the real-world scenarios Coding agent and Back-office automation with durable execution.

Go further

Relations

Smells it helps cure 2
Refactorings that lead here 1
Search