Docs / Skills
Writing a skill
How to write a skill of your own: the SKILL.md format, the YAML rule that matters most, scripts and references, and the lint that checks it all.
Start from the scaffold
ivralo skills create deploy-checks --agent coder
writes a skill on the local shelf that already validates, and assigns it to the coder preset in the same step. Leave out --agent and the skill rides a specialist of its own (see How skills work).
| Option | What it adds |
|---|---|
--agent, -a | the preset key that owns it; repeatable |
--scripts | a scripts/ folder with example.py |
--references | a references/ folder with notes.md |
--shipped | writes to the shipped shelf instead; for work on ivralo itself |
A name is lowercase letters, digits and single hyphens, at most 64 characters, and must not exist on either shelf. --shipped cannot be combined with --agent: a shipped skill is owned through a preset's skills= list in the roster, and the command prints that edit for you.
The SKILL.md file
A SKILL.md is YAML frontmatter between two --- lines, then a markdown body. This is what the scaffold writes:
---
name: "deploy-checks"
description: "TODO: what this skill teaches and when to load it. State the trigger ('Load when the user ...') and draw the boundary: Do NOT load it for jobs another skill owns - name that skill instead."
metadata:
ivralo:
capabilities: []
---
# deploy-checks
TODO: the technique this skill teaches - when to use it, the hard rules,
worked code the agent can adapt.
## Steps
1. TODO
The frontmatter keys ivralo knows are name, description, metadata, license, compatibility and allowed-tools. Any other key is a lint warning. name must equal the directory's name.
Quote the description
The frontmatter is YAML. A description almost always contains a colon, and an unquoted value with a colon in it is not valid YAML. When that happens the skill does not load, and nothing else tells you. So always put the description in double quotes.
Write the description for the index
The description is the only part an agent sees until it opens the skill. It should say three things:
- what the skill teaches, in its first sentence (that sentence is all other specialists see);
- when to load it: "Load when the user asks ...";
- when not to: "Do NOT load for ...", naming the skill that owns that job instead.
Lint warns when a description is shorter than 120 characters or has no "do not load" boundary.
The body
The body is read once the agent decides the skill applies. Keep it to the technique: the steps, the rules that are easy to get wrong, and short worked code the agent can adapt. A body that alone would take more than a quarter of the model's context window is a lint warning. Move long material into references/ and say in the body when to read it.
Scripts
A script is a Python file in scripts/. The agent runs it from its own Python, inside the worker, with run_skill_script from ivralo.engine:
from ivralo.engine import run_skill_script
run_skill_script("deploy-checks", "check_manifest.py", "deploy/manifest.yaml")
run_skill_script finds the script on whichever shelf serves the skill, runs it with the runtime's interpreter, and prints what it wrote, which is how the output reaches the model. Arguments are the script's command line; file names in them are workspace names. A script that runs longer than 120 seconds is stopped, and a non-zero exit raises after its output is printed.
Two rules follow from that:
- print what the caller needs to see; printed output is the only thing that comes back;
- never tell the agent to run a script by a relative path; the shelf is not in the workspace.
Scripts run inside the worker under its write guard, not in ivralo's own process. Read a script before you install a skill that ships one.
References
A file in references/ is longer documentation the agent reads only when the body sends it there. It costs nothing until then.
Grants for the skill's own specialist
metadata.ivralo.capabilities lists the grants the skill asks for when it rides a specialist of its own. The scaffold writes it empty, because most skills need nothing beyond the baseline: Python in the worker, the workspace's files and asking you a question. Names come from Capabilities; an unknown name is a lint error.
Lint
ivralo skills lint deploy-checks
reads skills for the mistakes that each cost someone a debugging session. With no name it reads both shelves.
| Level | Rule | What it catches |
|---|---|---|
| error | description-unquoted | a description without quotes |
| error | capabilities-declared | an unknown grant name, or not a list |
| error | script-missing | the body names a script the skill does not ship |
| error | script-syntax | a script that does not compile |
| warning | description-short | under 120 characters |
| warning | description-no-negative-guidance | no "do not load" boundary |
| warning | frontmatter-unknown-keys | a key ivralo does not know |
| warning | subprocess-no-capture | subprocess without capture_output=True |
| warning | os-system | a call to os.system() |
| warning | absolute-path-write | a write to an absolute or .. path |
| warning | stdout-not-printed | a worked example that computes and never prints |
| warning | body-oversized | a body over a quarter of the context window |
Findings never block. An install shows them before it asks, ivralo doctor lists them, and the options below change the rest:
| Option | Effect |
|---|---|
--strict | exit 1 when an error is found, for CI |
--shelf shipped or --shelf local | read one shelf only |
--fix | quote an unreadable name: or description: on the local shelf |
Check it loads
ivralo skills
shows the skill on its shelf and whether it validates. Start a new session, or type /new, and the team carries it.