zot skills
A skill is a reusable instruction set written as a single
SKILL.md file with a YAML frontmatter header. Skills may be supplied by
an extension as well as by the normal skill directories. Unless a skill disables
model invocation, zot discovers it at startup and surfaces it to the model
in two ways:
- The system prompt gains a short manifest:
Available skills: ... - code-review — Run a self-review pass...
- A built-in
skill tool lets the model load any one skill's full
body on demand.
The on-demand-load model keeps token usage cheap: only the manifest
goes into every request; the body is fetched as a tool result the
one or two turns the model actually needs it.
Anatomy
---
name: code-review
description: Run a thorough self-review pass on a recent change.
allowed-tools: [read, bash]
permissions:
bash: ["git diff*", "git log*"]
---
# Code review
When asked to review code, ...
Frontmatter fields
| field |
required |
purpose |
name |
optional |
skill identifier; defaults to the normalized relative directory path |
description |
required |
one-line summary shown in the system prompt |
disable-model-invocation |
optional |
when true, hide the skill from the model's startup manifest; invoke it explicitly with /skill:<name> |
allowed-tools |
optional |
list of tool names the skill is meant to use; informational |
permissions |
optional |
per-tool patterns; informational |
allowed-tools and permissions are parsed but not enforced in
this version. They appear in the rendered skill body so the model can
see them and self-regulate. Future versions may enforce.
The body (everything after the second ---) is plain markdown.
There's no template engine; the model sees what you write.
Discovery
zot recursively looks in these directories, in priority order, and registers the
first SKILL.md it finds for each unique name:
| location |
scope |
./.zot/skills/<name>/SKILL.md |
project (native) |
$ZOT_HOME/skills/<name>/SKILL.md |
global (native) |
./.claude/skills/<name>/SKILL.md |
project (claude-compat) |
~/.claude/skills/<name>/SKILL.md |
global (claude-compat) |
./.agents/skills/<name>/SKILL.md |
project (agent-compat) |
~/.agents/skills/<name>/SKILL.md |
global (agent-compat) |
The compat paths are deliberate: a SKILL.md written for an existing
skill ecosystem works in zot unchanged. Drop your existing
.claude/skills/ or .agents/skills/ directories into a project and
zot will pick them up. When no frontmatter name is present, nested path
components are lowercased and normalized to kebab-case and joined with -.
Only files named exactly SKILL.md are read.
Extensions namespace their skills, for example git-tools:writing-git-commits.
Explicit --ext bundles take precedence over environment, project, global, and
compatibility sources. Duplicate names from different files keep the
higher-precedence file and report a diagnostic. Rediscovering the same file
under the same skill name (including through a symlink) is silently ignored.
Skill directories may themselves be symlinks, so a checkout can be kept in one
place and linked into a skills directory. Real directories are scanned before
links, so an alias cannot rename an existing unnamed skill. Link cycles are
ignored, a directory reached twice through links is scanned once, and a link
that points at nothing is skipped. This includes overlapping project and global
locations when running from your home directory. --no-ext omits implicit
extension bundles. --no-ext --ext PATH loads only the explicitly named bundle.
--no-skill disables all skills, including extension and built-in skills.
When XDG_STATE_HOME is set on any platform, $ZOT_HOME defaults to
$XDG_STATE_HOME/zot. Otherwise it defaults to ~/Library/Application Support/zot/
on macOS, ~/.local/state/zot on Linux, or %LOCALAPPDATA%\zot on Windows.
Inspecting installed skills
In zot, run /skills. A picker lists every discovered skill with its
description and source path. Press enter on a row to view the full
body inline. Long descriptions, source paths, and body lines wrap to fit the
terminal. Use up/down or pgup/pgdn to scroll through the preview, including its
metadata on short terminals. Press esc to go back.
Pinning skills
In the /skills list, press p to toggle a project pin or g to toggle a
global pin. Rows show [p-], [-g], or [pg] for the selected scopes.
Global and project pins are combined in alphabetical name order, with each
skill loaded only once. Unpinning one scope leaves the other scope intact.
On a fresh interactive session, a read-only [Pinned skills] section above
the input lists the skills that will accompany your next message. Its heading,
spacing, and indented names match the other startup information sections.
Each name shows its pin scope: (project), (global), or (project + global).
Opening zot does not start a model turn. The complete skill bodies and their
source directories are included
in your first submitted message and persisted with it, without changing the
editor text. /clear prepares pins again. Switching working directories with
/cd prepares the new directory's pins. Resuming, importing, or branching an
existing session does not inject pins again, even when its transcript is empty.
Changing models and compacting context do not reload pins.
Changing pins before the first message updates the pending selection.
After that message, pin changes apply on the next fresh conversation or
/clear. Unpinning does not remove instructions already in the transcript.
There are no per-request removal controls in the editor.
Preferences live in $ZOT_HOME/skill-pins.json, not in the repository.
Project keys are absolute starting directories with symlinks resolved when
possible, not Git roots. Subdirectories have separate project pins.
For example:
{
"global": ["code-review"],
"projects": {
"/home/me/work/app": ["test-fix"]
}
}
Pins store discovered names, including extension namespaces, rather than
copies of skill files. Normal discovery precedence still applies, so a global
pin may resolve to a project's higher-priority skill of the same name.
Missing or disabled sources produce a warning and are skipped. A malformed
preferences file is reported and is not overwritten by a toggle.
--no-skill disables preloading. Explicitly pinning a skill with
disable-model-invocation: true is allowed.
Print, stream, and JSON modes also include pins with the first main prompt of
a fresh session, including runs with session persistence disabled. Zotfile
startup pre commands run before pins are applied. RPC, standalone bot modes,
the SDK, and swarm background agents do not automatically load pin preferences.
The interactive Telegram bridge uses the interactive conversation's pins.
Pinning saves repeated invocation, not tokens: complete skill bodies add to
the context window and may later be summarized by compaction. Keep pins small,
especially with local models. Preferences are saved via temporary-file replacement.
Concurrent edits from separate zot processes are not merged. An already open interactive
session refreshes preferences on /skills or /clear, not continuously.
Invoking skills
For normal skills, the system prompt tells the model the skill names and
short descriptions. When a request matches, the model calls the skill tool
to load the full instructions on demand.
To force a specific skill, invoke it as a slash command. Typing /skill: opens a filtered list of discovered user skills. Use the arrow keys to select one, tab to complete its name, or enter to invoke the highlighted skill.
/skill:code-review
/skill:code-review focus on security issues
By default, suggestions match case-insensitive name prefixes and are sorted
alphabetically. Enable fuzzy skill suggestions in /settings to match
characters in order anywhere in a skill name: /skill:review and /skill:crv
can both find code-review. Results are ranked by relevance, with alphabetical
tie-breaking, and matching characters are shown in bold, including in the
selected row. Only names are searched, not descriptions or the /skill: prefix.
An empty query still lists skills alphabetically; an exact name shows only that
skill. Other slash-command suggestions and exact skill lookup during invocation
are unchanged. Complete a fuzzy query before adding request text.
The setting takes effect immediately and persists as fuzzy_skill_suggest in
$ZOT_HOME/config.json. Missing or false preserves the default prefix behavior.
You can turn it off at any time. Bold emphasis depends on terminal support.
zot expands the command into a user message containing the complete skill
body, its directory for resolving relative references, and any text following
the command as the request. This bypasses model-side skill selection.
Set disable-model-invocation: true in a skill's frontmatter when it should
only run after explicit user invocation. The skill remains visible in
/skills and available through /skill:<name>, but its name and description
are omitted from the model's startup context.
Writing good skills
- Be procedural. Number steps. Tell the model what to do in what
order. Skills are habits, not knowledge dumps.
- Be precise about boundaries. "Stop after step 4" is more
effective than "don't go too far".
- Trim aggressively. A 200-line skill bloats every turn the
model uses it. Aim for 20–80 lines.
- One skill per behaviour. Don't pack three workflows into one
SKILL.md; the model picks one path. Two separate skills work better.
- Lead with the trigger. First paragraph should make it
obvious when to use the skill so the model self-selects correctly.
Examples
See examples/skills/ for starter skills:
code-review/ — self-review pass on a recent diff
test-fix/ — diagnose + minimally fix a failing test
recursive/ — nested path-derived naming and explicit-name discovery
Comparison to other discovery layouts
| ecosystem |
path |
zot reads it? |
| (native) |
.zot/skills/<name>/SKILL.md |
yes |
| (claude-style) |
.claude/skills/<name>/SKILL.md |
yes |
| (agent-style) |
.agents/skills/<name>/SKILL.md |
yes |
Cross-pollination is intentional: pick whichever convention you're
already using and zot tags along.
source
1# zot skills
2
3A skill is a reusable instruction set written as a single
4`SKILL.md` file with a YAML frontmatter header. Skills may be supplied by
5an extension as well as by the normal skill directories. Unless a skill disables
6model invocation, zot discovers it at startup and surfaces it to the model
7in two ways:
8
91. The system prompt gains a short manifest:
10 `Available skills: ... - code-review — Run a self-review pass...`
112. A built-in `skill` tool lets the model load any one skill's full
12 body on demand.
13
14The on-demand-load model keeps token usage cheap: only the manifest
15goes into every request; the body is fetched as a tool result the
16one or two turns the model actually needs it.
17
18## Anatomy
19
20```markdown
21---
22name: code-review
23description: Run a thorough self-review pass on a recent change.
24allowed-tools: [read, bash]
25permissions:
26 bash: ["git diff*", "git log*"]
27---
28
29# Code review
30
31When asked to review code, ...
32```
33
34### Frontmatter fields
35
36| field | required | purpose |
37|---|---|---|
38| `name` | optional | skill identifier; defaults to the normalized relative directory path |
39| `description` | required | one-line summary shown in the system prompt |
40| `disable-model-invocation` | optional | when `true`, hide the skill from the model's startup manifest; invoke it explicitly with `/skill:<name>` |
41| `allowed-tools` | optional | list of tool names the skill is meant to use; informational |
42| `permissions` | optional | per-tool patterns; informational |
43
44`allowed-tools` and `permissions` are **parsed but not enforced** in
45this version. They appear in the rendered skill body so the model can
46see them and self-regulate. Future versions may enforce.
47
48The body (everything after the second `---`) is plain markdown.
49There's no template engine; the model sees what you write.
50
51## Discovery
52
53zot recursively looks in these directories, in priority order, and registers the
54first `SKILL.md` it finds for each unique name:
55
56| location | scope |
57|---|---|
58| `./.zot/skills/<name>/SKILL.md` | project (native) |
59| `$ZOT_HOME/skills/<name>/SKILL.md` | global (native) |
60| `./.claude/skills/<name>/SKILL.md` | project (claude-compat) |
61| `~/.claude/skills/<name>/SKILL.md` | global (claude-compat) |
62| `./.agents/skills/<name>/SKILL.md` | project (agent-compat) |
63| `~/.agents/skills/<name>/SKILL.md` | global (agent-compat) |
64
65The compat paths are deliberate: a `SKILL.md` written for an existing
66skill ecosystem works in zot unchanged. Drop your existing
67`.claude/skills/` or `.agents/skills/` directories into a project and
68zot will pick them up. When no frontmatter `name` is present, nested path
69components are lowercased and normalized to kebab-case and joined with `-`.
70Only files named exactly `SKILL.md` are read.
71
72Extensions namespace their skills, for example `git-tools:writing-git-commits`.
73Explicit `--ext` bundles take precedence over environment, project, global, and
74compatibility sources. Duplicate names from different files keep the
75higher-precedence file and report a diagnostic. Rediscovering the same file
76under the same skill name (including through a symlink) is silently ignored.
77Skill directories may themselves be symlinks, so a checkout can be kept in one
78place and linked into a skills directory. Real directories are scanned before
79links, so an alias cannot rename an existing unnamed skill. Link cycles are
80ignored, a directory reached twice through links is scanned once, and a link
81that points at nothing is skipped. This includes overlapping project and global
82locations when running from your home directory. `--no-ext` omits implicit
83extension bundles. `--no-ext --ext PATH` loads only the explicitly named bundle.
84`--no-skill` disables all skills, including extension and built-in skills.
85
86When `XDG_STATE_HOME` is set on any platform, `$ZOT_HOME` defaults to
87`$XDG_STATE_HOME/zot`. Otherwise it defaults to `~/Library/Application Support/zot/`
88on macOS, `~/.local/state/zot` on Linux, or `%LOCALAPPDATA%\zot` on Windows.
89
90## Inspecting installed skills
91
92In zot, run `/skills`. A picker lists every discovered skill with its
93description and source path. Press enter on a row to view the full
94body inline. Long descriptions, source paths, and body lines wrap to fit the
95terminal. Use up/down or pgup/pgdn to scroll through the preview, including its
96metadata on short terminals. Press esc to go back.
97
98## Pinning skills
99
100In the `/skills` list, press `p` to toggle a project pin or `g` to toggle a
101global pin. Rows show `[p-]`, `[-g]`, or `[pg]` for the selected scopes.
102Global and project pins are combined in alphabetical name order, with each
103skill loaded only once. Unpinning one scope leaves the other scope intact.
104
105On a fresh interactive session, a read-only `[Pinned skills]` section above
106the input lists the skills that will accompany your next message. Its heading,
107spacing, and indented names match the other startup information sections.
108Each name shows its pin scope: `(project)`, `(global)`, or `(project + global)`.
109Opening zot does not start a model turn. The complete skill bodies and their
110source directories are included
111in your first submitted message and persisted with it, without changing the
112editor text. `/clear` prepares pins again. Switching working directories with
113`/cd` prepares the new directory's pins. Resuming, importing, or branching an
114existing session does not inject pins again, even when its transcript is empty.
115Changing models and compacting context do not reload pins.
116
117Changing pins before the first message updates the pending selection.
118After that message, pin changes apply on the next fresh conversation or
119`/clear`. Unpinning does not remove instructions already in the transcript.
120There are no per-request removal controls in the editor.
121
122Preferences live in `$ZOT_HOME/skill-pins.json`, not in the repository.
123Project keys are absolute starting directories with symlinks resolved when
124possible, not Git roots. Subdirectories have separate project pins.
125For example:
126
127```json
128{
129 "global": ["code-review"],
130 "projects": {
131 "/home/me/work/app": ["test-fix"]
132 }
133}
134```
135
136Pins store discovered names, including extension namespaces, rather than
137copies of skill files. Normal discovery precedence still applies, so a global
138pin may resolve to a project's higher-priority skill of the same name.
139Missing or disabled sources produce a warning and are skipped. A malformed
140preferences file is reported and is not overwritten by a toggle.
141`--no-skill` disables preloading. Explicitly pinning a skill with
142`disable-model-invocation: true` is allowed.
143
144Print, stream, and JSON modes also include pins with the first main prompt of
145a fresh session, including runs with session persistence disabled. Zotfile
146startup `pre` commands run before pins are applied. RPC, standalone bot modes,
147the SDK, and swarm background agents do not automatically load pin preferences.
148The interactive Telegram bridge uses the interactive conversation's pins.
149
150Pinning saves repeated invocation, not tokens: complete skill bodies add to
151the context window and may later be summarized by compaction. Keep pins small,
152especially with local models. Preferences are saved via temporary-file replacement.
153Concurrent edits from separate zot processes are not merged. An already open interactive
154session refreshes preferences on `/skills` or `/clear`, not continuously.
155
156## Invoking skills
157
158For normal skills, the system prompt tells the model the skill names and
159short descriptions. When a request matches, the model calls the `skill` tool
160to load the full instructions on demand.
161
162To force a specific skill, invoke it as a slash command. Typing `/skill:` opens a filtered list of discovered user skills. Use the arrow keys to select one, `tab` to complete its name, or `enter` to invoke the highlighted skill.
163
164```text
165/skill:code-review
166/skill:code-review focus on security issues
167```
168
169By default, suggestions match case-insensitive name prefixes and are sorted
170alphabetically. Enable **fuzzy skill suggestions** in `/settings` to match
171characters in order anywhere in a skill name: `/skill:review` and `/skill:crv`
172can both find `code-review`. Results are ranked by relevance, with alphabetical
173tie-breaking, and matching characters are shown in bold, including in the
174selected row. Only names are searched, not descriptions or the `/skill:` prefix.
175An empty query still lists skills alphabetically; an exact name shows only that
176skill. Other slash-command suggestions and exact skill lookup during invocation
177are unchanged. Complete a fuzzy query before adding request text.
178
179The setting takes effect immediately and persists as `fuzzy_skill_suggest` in
180`$ZOT_HOME/config.json`. Missing or `false` preserves the default prefix behavior.
181You can turn it off at any time. Bold emphasis depends on terminal support.
182
183zot expands the command into a user message containing the complete skill
184body, its directory for resolving relative references, and any text following
185the command as the request. This bypasses model-side skill selection.
186
187Set `disable-model-invocation: true` in a skill's frontmatter when it should
188only run after explicit user invocation. The skill remains visible in
189`/skills` and available through `/skill:<name>`, but its name and description
190are omitted from the model's startup context.
191
192## Writing good skills
193
194- **Be procedural.** Number steps. Tell the model what to do in what
195 order. Skills are habits, not knowledge dumps.
196- **Be precise about boundaries.** "Stop after step 4" is more
197 effective than "don't go too far".
198- **Trim aggressively.** A 200-line skill bloats every turn the
199 model uses it. Aim for 20–80 lines.
200- **One skill per behaviour.** Don't pack three workflows into one
201 SKILL.md; the model picks one path. Two separate skills work better.
202- **Lead with the trigger.** First paragraph should make it
203 obvious *when* to use the skill so the model self-selects correctly.
204
205## Examples
206
207See `examples/skills/` for starter skills:
208
209- `code-review/` — self-review pass on a recent diff
210- `test-fix/` — diagnose + minimally fix a failing test
211- `recursive/` — nested path-derived naming and explicit-name discovery
212
213## Comparison to other discovery layouts
214
215| ecosystem | path | zot reads it? |
216|---|---|---|
217| (native) | `.zot/skills/<name>/SKILL.md` | yes |
218| (claude-style) | `.claude/skills/<name>/SKILL.md` | yes |
219| (agent-style) | `.agents/skills/<name>/SKILL.md` | yes |
220
221Cross-pollination is intentional: pick whichever convention you're
222already using and zot tags along.