# 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](https://agentskills.io/specification) 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
```

```markdown
---
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[]`:

```yaml
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](https://zidane.si/docs/files.md#files-you-write)).

```ts
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 }` |

```ts
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](https://zidane.si/docs/hooks.md#mcp-connection-and-skills).

In the apps, `/<skill-name>` activates that skill for the next message. It wins over a [Markdown command](https://zidane.si/docs/extensions.md#commands) 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](https://zidane.si/docs/ui-onboarding.md) 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 as `https://github.com/cursor/plugins/tree/HEAD/pstack/skills`
- `owner/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 |
| - | - | - |
| Matt Pocock | [`mattpocock/skills`](https://github.com/mattpocock/skills) | Engineering habits for real projects: test-driven development, code review, research and planning. |
| Anthony Fu's pack | [`antfu/skills-pack`](https://github.com/antfu/skills-pack) | A hand-picked pack of skills from several authors, in one list. |
| Anthony Fu | [`antfu/skills`](https://github.com/antfu/skills) | Conventions for Vue, Nuxt, Vite, Vitest, UnoCSS and pnpm projects. |
| Cursor pstack | [`cursor/plugins/pstack/skills`](https://github.com/cursor/plugins/tree/HEAD/pstack/skills) | Principles and workflows from the pstack plugin: design, verification and clear writing. |
| Anthropic | [`anthropics/skills`](https://github.com/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.

```ts
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](https://zidane.si/docs/extensions.md#install): GitHub `owner/repo[#ref][:subdir]`, a GitHub URL, an npm package, or a `skills.sh/<owner>/<repo>[/<skill>]` URL.

```ts
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.

## Reference

- [`zidane/skills`](https://zidane.si/docs/reference/skills.md)
