Skills
An agent’s capabilities today are tools: (MCP servers it can call) plus
whatever soul.md tells it to do, always loaded on every call. A skill is a
third thing: a self-contained, reusable body of instructions — “how we
triage a Sev2,” “how to write a Terraform module our way,” “the runbook for
rotating this credential” — that an agent discovers and loads only when a
conversation actually needs it, rather than paying for in every system
prompt regardless.
Skills live as subdirectories under skills_dir (/data/skills in the
container, ~/.vectorstep/skills/ from the host side — same shape as
agents_dir, see Creating agents).
SKILL.md format
Section titled “SKILL.md format”skills/ incident-triage/ SKILL.md scripts/ classify_severity.py reference/ escalation-matrix.md---name: incident-triagedescription: > Use when the user is reporting a production incident and needs help classifying severity, identifying the right escalation path, and drafting an initial status update. Covers Sev1-Sev4 per this org's matrix.---
# Incident Triage
1. Ask for: affected service, user-visible symptom, start time.2. Classify severity using `reference/escalation-matrix.md`....| Field | Required | Description |
|---|---|---|
name |
Yes | Must match the directory name — same rule and same reason as an agent’s name vs. its directory. |
description |
Yes | The only field an agent pays context for when the skill isn’t in use — see Writing a good description below. |
The Markdown body is the skill’s full instructions, loaded only when the
agent decides the skill is relevant. Any other file in the skill’s directory
— a reference doc, a template, a script — is read individually, by path,
only when the agent asks for it. Nothing beyond SKILL.md is ever preloaded
into context.
Progressive disclosure
Section titled “Progressive disclosure”This is the same three-level shape a well-designed tool-search or documentation system already uses — cheap metadata always present, full content fetched on demand, bundled resources fetched individually past that:
- Level 1 — always in the system prompt, for every granted skill. One
line per skill: name and
description. This is the entire cost of a grant an agent isn’t currently using. - Level 2 — the full
SKILL.mdbody, loaded on demand. The agent calls a syntheticload_skill(name)tool when a skill’s description looks relevant; it gets back the full instructions plus the list of any bundled files. - Level 3 — one bundled file at a time, read on demand. The agent calls
read_skill_file(skill_name, path)for a specific reference doc or templateload_skilltold it about.
An agent with no skills: grants pays nothing for this feature at all — not
the catalog text, not the two tool schemas. Both synthetic tools are added
to the tool list only when at least one skill is granted.
Granting a skill
Section titled “Granting a skill”Default-deny, identical posture to tools: — an agent sees zero skills
unless its agent.yaml names them:
name: sre-triagemodel: anthropic/claude-sonnet-4-6max_tokens: 8192skills: - incident-triage - terraform-module-styleThere’s no scoping-within-a-skill the way tools: can scope down to
specific tool names — a skill is granted or it isn’t.
Writing a good description
Section titled “Writing a good description”description is the one field most likely to be gotten wrong by someone
used to always-loaded system prompts: it’s the only thing that costs
context when the skill isn’t in use, and it’s the only signal the agent has
to decide whether to load the full body at all. Write it like a router, not
a summary — state plainly when to use this skill, not just what it
contains:
- Good: “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. Covers Sev1-Sev4 per this org’s matrix.”
- Weak: “Incident response skill.” — doesn’t tell the agent what situations should trigger loading it, or what’s actually inside.
Reading and writing skills
Section titled “Reading and writing skills”Skills are authored the same way agents are — hand-edit the files on disk,
or use the REST API / Gateway MCP
(create_skill, update_skill, delete_skill, validate_skill). See
REST API for the full endpoint set. skills/
is gitignored, same as agents/ — every write result says so explicitly,
never that anything was committed.
Hot reload
Section titled “Hot reload”POST /reload # via HTTPkill -HUP <pid> # via SIGHUPReloads all skill directories from disk without restarting, in the same call that reloads agents — not a separate reload path to remember. In-progress runs are unaffected.
Running a skill’s bundled scripts
Section titled “Running a skill’s bundled scripts”A skill can bundle a script (scripts/classify_severity.py above) as
ordinary content — installable via create_skill/update_skill like any
other bundled file. That’s deliberately not the same thing as making it
runnable.
Content is API-writable. Execution approval never is — full stop. A
skill’s script only becomes callable if an operator separately, manually
adds it to security.skills.allowed in config.yaml:
security: skills: allow_script_execution: false # default false — off by default, same # posture as VectorStep's shell steps allowed: - skill: incident-triage path: scripts/classify_severity.py # relative to the skill's own directory sha256: 3f2a1c9e... # required — no unpinned optionconfig.yaml is loaded once at startup and is not writable via any API —
that’s what makes this an operator control an admin-token caller can’t
loosen through the REST API or Gateway MCP, no matter what it can do to a
skill’s content. An admin-token caller can ship a script as part of a
skill’s bundled files; only someone with host/filesystem access to
config.yaml can make that specific script executable. POST /reload /
SIGHUP reload skill content but never re-read config.yaml, so approving
a new script needs an actual restart.
On Kubernetes, where skills arrive from a ConfigMap, see Skill scripts for how to change an approved script and its pin together.
When enabled and approved, the script is offered to the agent as a third
synthetic tool, run_skill_script(skill_name, path, args) — but only if
this specific agent has at least one granted skill with at least one
approved script; an agent with nothing runnable never sees the tool at all.
Arguments are passed literally as argv entries, never shell-interpreted —
there is no free-text command field anywhere in this feature. The script’s
checksum is re-verified immediately before every single run, not just at
startup, so a script swapped on disk after boot is caught rather than
silently executed. Execution is contained the same way VectorStep’s own
executor: shell pipeline steps are: an isolated scratch working directory,
CPU/memory/output-size limits, and a killed process group on timeout — see
Executors — shell
for the shared design this was ported from. The subprocess runs with an
empty environment — nothing inherited from the gateway process.
GET /skills / GET /skills/{name} include an executable_scripts field —
which of a skill’s bundled files are currently approved — a read-only fact
safe to expose even though the approval list itself isn’t.
Seeing skills in VectorStep
Section titled “Seeing skills in VectorStep”VectorStep has a read-only Skill Library
(/ui/skills) that browses whatever’s loaded on the Gateway — description,
bundled files, which agents are granted each skill, usage over time, and a
version history that survives a skill’s content changing or the skill being
deleted entirely. A run’s step detail shows which of its agent’s granted
skills were actually loaded, and Insights — Skills
rolls up load rate and script activity across every agent that runs it —
the fastest way to tell whether a skill’s description is actually getting
it triggered. None of this is writable from VectorStep; it’s read-only
observability over what you author here as files.
Where next
Section titled “Where next”- Creating agents —
skills:sits alongsidetools:inagent.yaml; see Writing good agents for when a capability belongs in one vs. the other. - Tool policy — the operator-owned
allow/deny backstop for
tools:calls does not currently extend to skills; an agent’sskills:grant is the only gate today. - REST API — the full
/skills*endpoint set. - Configuration —
skills_dirandsecurity.skills.