Navigation
Copy page

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:

  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; see choosing an altitude for how they sit beside prompt templates and Lisp extensions. To write one, follow Write a skill.