# Skills (/docs/skills)



A skill is a markdown file with YAML frontmatter. The runtime
loads it, indexes it by intent, and injects its body into the
system prompt when the user's intent matches. The variable surface
is the markdown body and the activation policy in the frontmatter;
the loader, the cache, and the intent matching are structural. See
[Prompts](/docs/prompts) for the contribution shape the skill body
becomes, and [Subagents](/docs/subagents) for how a skill with an
`execution` block delegates to a child runtime.

Skill is one of three concepts in the
[composing-agents cluster](/docs/agents#the-composing-agents-cluster),
alongside the subagent spawn that executes it and the agent
profile that scores it. The skill carries the spec name; a matching
subagent invocation writes one entry into the profile keyed by
that same name.

Ships from the `@pleach/core/skills` barrel — a real, emitted subpath
export (`SkillLoader`, `parseSkillFrontmatter`, and the `Skill` /
`SkillFrontmatter` types all import from it). There are no
`/loader`, `/parser`, or `/types` sub-subpaths; import everything from
the `@pleach/core/skills` barrel.

```typescript
import { SkillLoader, parseSkillFrontmatter } from "@pleach/core/skills";
import type { Skill, SkillFrontmatter } from "@pleach/core/skills";
```

<SourceMeta source="{ label: &#x22;src/skills/&#x22;, href: &#x22;https://github.com/pleachhq/core/tree/main/src/skills&#x22; }" />

## `Skill` shape [#skill-shape]

| Field             | Type                               | Notes                                                           |
| ----------------- | ---------------------------------- | --------------------------------------------------------------- |
| `name`            | string                             | kebab-case identifier                                           |
| `description`     | string                             | One-line summary                                                |
| `version`         | string \| undefined                | Semver, optional                                                |
| `author`          | string \| undefined                | Email or identifier                                             |
| `allowedTools`    | string\[] \| undefined             | Whitelist                                                       |
| `disallowedTools` | string\[] \| undefined             | Blacklist                                                       |
| `toolMode`        | `"inherit" \| "none"` \| undefined | Parent's resolved tools, or pure reasoning                      |
| `intents`         | string\[] \| undefined             | Keywords from the intent classifier                             |
| `activation`      | enum                               | `auto` / `manual` / `intent-matched` (default `intent-matched`) |
| `content`         | string                             | Markdown body, injected into the prompt                         |
| `source`          | enum                               | `builtin` / `org` / `user`                                      |
| `path`            | string \| undefined                | Filesystem path (builtin skills)                                |
| `execution`       | object \| undefined                | `maxSteps`, `timeout`, `canDelegate`                            |

`activation` decides how the loader surfaces the skill. `auto`
and `intent-matched` are both returned by `getByIntent(intent)`;
`manual` skills are returned only by `getByName(name)`.

## Loader sources [#loader-sources]

`SkillLoader.loadAll(orgId?, userId?)` walks three sources and
unions the results into a single cache. Builtin first, then store,
then subagent-spec migration; the loader returns the merged list
and caches by name.

| Order | Source           | Where it reads                                       | Notes                                                                      |
| ----- | ---------------- | ---------------------------------------------------- | -------------------------------------------------------------------------- |
| 1     | Builtin          | `skills/builtin/<name>/SKILL.md` (server only)       | Skipped in the browser; missing dir is fine                                |
| 2     | Org / user store | `[orgId, "skills"]` then `[orgId, userId, "skills"]` | Up to 50 per namespace                                                     |
| 3     | Subagent specs   | The `subagentSpecs` array passed to the constructor  | Skipped if a skill with the same `name` already exists from sources 1 or 2 |

The constructor takes the store and the spec list:

```typescript
const loader = new SkillLoader({
  store,                                  // anything implementing { search }
  subagentSpecs: [
    {
      name:               "researcher",
      description:        "Fan out searches.",
      tools:              ["search_corpus", "search_news"],
      maxSteps:           5,
      timeout:            120_000,
      systemPromptSuffix: "You are a research subagent.",
    },
  ],
});

await loader.loadAll(orgId, userId);
```

A migrated subagent spec lands as a builtin skill with
`activation: "manual"` — the spec was already gated by an
explicit delegation, not by intent matching.

## Lookups [#lookups]

| Method                     | Returns              | Effect                                                                                       |
| -------------------------- | -------------------- | -------------------------------------------------------------------------------------------- |
| `loadAll(orgId?, userId?)` | `Skill[]`            | Walks the three sources, clears and repopulates the cache                                    |
| `getByIntent(intent)`      | `Skill[]`            | Skills whose `intents` include `intent` and whose `activation` is `auto` or `intent-matched` |
| `getByName(name)`          | `Skill \| undefined` | Exact-match cache lookup                                                                     |
| `getAll()`                 | `Skill[]`            | Every cached skill                                                                           |
| `size`                     | number               | Cache size                                                                                   |

`loadAll` is the only async method; everything else reads from the
in-memory cache the load populated.

The lookup path is the synchronous half — call `loadAll` once at
session start, then route per-turn intents through the cache.

```typescript
await loader.loadAll(orgId, userId);

const matched = loader.getByIntent("vendor_evaluation");
for (const skill of matched) {
  systemPrompt.append(skill.content);
}

const fallback = loader.getByName("researcher");
if (fallback) await delegateToSubagent(fallback);
```

`getByIntent` filters to `auto` and `intent-matched` activations.
`getByName` is the only path to a `manual` skill.

## SKILL.md format [#skillmd-format]

```markdown
---
name: vendor-evaluation
description: Score vendors against a fixed rubric.
version: 1.0.0
author: ops@example.com
allowed-tools:
  - search_web
  - fetch_url
intents:
  - vendor_evaluation
  - rfp_scoring
activation: intent-matched
execution:
  maxSteps: 8
  timeout: 180000
---

You are a vendor-evaluation agent. Score each candidate against the
fixed rubric in the user's request. Cite every claim with the URL
you pulled it from.
```

`parseSkillFrontmatter(fileContent, source, filePath?)` is exported
directly for callers that want to parse a SKILL.md without going
through the loader (tests, custom store adapters). The parser is
intentionally a lightweight YAML reader — keys, scalars, inline
arrays, indented arrays, and one level of nested objects. Anything
more elaborate belongs in a real YAML library.

A direct parse, no loader involved:

```typescript
import { parseSkillFrontmatter } from "@pleach/core/skills";

const raw = await readFile("./skills/builtin/researcher/SKILL.md", "utf-8");
const skill = parseSkillFrontmatter(raw, "builtin", "./skills/builtin/researcher/SKILL.md");

console.log(skill.name, skill.activation, skill.allowedTools);
```

## Where to go next [#where-to-go-next]

<Cards>
  <Card title="Subagents" href="/docs/subagents" description="A skill with an `execution` block can be delegated as a subagent." />

  <Card title="Prompts" href="/docs/prompts" description="The skill's markdown body lands as a system-prompt section contribution." />

  <Card title="Tools" href="/docs/tools" description="`allowedTools` / `disallowedTools` / `toolMode` gate the tool registry per skill." />

  <Card title="Subpath exports" href="/docs/subpath-exports" description="`@pleach/core/skills` is a real emitted barrel — import SkillLoader, parseSkillFrontmatter, Skill, and SkillFrontmatter from it." />
</Cards>
