Skill Anatomy
On this page
A skill puts a set of instructions in front of the model at the moment a task calls for them. You write the instructions once in a file. The model reads a one- line summary of every skill on every turn, and when a task matches a summary it loads the full file on its own. You can also load a skill yourself, by name. The shape of the file is what makes both work.
A skill is a directory with a SKILL.md inside it. The file has a small YAML
frontmatter block and a Markdown body. The frontmatter is how kli finds and
advertises the skill; the body is the instruction text the model reads once the
skill is loaded. Everything that decides whether and how a skill reaches the
model lives in those few frontmatter keys.
The frontmatter
Three keys appear in the frontmatter. One is required.
description is required. kli puts it in the list of skills it shows the model
each turn, so this is the text the model reads when it decides whether a task
matches. Write it to say what the skill is for and when to reach for it, not how
it works. A skill with no description is dropped at discovery and never reaches
the model. The description is capped at 1024 characters.
name is the identifier kli uses everywhere it refers to the skill: in the
advertised list, in the $name sigil, and in the skill:<name> command. It must
be lowercase, made of a-z, 0-9, and hyphens, with no leading or trailing
hyphen and no two hyphens in a row, and at most 64 characters. It must match the
name of the directory that holds the SKILL.md. If you leave name out, kli
uses the directory name as the name. Setting it to anything that disagrees with
the directory is a validation warning, so in practice the directory name is the
name.
disable-model-invocation is optional. Set it to true and kli keeps the skill
out of the list it shows the model, so the model never loads it on its own. The
skill stays reachable by you, through the $name sigil and the skill:<name>
command. Use it for a skill you want on hand but never auto-loaded.
A SKILL.md is read whole, and its body lands in the model's context verbatim,
so kli reads at most 2 MiB of it. A file over that limit is refused at discovery
and refused again if it has grown past the limit by the time the skill is
loaded. Keep the body to instructions and link out to anything large.
How kli finds a skill
kli discovers skills by walking a fixed list of directories. A directory that
holds a SKILL.md is a skill, and its own subdirectories are not searched
further. Directories without a SKILL.md are searched for nested skills.
Dotfiles, node_modules, and paths matched by a .gitignore, .ignore, or
.fdignore in the tree are skipped.
The directories are walked in a set order, and the first skill of a given name wins. Later directories cannot replace a name already taken:
- The project skills directory,
<repo>/.kli/skills/. .agents/skills/directories from the project outward to the repository root.- The global skills directory,
~/.config/kli/skills/. - The user agents directory,
~/.agents/skills/. - The skills that ship with kli.
Because the built-in skills come last, a skill of the same name in any of your own directories takes precedence over the one kli ships. A second skill that resolves to the same name as an earlier one is dropped and reported as a name collision, so two skills cannot share a name.
How a skill is invoked
A discovered skill reaches the model three ways.
The model loads it on its own. Each turn kli appends the list of advertised
skills to the system prompt, each as a name, a description, and a location. When
a task matches a description, the model reads the file at that location and works
from it. This is the path description is written for, and the one
disable-model-invocation turns off.
You write $name in your message. Typing $review puts the body of the review
skill in front of the model along with your message, before the turn runs. The
sigil only triggers at a word boundary and only for a name that matches a
discovered skill; $total in ordinary prose stays prose. When two skill names
share a prefix, the longer match wins.
You run skill:<name> as a command. Every discovered skill registers a
skill:<name> command whose description is the skill's. Running it submits the
skill body as your message, so the transcript keeps the command line you typed
while the model sees the full body. Anything you type after the name is passed
along after the body.
The first two paths read the description and the name straight from the
frontmatter; the third uses the name to build the command. A SKILL.md with a
clear name and a description that says when to use it is reachable all three ways
at once.
Where to go next
Skills are the zero-code altitude of extending kli; see choosing an altitude for how they sit beside prompt templates and Lisp extensions. To write one, follow Write a skill.