# Skill Anatomy


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:

1. The project skills directory, `<repo>/.kli/skills/`.
2. `.agents/skills/` directories from the project outward to the repository root.
3. The global skills directory, `~/.config/kli/skills/`.
4. The user agents directory, `~/.agents/skills/`.
5. 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](/kli/extend); see
[choosing an altitude](/kli/extend/choosing-an-altitude) for how they sit beside prompt
templates and Lisp extensions. To write one, follow
[Write a skill](/kli/extend/skills/write-your-first).
