# Add skills to a harness

Your users keep skills as `SKILL.md` folders, in the repo and in their home folder, and they want to call them from your agent like any other command. The skills plugin reads those folders, gives the model the list of skills, and makes each skill a `/<name>` command. A skill that a user adds while the session runs works at once.

The plugin reads the folders with `node:fs`, so it runs on Node.js. It does not work on Cloudflare Workers.

## 1. Add the plugin

```sh
npm i @tanstack/ai-skills
```

```ts group=harness-skills
import { homedir } from 'node:os'
import { join } from 'node:path'
import { defineHarness } from '@tanstack/ai-harness'
import { openaiText } from '@tanstack/ai-openai'
import { skills } from '@tanstack/ai-skills/harness'

// One time, outside `plugins`, so the sessions share the folder watchers.
const agentSkills = skills({
  dirs: ['./.agents/skills', join(homedir(), '.acme', 'skills')],
})

export const assistant = defineHarness({
  name: 'acme/assistant',
  adapter: openaiText('gpt-6.1-sol'),
  plugins: () => [agentSkills],
})
```

- `dirs` lists the skill folders. Each one holds skill folders with a `SKILL.md`.
- When two folders have a skill with the same name, the first folder wins. Put the repo folder first, so a project can replace a user's skill.
- A folder that does not exist yet is fine. Its skills show once the user makes it.

## 2. Call a skill

`/release-notes write them for v2` starts a turn with "Use the release-notes skill: write them for v2". The model loads the skill with `load_skill`, then does the task. `/release-notes` alone asks for the skill with no task.

The model also sees the list of skills, so it can load one on its own when a task matches.

## Names that are taken

A skill with the name of another command gets a prefix: a `model` skill is `/skill:model`, and `/model` stays the model picker. Pass `reserved` with the names that your UI handles itself:

```ts group=harness-skills
export const cliSkills = skills({
  dirs: ['./.agents/skills'],
  reserved: ['help', 'exit'],
})
```

`/skills` lists every skill, its folder, and its command.

## Skills that change while the session runs

The plugin watches the folders, and the commands follow them:

- A new `SKILL.md` adds its command.
- A removed skill takes its command away.
- An edited description updates the command.
- A broken `SKILL.md` is left out until it is fixed.

Each change sends a `harness.commands.changed` event, so a UI can show the new command list at once.

The plugin watches each folder once for each host. All sessions of the harness on that host share the watchers, so many open sessions still use one watcher for each folder. A change only tells the sessions to update. The plugin reads the folders again at the start of each turn.

If the plugin cannot watch a folder, it logs one warning with the folder and the reason. An example is a Linux system with no free file watchers. Changes in that folder still show at the next turn.

## Many skills

The model's list has one line for each skill: the name and the first sentence of the description. When the list is longer than `maxCatalogTokens` (default 4000), the skills of the earlier folders stay, and the rest leave the model's list. A skill that the user calls with its command always stays in it.

The other options of `withSkills` pass through, for example `maxCatalogTokens` and `requireApproval`:

```ts group=harness-skills
export const manySkills = skills({
  dirs: ['./.agents/skills'],
  maxCatalogTokens: 8000,
})
```

## What you have now

- The skills of your folders, in the model's list and as `/<name>` commands.
- New and changed skills that work in a running session, with no restart.

Next: [Agent Skills](../skills/agent-skills) has the `SKILL.md` format, and [Write a harness plugin](./plugins#change-commands-while-the-session-runs) shows the `ctx.commands` API that the plugin uses.
