Skills and Reusable Capability
Packaging instructions, examples and assets into named units the model loads only when relevant. Progressive disclosure, when a skill beats a bigger system prompt, and how to keep a library of them from rotting.
Packaging instructions, examples and assets into named units the model loads only when relevant. Progressive disclosure, when a skill beats a bigger system prompt, and how to keep a library of them from rotting.
Your system prompt is nineteen hundred words long. It covers the incident postmortem format, the SQL review checklist, how release notes are worded, which log fields are safe to quote, and eight other procedures that each mattered enough on some Tuesday to get added. Every one of them is correct, and every one of them is sent on every request, including the requests about none of them.
That block will not get shorter, because nothing in it is wrong. By the end of this lesson you will know how to convert it into skills — named units of instruction that load only when they are relevant — where to put the boundary between one skill and the next, when a skill is the wrong answer and you needed a tool instead, and how to catch the skill that quietly stopped matching six weeks ago.
The system prompt is the right home for instructions true of every request: who the assistant is, what it refuses, the house tone. That is exactly what makes it the wrong home for the twelfth procedure. A procedure is not true of every request, and the standing layer has no way to say "only when relevant" — it is one block with one condition, always.
The obvious cost is the smaller one. You pay tokens for all of it every time; how that bill is shaped is the subject of Designing the Context Window, so take it as read. The second cost is invisible on the invoice: dilution. Instructions compete for the model's attention, so a paragraph of SQL review rules sitting above a paragraph of postmortem tone rules makes the postmortem rules fractionally less likely to be followed. The effect compounds with every addition, and the symptom is a distinctive bug report — the feature followed the wrong procedure. Someone asked for release notes and got the postmortem structure, because both instructions were present and neither said when it applied.
A skill is a named, self-contained unit of instructions — plus any files those instructions need — that the model loads only when the current request looks like a match. Several tools now ship this pattern under different names; the shape is consistent enough to learn once.
The mechanism underneath is progressive disclosure: information revealed in layers, cheapest first. The first layer is always present — for every skill you own, the model sees a name and a description a line or two long, and that catalogue is the only part you pay for on every request. The second layer is the body, read only when a description matches: the steps, the format, the rules. The third is the assets, read only when the body tells the model to open them.
skills/postmortem/
SKILL.md # description header + the procedure
template.md # the document structure to fill in
severity-matrix.md # sev definitions, owned by the SRE teamThe header at the top of SKILL.md is the part the runtime can
read without reading the file; the procedure lives below it,
untouched until the header matches.
Now the arithmetic. Twelve procedures in a system prompt cost you twelve procedures on every request. Twelve skills cost you twelve descriptions plus exactly one body, only when it applies. What the model reads barely grows while what it knows grows linearly, and that gap is the whole reason the pattern exists.
Whether a skill works at all starts with its description, because that is the only part the model ever sees unprompted. A body can be immaculate and never be read. This is the highest-leverage text in the pattern, and it is routinely written last, in ten seconds, as an afterthought.
The mistake is to write it as a summary. A summary describes the skill to someone who already knows it exists; a description is matched against a request from someone who has never heard of it.
Bad — describes the contents, so it matches nothing in particular.
---
name: postmortem
description: Guidance and standards for our postmortem process.
---Good — names the situations that should load it, in the words a person would actually use.
---
name: postmortem
description: Write or review an incident postmortem. Use when
the request mentions an incident, an outage, a sev-1 or
sev-2, root cause, a timeline of events, or action items
after something broke in production. Not for scheduled
maintenance writeups — use the maintenance-notice skill.
---"Guidance and standards for our X process" appears in every skill in the library with X swapped out, so it carries no signal for choosing between them. The first version loads on a request that says "postmortem" and misses the one that says "write up why checkout fell over last night" — which is how the request actually arrives.
Three things make a description work. Name the situation, not the contents. Use the vocabulary people genuinely type, including terms your team migrated away from, because half your users have not. And where a sibling is close, say explicitly when not to use this one; near-misses between two skills are far more common than a skill matching nothing at all.
The working heuristic is one skill, one procedure, one trigger. If you cannot state the loading condition in a single sentence without the word "and", you are looking at two skills.
Bad — one skill for everything the release process touches, so most of the body is dead weight on every load.
---
name: releases
description: Everything about our release process — cutting a
release, writing release notes, running the rollback
procedure, and handing over to the next on-call engineer.
---Good — one trigger each, so the model loads the part of the process it is in.
---
name: rollback
description: Roll back a bad deploy. Use when a release needs
reverting, or a deploy is causing errors in production.
---
---
name: release-notes
description: Draft user-facing release notes from merged pull
requests. Use when asked for a changelog or release notes.
---Cutting a release and the on-call handover get their own skills on the same principle. The combined version loads during a rollback — the worst moment to be reading anything optional — and three-quarters of what arrives is changelog wording and handover etiquette. The model weighs changelog rules while someone watches the error graph climb.
The error runs both ways. Split too coarse and half the body is irrelevant on every load, reintroducing dilution inside the skill you created to escape it. Split too fine and you get skills never loaded without their neighbour: that is one skill with a chapter break, bought at the price of two catalogue entries. If you never see one load without the other, merge.
Three homes, one test each, and the test is not about size.
The system prompt — if it must always be true
Identity, refusals, tone. Universal, non-negotiable, and short enough to afford being present on every request.
A skill — if it applies sometimes
How this team writes a thing, what the steps are, which rules govern the output. Conditional, and allowed to be long, because you only pay for it on a match.
A tool — if something has to happen
A row read, a number computed, an API called. No amount of prose gets you there; the model can only simulate the result, and simulation is where reliability goes to die.
Bad — a skill body asking the model to do the work itself.
## Checking an SLA breach
Take the incident start and end timestamps from the ticket and
subtract them. Compare the result against the customer's tier
allowance: Enterprise 15 minutes, Business 60, Starter 240. If
downtime exceeds the allowance, the incident is a breach.Good — the skill says when and how to interpret; the tool does the work.
## Checking an SLA breach
Call `check_sla_breach(incident_id)`. It returns the tier, the
allowance and the measured downtime. Report a breach only when
the tool reports one — never estimate from timestamps in the
ticket, which record when a human noticed rather than when
monitoring did.Two things go wrong in the first version and only one is arithmetic. The model will occasionally get the subtraction or the comparison subtly wrong, fluently, with no error to catch. Worse, that tier table is a copy: the day sales adds a new tier, the skill keeps confidently applying the old one, and the answer is wrong in a way that reads exactly like being right.
Which gives one more rule worth stating alone: data that changes without a deploy never lives in skill prose. Price tiers, on-call rotas, feature flags, the current schema — put a tool in front of all of it. That is where skill and tool most often ship as a pair: the tool does the thing, the skill is the manual saying when to reach for it and how to read the result.
The interesting part of a good skill is what it refuses to put
in its body. Take a brand voice skill whose real content is a
terminology table: four hundred rows of product names, approved
spellings, and phrases legal has asked you to stop using.
Inlined, every request touching customer-facing copy drags four
hundred rows into context to use three of them. As an asset, the
body says only before writing customer-facing copy, search
terminology.csv for every product name you plan to use and
follow the approved spelling exactly — and the cost becomes
proportional to the request rather than to the reference. That
is what lets a skill carry material far larger than any context
window would tolerate.
Assets have a second property worth more than the token saving: their own owner. The severity matrix belongs to the SRE team and lives where SRE maintains it; the skill points at it. The moment you copy someone else's reference into your body you have forked it, and a forked reference is a skill that will eventually start lying with total confidence.
One constraint follows. A body is read cold, with no memory of the conversation that triggered it, so "as described above" or "the usual format" breaks in a way that will not surface until the request where it matters. Self-contained is a literal requirement, not a style preference.
A team ran a postmortem skill happily for a year. Then they renamed the practice — "postmortem" tested badly with customers, so everything became an incident review — and updated the ticket templates, the runbook and the wiki. Nobody updated the skill description, which still said "postmortem" and "root cause".
The skill stopped loading. Nothing failed: no error, no latency change, no alert. The model still produced a competent-looking writeup, only a generic one, with no severity matrix, no action-item ownership rules, and none of the "no individual names in the timeline" rule legal had asked for. Three months of documents were quietly wrong, and nobody noticed because there was nothing to notice.
This is the defining failure mode of the pattern, and it falls straight out of the mechanism. A tool that breaks raises. A skill that stops matching returns a fluent answer with a piece missing. Non-invocation has no error.
So you test the trigger separately from the output: a fixed set of realistic requests, and the skill each one must load.
MUST_LOAD = [
("write up last night's incident review", "postmortem"),
("why did checkout go down on Tuesday?", "postmortem"),
("what changed in this week's release?", "release-notes"),
]
# Near misses: close enough to match, wrong enough to hurt.
MUST_NOT_LOAD = [
("review this migration for lock contention", "postmortem"),
("schedule Sunday's maintenance window", "postmortem"),
]
def test_skills_load_when_they_should(agent):
for request, skill in MUST_LOAD:
loaded = agent.run(request).skills_loaded
assert skill in loaded, f"missed on: {request!r}"
def test_skills_stay_out_when_they_should(agent):
for request, skill in MUST_NOT_LOAD:
loaded = agent.run(request).skills_loaded
assert skill not in loaded, f"fired on: {request!r}"The negative set is not padding. Without it, every failing trigger gets fixed by adding words until something matches, and you converge on a library where three skills load for every request.
The suite also earns its keep the day you change models. Skill matching is performed by a model reading descriptions, so a model change is a matching change: a description comfortably above the line on one can drift below it on another, with no code touched and nothing in the diff.
Skills rot the way internal documentation rots, with one difference that makes it worse. When documentation is wrong, a human reads it, frowns, and complains. When a skill is wrong it is obeyed — silently, at machine speed, on every matching request, by something with no way to frown. Four habits keep a library alive.
Give every skill a named owner — a person, not a team. A team-owned skill is an unowned skill with better paperwork.
Treat skills as part of a change's blast radius. When you rename a tool, change an output format, or retire the vocabulary a description depends on, the skills mentioning it are downstream of that change exactly as the tests are. Grep the skills directory in the same pull request; the rename that broke the postmortem skill would have been caught by one search.
Log which skills load, and read the totals. Zero loads in a quarter means either nobody needed it or it silently stopped matching. Those demand opposite responses, and both need a decision. Neither is fine.
Delete things. A skill nobody needs is not free — its description pays rent in the catalogue on every request. Retirement is maintenance, not failure.
None of this measures whether the skill is any good: whether the postmortems it produces are ones you would send. That is ordinary output quality, and it is the subject of Evaluating an AI Feature. Trigger tests sit a layer below, telling you only that the skill was in the room.
WHERE DOES IT GO?
true of every response, always -> system prompt
a procedure needed sometimes -> skill
something must actually happen -> tool
data that changes without a deploy -> tool, never prose
THE THREE LAYERS
name + description always in context; the catalogue
body read on a match; the procedure
assets read when the body says to; tables,
templates, examples, scripts
DESCRIPTION RULES
name the situation, not the contents
use the words a user would actually type
keep the vocabulary you migrated away from
say when NOT to use it when a sibling is close
broaden only by adding a trigger case first
BOUNDARY TESTS
one skill, one procedure, one trigger
cannot state the trigger without "and" -> two skills
half the body irrelevant on load -> split it
never loads without its neighbour -> merge them
ROT CHECKS
trigger set real requests -> assert the skill loaded
negative set near misses -> assert it did not
run both on every model change, not just code changes
grep skills/ whenever you rename a tool or a term
log loads per skill; zero loads is a question, not a state
one named human owns each skill; delete the restYou can now take a bloated standing instruction layer apart: one procedure per skill, a description written to be matched rather than read, heavy reference pushed out into assets, anything that executes pushed out into a tool, and a trigger suite that notices when a skill goes quiet.
The next step is Retrieval That Actually Retrieves. Skills are curated capability: you decide what exists, you write each description by hand, and matching happens over a list short enough to read in one sitting. Retrieval begins where that ends — when the material is ten thousand documents nobody can hand-curate and the right piece has to be found rather than declared. The failure mode rhymes, which is why it is the right thing to read next: something relevant existed, it was not loaded, and nothing raised an error.
The thing to go and do is small. Take the longest procedure in your system prompt — the one most requests would not miss — and move it into a skill verbatim. Do not improve it. Write a description, then pull five real requests from your logs that should have used that procedure and see how many load the skill. If it is fewer than five, the description is wrong, not the procedure. That gap, between a procedure being correct and a procedure being reachable, is the whole lesson.