Deep Agents
AgentContextOrchestratorRetrievalText2SQLToolbox

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 workflow

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

skills/deploy/SKILL.md
---
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.
SKILL.md
SKILL.md

The frontmatter requires exactly two fields:

FieldTypeDescription
namestringUnique skill identifier
descriptionstringShort 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:

  1. An instructions fragment with guidance on how the LLM should discover and use skills
  2. One skill fragment per discovered skill with name, path, and description

The instructions tell the LLM to:

  • Match user requests to skill names or descriptions
  • Read the SKILL.md file 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.md and 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 the available_skills fragment after you've called set(skills(sandbox)). Used by the agent framework to populate GuardrailContext.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:

FieldTypeDescription
namestringSkill name
descriptionstringSkill description
hoststringPath to the SKILL.md on the host
sandboxstringPath 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

OptionTypeDefaultDescription
topNnumber5Maximum number of matches to return
thresholdnumber0Minimum 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