# Skills

> SKILL.md frontmatter rules, naming constraints, collision precedence, disable-model-invocation, and valid versus rejected skill fixtures.

- Repository: earendil-works/pi
- GitHub: https://github.com/earendil-works/pi
- Human docs: https://grok-wiki.com/public/docs/earendil-works-pi-7860a70e44d1
- Complete Markdown: https://grok-wiki.com/public/docs/earendil-works-pi-7860a70e44d1/llms-full.txt

## Source Files

- `packages/coding-agent/examples/sdk/04-skills.ts`
- `packages/coding-agent/test/fixtures/skills/valid-skill/SKILL.md`
- `packages/coding-agent/test/fixtures/skills/disable-model-invocation/SKILL.md`
- `packages/coding-agent/test/suite/regressions/2781-skill-collision-precedence.test.ts`
- `packages/coding-agent/test/fixtures/skills-collision/first/calendar/SKILL.md`
- `packages/coding-agent/test/fixtures/skills/missing-description/SKILL.md`

---

---
title: "Skills"
description: "SKILL.md frontmatter rules, naming constraints, collision precedence, disable-model-invocation, and valid versus rejected skill fixtures."
---

Skills are specialized instruction documents (`SKILL.md`) that `DefaultResourceLoader` discovers and loads into the agent session. Each skill has a `name`, `description`, optional `disableModelInvocation` flag, and markdown body content. Discovery covers project, user (agent), and package skill directories; same-name collisions resolve with a fixed precedence order and emit diagnostics for the losing skill.

## Skill file layout

A skill is a directory that contains a single `SKILL.md` file. Layouts used by auto-discovery and package registration:

:::files
project/
  .pi/
    skills/
      {name}/
        SKILL.md
    settings.json          # optional: { "packages": ["..."] }

{agentDir}/                # user agent dir from getAgentDir()
  skills/
    {name}/
      SKILL.md
  settings.json            # optional: { "packages": ["..."] }

{package}/
  package.json             # pi.skills: ["skills/{name}"]
  skills/
    {name}/
      SKILL.md
:::

| Scope | Path | How it is registered |
| --- | --- | --- |
| Project | `{cwd}/.pi/skills/{name}/SKILL.md` | Auto-discovered |
| User | `{agentDir}/skills/{name}/SKILL.md` | Auto-discovered |
| Package | `{pkgDir}/skills/{name}/SKILL.md` | `package.json` → `pi.skills`, then listed in `settings.json` `packages` |

SDK example comments document discovery from `cwd/.pi/skills`, `~/.pi/agent/skills` (via `getAgentDir()`), and related locations.

## SKILL.md frontmatter

`SKILL.md` is YAML frontmatter plus a markdown body. Fields observed in fixtures and runtime `Skill` objects:

| Frontmatter key | Runtime field | Required | Notes |
| --- | --- | --- | --- |
| `name` | `name` | Yes (all fixtures set it) | Skill identifier used for lookup and collision |
| `description` | `description` | Expected for a valid skill | Absent in the `missing-description` fixture |
| `disable-model-invocation` | `disableModelInvocation` | No | Boolean; `true` means manual-only invocation |

### Valid skill

```markdown
---
name: valid-skill
description: A valid skill for testing purposes.
---

# Valid Skill

This is a valid skill that follows the Agent Skills standard.
```

Minimum valid shape used in package/user/project collision tests:

```markdown
---
name: web-fetch
description: Package web-fetch skill
---
Package skill content
```

### Manual-only skill (`disable-model-invocation`)

```markdown
---
name: disable-model-invocation
description: A skill that cannot be invoked by the model.
disable-model-invocation: true
---

# Manual Only Skill

This skill can only be invoked via /skill:disable-model-invocation.
```

When `disable-model-invocation: true`, the model does not invoke the skill; the documented manual path is `/skill:{name}` (for example `/skill:disable-model-invocation`).

### Incomplete fixture (missing description)

```markdown
---
name: missing-description
---

# Missing Description

This skill has no description field.
```

This fixture is the contrast case for a valid skill: `name` is present, `description` is not. Treat a complete skill as having both `name` and `description` in frontmatter.

### Collision fixture sample

```markdown
---
name: calendar
description: First calendar skill.
---

# Calendar (First)

This is the first calendar skill.
```

## Runtime `Skill` shape

SDK-constructed skills use the exported `Skill` type:

| Field | Type / example | Purpose |
| --- | --- | --- |
| `name` | `"my-skill"` | Stable skill id |
| `description` | string | Human/model-facing summary |
| `filePath` | `"/virtual/SKILL.md"` | Path or virtual path |
| `baseDir` | `"/virtual"` | Skill base directory |
| `sourceInfo` | from `createSyntheticSourceInfo(...)` | Provenance; SDK example uses `{ source: "sdk" }` |
| `disableModelInvocation` | `boolean` | Maps from frontmatter `disable-model-invocation` |

## Discovery and load API

`DefaultResourceLoader` loads skills and exposes them after `reload()`:

```ts
import {
  createAgentSession,
  createSyntheticSourceInfo,
  DefaultResourceLoader,
  getAgentDir,
  SessionManager,
  type Skill,
} from "@earendil-works/pi-coding-agent";

const loader = new DefaultResourceLoader({
  cwd: process.cwd(),
  agentDir: getAgentDir(),
});
await loader.reload();

const { skills: allSkills, diagnostics } = loader.getSkills();
```

| API | Role |
| --- | --- |
| `new DefaultResourceLoader({ cwd, agentDir, skillsOverride? })` | Construct loader for project + agent dirs |
| `await loader.reload()` | Discover and resolve skills (and other resources) |
| `loader.getSkills()` | Returns `{ skills, diagnostics }` |
| `createAgentSession({ resourceLoader, sessionManager })` | Attach loaded skills to a session |

