Skills
SKILL.md files agents load on demand via skill_load.
drover implements the Agent Skills
specification. A skill is a directory containing a SKILL.md file plus
optional scripts/, references/, and assets/ subdirectories. The
agent’s system prompt advertises name + description; the body loads on
demand via skill_load; supporting files are pulled lazily via
skill_resource. Progressive disclosure keeps the system prompt small.
Directory layout
my-skill/
├── SKILL.md # required: metadata + instructions
├── scripts/ # optional: executable code agents can run
├── references/ # optional: extended documentation
└── assets/ # optional: templates, schemas, images
SKILL.md format
---
name: pdf-processing
description: Extract text and tables from PDFs, fill forms, merge files. Use when working with PDF documents.
license: Apache-2.0
compatibility: Requires poppler-utils
metadata:
author: example-org
version: "1.0"
allowed-tools: Bash(jq:*) Read Edit
---
# pdf-processing
Step-by-step instructions go here. See [references/REFERENCE.md](references/REFERENCE.md)
for the full API.Frontmatter fields
| field | required | constraints |
|---|---|---|
name | yes | 1-64 chars, [a-z0-9-]+, no leading/trailing/consecutive hyphens. Must equal the parent directory name. |
description | yes | 1-1024 chars. Describes what the skill does and when to use it. |
license | no | License name or reference to a bundled file. |
compatibility | no | ≤500 chars. Environment requirements (system packages, network access, etc.). |
metadata | no | Map of string keys to string values for clients to use. |
allowed-tools | no | Space-separated (or list) of pre-approved tools. Experimental. |
Any other frontmatter keys are preserved verbatim under spec.extra.
Build a registry
import { createSkillRegistry, scanSkillDirs } from "@drover/skills";
const specs = await scanSkillDirs(["./skills", "./node_modules/@my-org/shared-skills"]);
const skills = createSkillRegistry(specs);scanSkillDirs walks each root recursively (max depth 3 by default),
finds SKILL.md files, dedups by name with first-wins. Pass agent-
local dirs ahead of shared library dirs so local skills shadow.
Strict vs lenient parsing
By default, any spec violation throws and stops the scan. Use
mode: "lenient" plus an onIssue callback to load best-effort and
surface warnings:
const specs = await scanSkillDirs([root], {
mode: "lenient",
onIssue: (file, issues) => {
for (const i of issues) console.warn(`${file} — ${i.field}: ${i.message}`);
},
});Declare in a spec
const spec = defineAgent({
/* ... */
skills: ["pdf-processing", "factcheck"],
});spec.skills is the allowlist. Skills outside it are unreachable for
this agent even if they’re in the registry — progressive disclosure
shouldn’t bypass least-privilege intent.
Wire on the run
runAgent(spec, input, { skills });The harness:
- Auto-injects
skill_loadandskill_resource, both gated byspec.skills. - Appends an “Available skills” section to the system prompt listing
only allowlisted name + one-line description (plus a
compatibilityhint when set).
Progressive disclosure in practice
- Startup (~100 tokens per skill): name + description in the system prompt.
- Activation (full body): the model calls
skill_load(name="<x>")when it decides the skill is relevant. - Resources (on demand): if the body references
scripts/extract.pyorreferences/REFERENCE.md, the model callsskill_resource(name="<x>", resource="<relative-path>").
skill_resource denies absolute paths and any .. segments — reads
stay inside the skill directory.
Inspect a registry
skills.has("pdf-processing"); // boolean
skills.get("pdf-processing"); // SkillSpec | undefined
skills.list(); // SkillSpec[]import { listSkillResources, readSkillResource } from "@drover/skills";
const spec = skills.get("pdf-processing")!;
const res = await listSkillResources(spec);
// { scripts: ["extract.py"], references: ["REFERENCE.md"], assets: [], other: [...] }
const text = await readSkillResource(spec, "references/REFERENCE.md");Alternative layouts
drover supports the canonical skills/<name>/SKILL.md layout out of
the box. For flat layouts (skills/<name>.md) or any other on-disk
shape, write a custom loader that produces SkillSpec[] and pass to
createSkillRegistry — the registry is decoupled from the on-disk
layout.
Authoring guidance
- Keep the
SKILL.mdbody under 500 lines / ~5000 tokens. - Push detailed reference material to
references/so the model only loads it when explicitly needed. - Use relative paths from the skill root for file references; keep them one level deep.
- Validate with the skills-ref reference linter before committing.