Writing good skills
Nothing here is enforced by the schema — a skill can be as vague, overstuffed,
or mis-scoped as you write it to be. A badly written skill doesn’t error; it
just sits there costing a line of system prompt and never getting loaded, or
getting loaded for the wrong conversations. For the mechanical reference —
SKILL.md format, progressive disclosure, granting, script execution — see
Skills. This guide is the judgment calls the
reference page doesn’t make for you.
Give it one job, the same as an agent
Section titled “Give it one job, the same as an agent”A skill titled “operations” that covers incident response, deployment
rollbacks, and on-call handoff is three skills wearing a trenchcoat — its
description has to somehow signal all three situations at once, which
means it either routes correctly for none of them or the agent loads it
“just in case” every time any of the three come up, defeating the point of
loading it selectively at all. Split it: incident-triage,
deployment-rollback, oncall-handoff — three skills an agent can be
granted independently, each with a description that names exactly the one
situation it covers.
Write the description like a router, not a summary
Section titled “Write the description like a router, not a summary”description is the only thing a skill costs when it isn’t in use, and it’s
the only signal the agent has to decide whether to load the rest at all — see
Skills — Writing a good description
for the mechanical version of this. The judgment call underneath it: write
for when, not what.
- Routes correctly: “Use when the user is reporting a production incident and needs help classifying severity, identifying the right escalation path, or drafting an initial status update.”
- Doesn’t route at all: “Incident response skill.” — true, but gives the agent nothing to match a real conversation against. It either never fires or fires on a hunch.
A useful test: could a colleague who’s never seen the skill’s body correctly guess, from the description alone, whether this specific message should trigger it? If the answer is “not really,” the description is a title, not a router.
Put the routine in the body, the exceptions in bundled files
Section titled “Put the routine in the body, the exceptions in bundled files”SKILL.md’s body (Level 2) is what the agent reads once it’s decided the
skill is relevant — put the workflow it should follow every time here:
numbered steps, what to gather, what to output. A bundled file (Level 3) is
for the part that’s long, changes independently of the workflow, or isn’t
needed on every single invocation — a full escalation matrix, a style guide
with a dozen worked examples, a reference schema. The body should tell the
agent when to go read the bundled file, not duplicate its contents; if
you’re pasting the reference file’s content into the body “just in case,” the
split isn’t earning its keep.
A skill with no bundled files at all is completely normal — most are. Reach for one only when the body would otherwise be uncomfortably long or contain material that’s genuinely reference-shaped rather than instructional.
Skill or system prompt?
Section titled “Skill or system prompt?”Covered in full in Writing good agents — Skill or system
prompt? — the
short version: soul.md is for what the agent always is, a skill is for what
it sometimes needs. If you’re writing a new skill and finding yourself
copying half of it into every agent’s soul.md anyway “to be safe,” that’s a
sign either the description isn’t routing correctly (fix the description
first) or the content genuinely isn’t situational and belongs in soul.md
after all.
A skill is not a way to smuggle in tools
Section titled “A skill is not a way to smuggle in tools”A skill can tell an agent to use tools it’s already been granted — “use the
filesystem tool to read X” — but granting the skill does not grant the
tool. tools: and skills: are independent lists in agent.yaml; an agent
without grafana in its tools: still can’t call Grafana no matter what a
loaded skill’s body says to do. Write a skill’s instructions assuming the
tools it references are already granted, and say so explicitly in the
skill’s own text (“requires the filesystem and fetch tools”) so an
author granting the skill to a new agent knows what else it needs.
Bundled scripts are the exception, not the default
Section titled “Bundled scripts are the exception, not the default”A skill can bundle a script under scripts/ and reach for it via
run_skill_script — but unlike everything else in this guide, that
capability is off by default and gated by an operator, not by how you write
the skill (see Skills — Running a skill’s bundled
scripts). Write a
skill’s instructions to work whether or not script execution ends up
enabled in a given deployment — lean on prose reasoning for anything a model
can reliably do itself, and reach for a bundled script only for something
that genuinely needs to run: a deterministic calculation, a lookup against a
local dataset, something you would not trust an LLM to get right by
reasoning alone every time. If the skill’s core value falls apart the moment
allow_script_execution is off, that’s worth flagging to whoever’s granting
it, not discovering at run time.
Validate before you grant
Section titled “Validate before you grant”validate_skill (REST POST /skills/validate, or the Gateway MCP tool of
the same name) is a dry-run — frontmatter parses, name matches, and
description is non-empty, all checked with nothing written. Cheap to run,
and it catches the two mistakes that are otherwise easy to ship silently: a
name: that doesn’t match the directory (the skill just won’t load, no
error surfaced to the agent granting it) and an empty description (same
failure mode). Run it before create_skill/update_skill, the same way
validate_agent is the recommended step before writing an agent.
Editing a skill resets calibration
Section titled “Editing a skill resets calibration”Treat editing a skill exactly like editing the soul.md of every agent that
grants it — because that’s what actually happens. A skill’s own content
(its SKILL.md body plus every bundled file and script) is folded into the
version of every agent currently granting it, the same content-hash field
VectorStep already uses to scope calibration buckets. Editing
SKILL.md, adding or changing a bundled reference file, or swapping a
bundled script all start a new calibration history — for every granting
agent, even one that rarely loads the skill at runtime. Adding or removing
which skills an agent is granted works the same way, as does tools: (see
Creating agents).
That means a small wording tweak to a widely-granted skill isn’t free the
way it might look — if an agent had a validated, enforced calibration gate
(calibration: {enforce: true}) on a step, editing a skill it’s granted
resets that gate back to its uncalibrated fallback behaviour until enough
new labelled runs land. Prefer to batch your skill edits the same way
you’d batch agent config changes, rather than making several small edits to
a high-traffic skill in quick succession. An agent with no skill grants at
all is never affected by any skill edit, on this or any other skill.
VectorStep’s Skill Library → Versions
tab shows this history with diffs, and an agent’s own Versions tab labels a
reset “changed by skill edit: <name>” when that’s specifically what
caused it — see How confidence and calibration
work
for what a reset actually does to a step’s gate.
Measuring whether your skill gets used
Section titled “Measuring whether your skill gets used”A skill’s description is the only thing that costs context when it isn’t
in use, and it’s the only signal the agent has to decide whether to load
the full body at all — see Write the description like a router, not a
summary above. Whether
that description is actually working is a question with real data behind
it, not a guess: Insights — Skills
(or a skill’s own detail page → Usage tab) shows load rate — of the
steps whose agent was granted this skill, what fraction actually called
load_skill — for every skill, over a selectable time range.
A “Granted but never loaded” panel on that page calls out any skill with real grants and zero loads in range — the clearest possible signal that a description isn’t triggering the model the way you intended. If you see a skill there, the fix is almost always to make the description read more like the “Good” example above: concrete triggering conditions, not a one-line summary of what’s inside. Iterate, re-grant if needed, and watch the load rate move; the version history on the skill’s detail page lets you correlate a description edit with the load rate before and after it, per version.
Where next
Section titled “Where next”- Skills — the full
SKILL.mdreference, progressive disclosure model, and script execution. - Writing good agents — the same
treatment for
agent.yaml/soul.md, including the skill-vs-system-prompt judgment call this guide builds on. - Tutorial: give your agent a skill — write one from scratch and watch an agent load it mid-run.