Skills
Progressive disclosure of agent capabilities through discoverable SKILL.md files
Skills are modular instruction packages that extend an agent's capabilities. They follow Anthropic's progressive disclosure pattern: at startup only metadata (name and description) is loaded into context, and the LLM reads the full instructions from disk only when a skill is relevant.
How Progressive Disclosure Works
Startup:
skills/
deploy/SKILL.md → { name: "deploy", description: "Deploy to production" }
refactor/SKILL.md → { name: "refactor", description: "Refactor code" }
Only name + description injected into system prompt.
Runtime:
User says "deploy the app"
→ LLM matches "deploy" skill
→ LLM reads /skills/deploy/SKILL.md for full instructions
→ LLM follows the workflowThis keeps the system prompt small while making dozens of skills discoverable.
The SKILL.md Format
Each skill lives in its own directory and is defined by a SKILL.md file with YAML frontmatter:
---
name: deploy
description: Deploy services to production with zero-downtime rollouts
---
## Workflow
1. Run pre-deploy checks
2. Build the container image
3. Push to registry
4. Roll out with health checks
## Output
Return a summary of what was deployed and the health check results.The frontmatter requires exactly two fields:
| Field | Type | Description |
|---|---|---|
name | string | Unique skill identifier |
description | string | Short description shown to the LLM for matching |
The body after the frontmatter contains the full instructions the LLM reads at runtime.
Discovering Skills
discoverSkillsInDirectory() scans a directory for subdirectories containing SKILL.md files and returns their metadata:
import { discoverSkillsInDirectory } from '@deepagents/context';
const skills = discoverSkillsInDirectory('./skills');
// [
// { name: 'deploy', description: 'Deploy to production', path: './skills/deploy', skillMdPath: './skills/deploy/SKILL.md' },
// { name: 'refactor', description: 'Refactor code', path: './skills/refactor', skillMdPath: './skills/refactor/SKILL.md' },
// ]Tilde paths (~/) are expanded to the user's home directory. Directories without a SKILL.md are silently skipped. Invalid SKILL.md files log a warning and are excluded.
Loading Individual Skills
loadSkillMetadata() loads metadata from a single SKILL.md path. It parses only the frontmatter -- the body is never loaded into memory:
import { loadSkillMetadata } from '@deepagents/context';
const skill = loadSkillMetadata('./skills/deploy/SKILL.md');
// { name: 'deploy', description: 'Deploy to production', path: './skills/deploy', skillMdPath: './skills/deploy/SKILL.md' }Parsing Frontmatter
parseFrontmatter() parses a SKILL.md string into its frontmatter and body:
import { parseFrontmatter } from '@deepagents/context';
const content = `---
name: deploy
description: Deploy to production
---
## Workflow
1. Build
2. Push
3. Roll out`;
const { frontmatter, body } = parseFrontmatter(content);
// frontmatter: { name: 'deploy', description: 'Deploy to production' }
// body: '## Workflow\n1. Build\n2. Push\n3. Roll out'Throws if the frontmatter is missing or lacks the required name and description fields.
The skills() Fragment
The skills() fragment is a thin projection of sandbox.skills. The sandbox is now the single source of truth: you declare skill directories when you build the sandbox, and the factory (a) uploads the files into the sandbox filesystem and (b) parses each SKILL.md frontmatter into sandbox.skills. The fragment just renders that array into the system prompt.
import {
ContextEngine,
SqliteContextStore,
createBashTool,
role,
skills,
} from '@deepagents/context';
const sandbox = await createBashTool({
skills: [{ host: './skills', sandbox: '/skills' }],
});
const store = new SqliteContextStore('./chat.db');
const context = new ContextEngine({
store,
chatId: 'chat-001',
userId: 'user-001',
}).set(
role('You are a helpful assistant.'),
skills(sandbox),
);Filtering
Filtering now happens at the sandbox level: pass only the host directories you want, or pre-filter the skills array before building the sandbox. The old include / exclude options on skills() have been removed because they could declare a skill that wasn't actually uploaded.
Generated Fragment
The skills() function returns an available_skills fragment containing:
- An
instructionsfragment with guidance on how the LLM should discover and use skills - One
skillfragment per discovered skill withname,path, anddescription
The instructions tell the LLM to:
- Match user requests to skill names or descriptions
- Read the
SKILL.mdfile on demand (progressive disclosure) - Load only the specific reference files needed, not everything
- Prefer running existing scripts over rewriting code
- Announce which skills are being used and why
- Understand that there is no separate skill tool, invoke action, command, or API -- using a skill means reading its
SKILL.mdand following the workflow defined there
The instructions also embed correct/incorrect usage examples so the LLM avoids a common misunderstanding: if a user says "use the onboarding skill", the correct behavior is to read the onboarding SKILL.md and follow it, not to claim a separate skill tool needs to be called, invoked, or activated first.
Extracting Available Skills
Two ways to reach skill metadata after a sandbox has been built:
sandbox.skills— the discovered mounts, directly on the sandbox. This is the source of truth.ContextEngine.getSkillMounts()— reads the same data from theavailable_skillsfragment after you've calledset(skills(sandbox)). Used by the agent framework to populateGuardrailContext.availableSkills, enabling guardrails to detect when the LLM confuses skills with tools.
const sandbox = await createBashTool({
skills: [{ host: './skills', sandbox: '/skills' }],
});
const context = new ContextEngine({ store, chatId: 'chat-001', userId: 'user-001' })
.set(skills(sandbox));
const { mounts } = context.getSkillMounts();
// [
// {
// name: 'deploy',
// description: 'Deploy to production',
// host: './skills/deploy/SKILL.md',
// sandbox: '/skills/deploy/SKILL.md',
// },
// ]Each entry is a SkillPathMapping:
| Field | Type | Description |
|---|---|---|
name | string | Skill name |
description | string | Skill description |
host | string | Path to the SKILL.md on the host |
sandbox | string | Path to the SKILL.md inside the sandbox |
Skill Classifier
The skill classifier ranks skills against user messages using BM25 text similarity. Instead of relying on the LLM to pattern-match skill names at inference time, the classifier scores each skill's name and description against the user's input and surfaces the top matches as a soft nudge in the prompt.
BM25SkillClassifier
The default implementation indexes skill metadata (name + description) into a BM25 corpus and ranks matches by relevance score:
import { BM25SkillClassifier, discoverSkillsInDirectory } from '@deepagents/context';
const skills = discoverSkillsInDirectory('./skills');
const classifier = new BM25SkillClassifier(skills);
const matches = classifier.match('deploy my docker container', { topN: 3 });
// [
// { skill: { name: 'deploy-helper', ... }, score: 0.82 },
// { skill: { name: 'docker-expert', ... }, score: 0.71 },
// ]SkillClassifierOptions
| Option | Type | Default | Description |
|---|---|---|---|
topN | number | 5 | Maximum number of matches to return |
threshold | number | 0 | Minimum score to include a match |
skillsReminder()
skillsReminder() bridges the classifier with the reminder system. It matches user messages against skills and injects relevant matches as a <system-reminder>. See Skill Reminders for full usage, custom classifiers, and related predicates.
Types
interface SkillMetadata {
name: string;
description: string;
path: string;
skillMdPath: string;
}
interface SkillPathMapping {
name: string;
description: string;
host: string;
sandbox: string;
}
interface ParsedSkillMd {
frontmatter: { name: string; description: string; [key: string]: unknown };
body: string;
}
interface ISkillClassifier {
match(query: string, options?: SkillClassifierOptions): SkillMatch[];
}
interface SkillMatch {
skill: SkillMetadata;
score: number;
}
interface SkillClassifierOptions {
topN?: number;
threshold?: number;
}Next Steps
- Fragments - How fragments work
- Context Engine - Managing fragments with ContextEngine
- Agent Wrapper - Running agents in sandboxed containers