Skills
Instruction packages the model loads on demand, per the Agent Skills spec.
A skill is a directory with a SKILL.md file, per the Agent Skills standard. The system prompt lists only the name and description of each skill. The model loads the full instructions when it needs them.
Skill layout#
my-skill/
SKILL.md # Required: frontmatter + instructions
scripts/ # Optional: executable code
references/ # Optional: documentation
assets/ # Optional: templates, data
---
name: my-skill
description: When to activate this skill.
allowed-tools: Bash Read Write
metadata:
zidane.model: claude-opus-4-7
zidane.thinking: low
zidane.paths: "src/**/*.ts,test/**/*.ts"
---
Full instructions the agent receives on activation.Top-level model, thinking and paths are deprecated. They still load: the parser moves them to metadata["zidane.*"] and adds a diagnostic.
allowed-tools takes three shapes, all normalized to string[]:
allowed-tools: Read Bash(git:*) # spec-canonical, whitespace-separated
allowed-tools: [Read, Bash(git:*)] # YAML flow list
allowed-tools: # YAML block list
- Read
- Bash(git:*)Shell interpolation#
!`cmd` in the instructions runs cmd on each activation and inlines its output, for example !`git branch --show-current`. These commands bypass the allowed-tools gate, and skills can come from third parties. skills.allowShellInterpolation: false turns interpolation off (default true). skills.approveShellInterpolation is a callback that approves or refuses each command.
Loading and discovery#
zidane scans these folders in order. On a name clash the first wins, so project skills beat user skills.
| Order | Folder | Scope |
|---|---|---|
| 1 | <root>/.agents/skills | Project, any agent |
| 2 | <root>/.zidane/skills | Project, zidane only |
| 3 | ~/.agents/skills | You, any agent |
| 4 | ~/.zidane/skills | You, zidane only |
| 5 | .claude/skills, .codex/skills, .pi/skills in the project, then ~/.claude/skills, ~/.codex/skills, ~/.pi/agent/skills | Apps only, with the setting Import configurations from other agents on |
| 6 | .ruler/skills in the project, then your global Ruler folder | Apps only, with the setting Support Ruler configuration (.ruler) on (default) |
<root> is the working directory in the library. The apps use the git root above the folder of the session, or the folder itself outside git. So a session in repo/packages/foo finds repo/.zidane/skills. User skills stay in ~/.zidane/skills on Linux too (files).
import { basic, createAgent, defineSkill } from 'zidane'
createAgent({
...basic,
provider,
skills: {
scan: ['./custom-skills'],
write: [defineSkill({ name: 'review', description: 'Code review.', instructions: '...' })],
exclude: ['deprecated-skill'],
enabled: ['review', 'deploy'],
},
})| Option | Default | Effect |
|---|---|---|
enabled | true | false or [] turns skills off. A string[] is an allow-list. |
scan | Extra directories | |
write | Skills written to disk at start, then loaded | |
catalog | Pre-resolved skills. They win name clashes. | |
exclude | Names to drop | |
skipDefaultPaths | false | Skip the default scan paths |
tool | true | Add the skills tools |
maxActive | unlimited | Active skills per run |
scriptTimeoutMs | 60000 | skills_run_script timeout |
trustProjectSkills | true | false skips project skills, for untrusted repos |
Activation#
Once the catalog has a skill, the agent gets three tools:
| Tool | Does |
|---|---|
skills_use | mode: 'activate' (default) returns the body in a <skill_content> block. mode: 'deactivate' (a zidane addition to the spec) releases the skill and its allowed-tools limit. |
skills_read | Reads a file from an active skill's references/ or assets/ |
skills_run_script | Runs a script from scripts/, returns { exitCode, stdout, stderr } |
await agent.activateSkill('pdf-processing') // fires skills:activate, via: 'explicit'
agent.activeSkills // readonly ActiveSkill[]
await agent.deactivateSkill('pdf-processing') // fires skills:deactivate, reason: 'explicit'activateSkill resolves the catalog on its first call, so it works before run(). To resolve early, call await agent.warmup() or set eager: true. activateSkill throws for an unknown name, when skills are off, and when maxActive is reached. Skill hooks are on hooks.
In the apps, /<skill-name> activates that skill for the next message. It wins over a Markdown command of the same name. Settings › Skills has an on/off switch per skill. Both apps install, edit and delete skills there.
Enforcement and resume#
When an active skill declares allowed-tools, a tool:gate handler blocks calls outside the union of the lists of the active skills. The three skills tools stay allowed. A blocked call throws AgentToolNotAllowedError. Its message tells the model to call skills_use({ mode: 'deactivate', name }).
On resume, zidane rebuilds the active set from the skills_use calls in the history. The last mode wins per skill. Resumed activations fire with via: 'resume'. skills:deactivate does not fire during the rebuild.
agent.activateSkill(name) writes no history, so the skill ends with the run. A host that needs it across runs (for example a TUI slash command) must activate it again before each run().
Add skills from a repository#
The Skills step of the setup wizard and Settings › Skills list the skills of a source before you install them. Settings shows a compact version, with the recommended sources as small chips. Type a source, or open a recommended one. Each skill shows its name, its description and its folder. Pick skills from one source or from several, then install them.
A source can be any of these:
owner/repo, or a GitHub link such ashttps://github.com/cursor/plugins/tree/HEAD/pstack/skillsowner/repo/path/to/folder, the form of the skills CLI- a skills.sh link, or a pasted
npx skills add …command - an npm package
A pack is a repository with no skills of its own whose README links skills in other repositories, such as antfu/skills-pack. Its list holds each linked skill, and each one installs from its own repository.
zidane reads the file list and the head of each SKILL.md through GitHub. With a GitHub token (GH_TOKEN, GITHUB_TOKEN or your gh login), GitHub allows 5,000 calls an hour instead of 60. When GitHub refuses, zidane downloads the source and lists it from its files. zidane keeps each listing for ten minutes. Nothing in a source runs, because a skill is text.
Recommended sources#
| Source | Repository | Skills for |
|---|---|---|
mattpocock/skills | Engineering habits for real projects: test-driven development, code review, research and planning. | |
antfu/skills-pack | A hand-picked pack of skills from several authors, in one list. | |
antfu/skills | Conventions for Vue, Nuxt, Vite, Vitest, UnoCSS and pnpm projects. | |
cursor/plugins/pstack/skills | Principles and workflows from the pstack plugin: design, verification and clear writing. | |
anthropics/skills | The official examples: documents, spreadsheets, design, the Claude API and MCP servers. |
From code#
browseSkillSource lists a source. Each skill carries the spec that installs it, and groupSkillPicks turns picks into one install per source.
import { groupSkillPicks, skillPickOf } from 'zidane/chat/pure'
import { browseSkillSource, installSkillFromSpec } from 'zidane/skills'
const listing = await browseSkillSource('mattpocock/skills')
const picks = listing.skills.filter(skill => ['tdd', 'research'].includes(skill.name)).map(skill => skillPickOf(skill, listing))
for (const group of groupSkillPicks(picks))
await installSkillFromSpec({ source: group.source, skills: group.rels, scope: 'user' })Install skills#
installSkillFromSpec takes the same sources as the extensions installer: GitHub owner/repo[#ref][:subdir], a GitHub URL, an npm package, or a skills.sh/<owner>/<repo>[/<skill>] URL.
import { installSkillFromSpec } from 'zidane'
await installSkillFromSpec({ source: 'vercel-labs/agent-skills', skill: 'react-best-practices', scope: 'user' })
await installSkillFromSpec({ source: 'https://skills.sh/anthropics/skills/frontend-design', scope: 'project' })skills: [...] installs a picked subset of a source, by name or folder. The apps send this option. Skills install to ~/.zidane/skills/<name> (user) or <gitRoot>/.zidane/skills/<name> (project). If the call names one skill and the skills CLI from skills.sh is on PATH, that CLI runs the install and writes to .agents/skills/. preferSkillsCli: false turns this off.
Validation parses SKILL.md and does not run code. The parser is lenient. Skills with minor deviations load, with warnings in SkillConfig.diagnostics. The authoring helpers defineSkill and writeSkillToDisk follow the spec strictly.