Lesson 2 of 8Foundations·12 min read

Agent Skills: Packaging Expertise So Any Model Can Load It On Demand

A skill is a folder of instructions, scripts and references an agent loads only when a task needs it. Learn the SKILL.md format, progressive disclosure, and how to make one skill work across Claude, Gemini and OpenAI.

After this lesson you can

  • Write a SKILL.md with a description that triggers reliably and a body that stays out of context until needed
  • Explain progressive disclosure and why it beats one giant system prompt
  • Load the same skill into Claude, Gemini and OpenAI agents

What a skill is (and what it is not)

A skill is a self-contained package of procedural knowledge: how to produce a valid Excel file with formulas, how your team writes a PR description, how to run your company’s deploy checklist. It lives on disk as a folder — a SKILL.md with YAML frontmatter (name, description) and a markdown body, plus optional scripts, templates and reference files.

The idea was popularised by Anthropic’s Agent Skills (used by Claude Code, the Claude apps and the Agent SDK) and is now an open format many harnesses read. But the concept is provider-neutral: a skill is just structured instructions the agent can discover, decide to load, and then follow. Gemini CLI has extensions and GEMINI.md context files, OpenAI Codex reads AGENTS.md and supports skills, and every SDK lets you inject text into the system prompt — which is all "loading a skill" ultimately means.

A skill is not a tool. A tool is a function the model calls and gets a result from. A skill is knowledge about how to use tools (and the model’s own abilities) well. Skills often ship with scripts the model runs as tools, but the skill itself is the instructions.

Progressive disclosure: the reason skills exist

The naive approach — put every instruction in the system prompt — dies at scale. Twenty procedures at 2,000 tokens each is 40k tokens on every single turn, most of it irrelevant to the current task, all of it diluting the model’s attention. Skills fix this with three levels of disclosure.

  • Level 1 — metadata (always in context): the name and a one-line description of when to use the skill. ~50–100 tokens per skill. This is what the model uses to decide relevance.
  • Level 2 — the SKILL.md body (loaded when the skill is chosen): the actual procedure, typically 500–5,000 tokens.
  • Level 3 — bundled files (read only when the body points at them): reference docs, schemas, scripts, example outputs. Unbounded, and never loaded unless needed.

Anatomy of a good SKILL.md

The description is the most important line you will write: it is the only thing the model sees before deciding to load the skill. Say what the skill does and when to use it, using the words a user would use. Keep the body imperative and specific — checklists, exact commands, the two or three mistakes that always happen — and push anything long into reference files with a clear pointer.

The same skill folder works everywhere. Only the loader differs.
markdown
# .claude/skills/release-notes/SKILL.md
---
name: release-notes
description: Write release notes from a git log or PR list in our house style.
  Use when asked for release notes, a changelog, or "what shipped this week".
---

# Release notes

1. Group changes into: Features, Fixes, Breaking, Internal.
2. Lead each bullet with a verb; ≤ 20 words; link the PR.
3. Breaking changes get a "Migration" sub-bullet.
4. Run `python scripts/lint_notes.py notes.md` before returning.

See [style.md](style.md) for tone examples and banned words.

# Claude Code, the Claude apps and the Agent SDK discover this folder
# automatically. Nothing else to wire up — the description is the trigger.

Loading a skill through the API

Outside a coding harness you implement progressive disclosure yourself, and it is about thirty lines. Index the skills (name + description) into the system prompt. Expose one tool, load_skill(name), that returns the SKILL.md body. The model calls it when the task matches, and the body lands in context only for that conversation. Bundled scripts become ordinary tools.

A minimal skill loader: descriptions always in context, bodies loaded on demand via a tool.
python
from pathlib import Path
import yaml
from anthropic import Anthropic

SKILLS = {}
for f in Path(".claude/skills").glob("*/SKILL.md"):
    fm, body = f.read_text().split("---")[1:3]
    meta = yaml.safe_load(fm)
    SKILLS[meta["name"]] = {"description": meta["description"], "body": body, "dir": f.parent}

index = "\n".join(f"- {n}: {s['description']}" for n, s in SKILLS.items())
SYSTEM = f"""You have skills. Call load_skill(name) BEFORE a task that matches one.
Available skills:
{index}"""

TOOLS = [{
    "name": "load_skill",
    "description": "Load the full instructions for a skill by name.",
    "input_schema": {"type": "object",
                     "properties": {"name": {"type": "string", "enum": list(SKILLS)}},
                     "required": ["name"]},
}]

client = Anthropic()
messages = [{"role": "user", "content": "Write release notes for PRs #412-#430"}]
while True:
    resp = client.messages.create(model="claude-sonnet-4-5", max_tokens=2000,
                                  system=SYSTEM, tools=TOOLS, messages=messages)
    messages.append({"role": "assistant", "content": resp.content})
    if resp.stop_reason != "tool_use":
        break
    results = [{"type": "tool_result", "tool_use_id": b.id,
                "content": SKILLS[b.input["name"]]["body"]}
               for b in resp.content if b.type == "tool_use"]
    messages.append({"role": "user", "content": results})
print(next(b.text for b in resp.content if b.type == "text"))

# With the Claude Agent SDK the loader is built in:
#   ClaudeAgentOptions(setting_sources=["project"])  → picks up .claude/skills/

Designing skills that trigger correctly

Skill failures are almost always description failures. Under-triggering: the description is too narrow or uses jargon the user never says. Over-triggering: it is so broad the model loads it for everything, wasting context. Write descriptions as "Use when the user asks for X, Y or Z", test them against a list of twenty realistic requests, and keep an eye on how often each skill loads in production logs.

Second most common failure: the body assumes tools the agent does not have. State prerequisites at the top ("requires python3 and openpyxl") and give the agent a fallback. Third: bodies that are essays. A skill is an operating procedure; write it like a runbook.

  • Description: what + when, in the user’s vocabulary, under 1,024 characters
  • Body: numbered steps, exact commands, known pitfalls — under ~5k tokens
  • References: anything longer goes in a linked file; scripts go in scripts/ and are called, not pasted
  • Version skills in git alongside the code they operate on; review them like code

Interview questions this lesson prepares you for

  1. What is progressive disclosure and what problem does it solve in agent design?
  2. How would you let a single agent handle fifty different internal procedures without a fifty-page system prompt?
  3. What is the difference between a skill and a tool? Can a skill contain tools?
  4. A skill is never being loaded in production. What do you check first?