### `skillsOverride`

Optional hook to filter, merge, or replace discovered skills before session use:

```ts
const customSkill: Skill = {
  name: "my-skill",
  description: "Custom project instructions",
  filePath: "/virtual/SKILL.md",
  baseDir: "/virtual",
  sourceInfo: createSyntheticSourceInfo("/virtual/SKILL.md", { source: "sdk" }),
  disableModelInvocation: false,
};

const loader = new DefaultResourceLoader({
  cwd: process.cwd(),
  agentDir: getAgentDir(),
  skillsOverride: (current) => {
    const filteredSkills = current.skills.filter(
      (s) => s.name.includes("browser") || s.name.includes("search"),
    );
    return {
      skills: [...filteredSkills, customSkill],
      diagnostics: current.diagnostics,
    };
  },
});
```

Override contract:

- Input `current` has `skills` and `diagnostics`.
- Return value must include `skills` and `diagnostics` (pass through or replace).
- Use cases: keep only matching names, inject inline `Skill` objects, preserve diagnostics.

## Package skills

A package skill is declared on the package and enabled via settings.

**Package manifest** (`package.json`):

```json
{
  "name": "fake-pkg-web-fetch",
  "version": "1.0.0",
  "pi": {
    "skills": ["skills/web-fetch"]
  }
}
```

**Settings** (`settings.json` under agent dir or project `.pi`):

```json
{
  "packages": ["/path/to/fake-package-web-fetch"]
}
```

| Setting key | Value | Effect |
| --- | --- | --- |
| `packages` | array of package directory paths | Registers package resources, including skills listed under `pi.skills` |

User-scope settings live under `agentDir`; project-scope under `{cwd}/.pi`.

## Collision precedence

When multiple skills share the same `name`, exactly one wins. Precedence (highest first):

```text
project (.pi/skills)  >  user (agentDir/skills)  >  package (pi.skills)
```

| Winner | Over | Verified behavior |
| --- | --- | --- |
| User auto-discovered skill | Package skill | Winner `filePath` and `description` are the user skill |
| Project auto-discovered skill | Package skill | Winner is the project skill |
| Project skill | User skill (and package) | Full stack: project beats user beats package |

Winning skill identity is the loaded `Skill` entry with that `name` after `reload()`; losing definitions are not returned in `skills`.

### Collision diagnostics

`getSkills().diagnostics` can include collision entries:

| Field | Meaning |
| --- | --- |
| `type` | `"collision"` |
| `collision.name` | Shared skill name (e.g. `"web-fetch"`) |
| `collision.loserPath` | Path of the skill that lost (package path contains the package dir name) |

Example assertion shape from the regression suite: when a user skill overrides a package skill, diagnostics include a `collision` for `web-fetch` whose `loserPath` contains the package directory (e.g. `fake-package`).

## Fixture catalog

| Fixture path | Frontmatter | Role |
| --- | --- | --- |
| `test/fixtures/skills/valid-skill/SKILL.md` | `name` + `description` | Valid Agent Skills–style skill |
| `test/fixtures/skills/disable-model-invocation/SKILL.md` | + `disable-model-invocation: true` | Manual-only; `/skill:disable-model-invocation` |
| `test/fixtures/skills/missing-description/SKILL.md` | `name` only | Incomplete: no `description` |
| `test/fixtures/skills-collision/first/calendar/SKILL.md` | `name: calendar` + description | Named collision sample |

## Authoring checklist

<Steps>
  <Step title="Create the skill directory">
    Place the skill under project (`.pi/skills/{name}/`), user (`{agentDir}/skills/{name}/`), or a package (`skills/{name}/` with `pi.skills` entry).
  </Step>
  <Step title="Write SKILL.md frontmatter">
    Set `name` and `description`. Optionally set `disable-model-invocation: true` for manual-only skills.
  </Step>
  <Step title="Add markdown body">
    Body after the closing `---` is the skill content loaded as specialized instructions.
  </Step>
  <Step title="Register packages if needed">
    For package skills, list the skill path in `package.json` `pi.skills` and add the package path to `settings.json` `packages`.
  </Step>
  <Step title="Verify with the loader">
    Construct `DefaultResourceLoader`, `await reload()`, then inspect `getSkills()` for the expected `name`, `description`, `filePath`, and any collision diagnostics.
  </Step>
</Steps>

## SDK session wiring

Minimal session with a custom loader and in-memory session manager:

```ts
const { session } = await createAgentSession({
  resourceLoader: loader,
  sessionManager: SessionManager.inMemory(),
});
// ...
session.dispose();
```

Skills contribute specialized instructions to the system prompt for that session. Filter or replace them only through `skillsOverride` (or by controlling which files/packages are present on disk).

## Related pages

<CardGroup>
  <Card title="Themes and Pi packages" href="/themes-and-packages">
    How shareable Pi packages declare and bundle skills with extensions, templates, and themes.
  </Card>
  <Card title="SDK" href="/sdk">
    Embed pi with the package main export: resource loaders, settings, and session construction.
  </Card>
  <Card title="SDK examples" href="/sdk-examples">
    Copy-paste recipes including skills configuration with DefaultResourceLoader.
  </Card>
  <Card title="Settings" href="/settings">
    Settings load paths and merge behavior for package lists and related session options.
  </Card>
  <Card title="Extensions" href="/extensions">
    TypeScript extension registration alongside skills and other resource types.
  </Card>
  <Card title="Context files" href="/context-files">
    Separate project context file discovery and injection into sessions.
  </Card>
</CardGroup>
