Writing a Skill an Agent Will Actually Read
A skill is a self-contained Markdown directive with a little YAML frontmatter on top, sitting in its own directory. That description makes it sound like documentation, which is exactly the trap. The interesting part is not what a skill contains. It's when the agent pays for it.
Here is the sentence we keep in our own skill registry, and it is the whole
design: a skill is a directive the agent reads on demand via a read_skill
tool, rather than something injected into every prompt — so the agent pays the
token cost only when the skill is actually relevant to the turn.
Read that twice, because every rule below is a consequence of it. On demand is not an implementation detail you can bolt on afterward. It is the property that makes a skill a skill instead of a system prompt with extra steps. And it means the best skill you can write is one that is absent from most of the agent's turns — present, correct, and completely unloaded.
What you are actually writing
Structurally there is almost nothing to it:
---
name: design-core
description: >-
Read BEFORE designing any visual deliverable — decks, documents, pages.
Carries the non-negotiable design bans and the layout method. Load this
first, then the medium-specific skill.
---
# Core design directive
## Absolute anti-slop bans
- No Times. No Arial.
- No pure black text on pure white.
- No default 1px grey borders.
...
Frontmatter, a body, a directory. The agent's harness knows the file exists and
knows what the description says. It does not know a word of the body until it
asks. That gap — the harness knowing the name while deferring the content —
is the entire budget you have to work with, and four rules follow from spending
it well.
Rule 1: load it through a tool, not a shell command
The agent could read the file with cat. Same bytes into context, no tool
required. Do not do this.
Loading through a dedicated tool keeps the raw directive text out of the
user-facing terminal stream. It rides to the model as a tool result; the UI
shows a compact card. If the agent cats the file instead, the user watches a
wall of imperatives about font choices scroll through their chat — a
conversation they were never party to, rendered as if it were output.
The tokens are identical. The product is worse. That asymmetry is worth internalizing, because it's the first place skill design stops being a context problem and starts being an interface problem. Your directive is addressed to the model. Anything that leaks it to the human is a bug, even when it's free.
Rule 2: the description is the routing logic
If bodies load on demand, something has to decide when. That something is the
description field — and it works because the tool that loads skills enumerates
the valid skills in its own description. The model sees the full menu of names
and triggers without loading a single body.
Names in the prompt. Bodies on demand.
We apply the same principle one layer down, to tool schemas: every tool name is in the system prompt, but the parameter schemas are fetched only when needed. The guidance we give the fetching tool says it plainly — your tools are ALL listed by exact name in the system prompt; this is NOT for discovery, it fetches the param schema you don't yet have. Discovery is free. Detail is not. Skills are that idea applied to prose instead of JSON.
Which reframes what your description is for. It is not a summary of the skill.
Nobody is browsing. It is a trigger — the condition under which a model that
has never read your body should decide it needs to. Write it as an instruction
about when, not a précis of what:
- Summary: "Guidance and conventions for building presentations."
- Trigger: "Read before creating any slide deck, after the core design skill."
The second one routes. The first one describes.
Selection should be boring. Ours is prompt-driven routing through a hard-coded table — deliberately not embedding search, not auto-selection. A preamble names the deliverable type and the skill to load. Clever selection fails in ways you cannot reproduce; a table fails in ways you can read.
Rule 3: layer, don't lump
The instinct when writing a skill is to make it complete. Resist it. Our six shipped skills are deliberately layered: one medium-agnostic core design directive is read before designing anything, and the medium-specific skills build on top of it with the format rules for exactly one output type. The agent reads the core, then the layer.
| Skill | Role | Size |
|---|---|---|
| Core design directive | Read before designing anything | ~7.5 KB |
| Presentation | Slide-specific rules on top of the core | ~8 KB |
| Self-contained HTML | Single-file page rules | ~5.6 KB |
| Document (PDF) | Print-oriented format rules | ~3.2 KB |
| Document (DOCX) | Editable-document format rules | ~2.7 KB |
| Spreadsheet (XLSX) | Tabular output rules | ~2.5 KB |
The sizes are the point. Not one of them is a manual. The core carries what is true of every visual deliverable; each layer carries only what is true of its medium and nothing that the core already said. A spreadsheet task loads roughly 10 KB total and never sees a word about slide transitions.
The failure this prevents is subtle. A single lumped skill isn't just bigger — it forces every task to carry every medium, which means the moment it starts crowding the context window, someone "fixes" it by making it vaguer. Layering lets you stay specific, because specificity is cheap when it's scoped.
Specificity is also what makes a skill bite. Our core design directive opens with "Absolute anti-slop bans": no Times, no Arial, no pure black on pure white, no default 1px grey borders. Concrete prohibitions beat vague encouragement, every time. "Use tasteful typography" is a sentence a model can agree with while producing Arial. Skills carry a persona and hard bans because that's what survives the trip through a generative process. And when prose is the wrong instrument, ship an executable script alongside the markdown — a skill directory is not obligated to be only words.
Rule 4: reference big data by path, never by content
Our presentation skill needs a 54 KB theme catalog. It does not contain a 54 KB theme catalog.
The loader appends the catalog's resolved absolute path to the skill text, so the
agent can json.load it inside run_python without the prompt ever carrying the
data — and the skill explicitly instructs the agent not to dump the thing into
the chat. A 54 KB asset costs a path.
Generalize this. Any time your skill wants to contain a lookup table, a schema dump, a catalog, or a corpus of examples, it wants a path instead. The agent has a filesystem and an interpreter. Prose is for judgment; data is for disk. A skill that inlines its data spends the whole context window on a lookup that a single line of Python does better.
The part where we admit it's hard
The skills we ship inside the product run 2.5–8 KB. The skills our own engineers write for their internal dev tooling run 25–85 times larger. One internal QA skill is 215 KB — far, far past any progressive-disclosure guidance we would give anyone, including ourselves.
The detail that makes it instructive rather than embarrassing: that skill has
an envs/ subdirectory. The author knew the pattern. They applied it to the
peripheral material and not to the main body.
That's the failure mode, and it's always the same one. Nobody sits down to write a 215 KB skill. They write a good 8 KB one, then answer a question in it, then another, then paste in the thing that keeps coming up — and each addition is locally correct. The skill grows into a manual because nobody pays the cost of loading it at review time. The author reads the diff. The agent reads the whole file, on every task that touches it, forever. The discipline is easy to state and genuinely hard to keep, and pretending otherwise would make the rest of this post less useful.
The pattern that actually holds the line is two-tier. One of our better internal skills opens by declaring what it is: it orients and routes, and the deep, authoritative reference is a longer document — the single source of truth. Read it before non-trivial work; don't duplicate it here. Eight kilobytes of skill pointing at twenty-nine kilobytes of reference.
Skills should route to depth, not contain it. The skill's job is to know that the reference exists, say when it matters, and get out of the way. The reference's job is to be right. Conflating those two jobs is how you get 215 KB.
How to know yours is good
Read your skill and ask when it should not load. If you can't answer crisply, your description isn't a trigger and your routing will be guesswork. Then check that nothing in the body would be equally at home in the core, in a sibling layer, or in a file on disk — those are the three things a bloated skill is made of.
Then apply the test that subsumes all of it:
If your skill would be equally useful pasted into every system prompt, it isn't a skill. It's a system prompt you haven't admitted to.
That's not a rhetorical flourish; it's a diagnostic with a fix on either side. If the content genuinely belongs in every turn, move it to the system prompt and stop paying the indirection. If it doesn't — if it's for decks, or for one language, or for the third Tuesday of the release cycle — then earn the deferral. Sharpen the trigger, split the layer, push the data to a path.
A skill earns its keep by being absent most of the time. Write it so the agent is glad to see it, and doesn't have to.
Written by CatalEx Engineering. We build the AI operating layer for AI-native companies — one platform to build, deploy, and run AI agents in production. More at catalex.co.