pleach
Build

Skills

SkillLoader loads markdown skills from three sources, matches them by user intent, and injects the body into the system prompt as a contribution.

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 for the contribution shape the skill body becomes, and 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, 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.

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

Skill shape

FieldTypeNotes
namestringkebab-case identifier
descriptionstringOne-line summary
versionstring | undefinedSemver, optional
authorstring | undefinedEmail or identifier
allowedToolsstring[] | undefinedWhitelist
disallowedToolsstring[] | undefinedBlacklist
toolMode"inherit" | "none" | undefinedParent's resolved tools, or pure reasoning
intentsstring[] | undefinedKeywords from the intent classifier
activationenumauto / manual / intent-matched (default intent-matched)
contentstringMarkdown body, injected into the prompt
sourceenumbuiltin / org / user
pathstring | undefinedFilesystem path (builtin skills)
executionobject | undefinedmaxSteps, 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

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.

OrderSourceWhere it readsNotes
1Builtinskills/builtin/<name>/SKILL.md (server only)Skipped in the browser; missing dir is fine
2Org / user store[orgId, "skills"] then [orgId, userId, "skills"]Up to 50 per namespace
3Subagent specsThe subagentSpecs array passed to the constructorSkipped if a skill with the same name already exists from sources 1 or 2

The constructor takes the store and the spec list:

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

MethodReturnsEffect
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 | undefinedExact-match cache lookup
getAll()Skill[]Every cached skill
sizenumberCache 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.

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

---
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:

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

On this page