Skills - Extensible Domain Knowledge
Level: Enhancement (beyond minimal agent) Prerequisites: Tool system, memory system
What This Adds
A markdown-based extensibility system for adding domain-specific instructions, workflows, and tools to the agent. Skills are discovered from multiple locations, loaded on-demand, and can bundle MCP servers, tools, and executable scripts.
Key Concept
| Question | Answer |
|---|---|
| What is a skill? | Markdown file with YAML frontmatter + instructions |
| When loaded? | On-demand via skill tool call |
| Where discovered? | 9 locations in priority order |
| Token cost? | Level 1: ~100 tokens at startup, Level 2: <5k when loaded |
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ SKILL DISCOVERY │
├─────────────────────────────────────────────────────────────────┤
│ .agents/skills/ → Workspace-local (highest priority) │
│ ~/.config/agents/skills/ → Global user skills │
│ ~/.config/amp/skills/ → Global amp skills │
│ .claude/skills/ → Claude-compatible (local) │
│ ~/.claude/skills/ → Claude-compatible (global) │
│ ~/.claude/plugins/cache/ → Plugin cache │
│ AMP_TOOLBOX / tools/ → Toolbox (deprecated) │
│ skills.path setting → Custom paths │
│ Built-in skills → 5 system skills (lowest priority) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ SKILL SERVICE │
├─────────────────────────────────────────────────────────────────┤
│ • File watching with 200ms debounce │
│ • Deduplication by name (first location wins) │
│ • MCP server derivation from bundled mcp.json │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ SKILL TOOL │
├─────────────────────────────────────────────────────────────────┤
│ name: "skill" │
│ input: { name: string, arguments?: string } │
│ output: <loaded_skill> XML with instructions │
└─────────────────────────────────────────────────────────────────┘
Discovery Order
Skills are discovered in priority order. First match wins for duplicate names.
| Priority | Path | Type |
|---|---|---|
| 1 | .agents/skills/ |
Workspace local |
| 2 | ~/.config/agents/skills/ |
Global agents |
| 3 | ~/.config/amp/skills/ |
Global amp |
| 4 | .claude/skills/ |
Local Claude compat |
| 5 | ~/.claude/skills/ |
Global Claude compat |
| 6 | ~/.claude/plugins/cache/ |
Plugin cache |
| 7 | AMP_TOOLBOX env / ~/.config/amp/tools |
Toolbox |
| 8 | skills.path setting |
Custom paths |
| 9 | Built-in | System default |
Skill Structure
Minimal Skill (Instructions Only)
.agents/skills/my-skill/
└── SKILL.md
Skill with Scripts
.agents/skills/my-skill/
├── SKILL.md
└── scripts/
└── my-script.sh
Complex Skill (Full)
.agents/skills/my-skill/
├── SKILL.md # Required: frontmatter + instructions
├── mcp.json # Optional: bundled MCP servers
├── scripts/ # Optional: executable scripts
│ └── run.sh
├── toolbox/ # Optional: auto-registered tools
│ └── my-tool
└── reference/ # Optional: additional docs
└── api.md
SKILL.md Format
Required Structure
---
name: my-skill-name
description: Does X when Y happens. Use for Z tasks.
---
# Instructions
The content here is injected into the conversation when loaded.
Frontmatter Fields
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Unique skill identifier |
description |
string | Yes | When to use this skill |
isolatedContext |
boolean | No | Run with isolated context |
disable-model-invocation |
boolean | No | Hide from model's skill list |
argument-hint |
string | No | Hint shown in available skills |
builtin-tools |
string[] | No | Tools to enable when loaded |
Example with All Options
---
name: web-scraper
description: Scrapes web pages and extracts structured data. Use for data extraction tasks.
argument-hint: <url>
builtin-tools:
- web_search
- read_web_page
---
# Web Scraper
Instructions for scraping web pages...
Token Budget (Progressive Disclosure)
| Level | When Loaded | Token Cost | Content |
|---|---|---|---|
| Level 1 | Startup | ~100 tokens | Name + description only |
| Level 2 | Tool invoked | <5k tokens | SKILL.md body |
| Level 3 | On demand | Variable | Reference files, scripts |
Keep SKILL.md under 500 lines. Split large content into separate files.
Built-in Skills
Five skills are always available:
| Skill | Purpose |
|---|---|
building-skills |
Guide for creating new skills |
bookkeeper |
Summarizes threads for handoff |
painter |
Image generation guidance |
code-review |
Automated code review |
migrating-to-skills |
Migrating from toolbox |
Built-in skills have lowest priority - workspace skills with the same name override them.
Skill Tool Schema
const skillTool = {
name: "skill",
description: "Load a skill to get specialized instructions for a task.",
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "The name of the skill to load"
},
arguments: {
type: "string",
description: "Optional arguments to pass to the skill"
}
},
required: ["name"]
}
};
Implementation
Discovery Function
interface Skill {
name: string;
description: string;
frontmatter: Record<string, unknown>;
content: string;
baseDir: string;
mcpServers?: MCPServerConfig[];
builtinTools?: string[];
files?: string[];
}
async function discoverSkills(
workspaceRoots: string[],
settings: Settings
): Promise<{ skills: Skill[]; errors: Error[] }> {
const skills: Skill[] = [];
const errors: Error[] = [];
const seenNames = new Set<string>();
const homedir = os.homedir();
// Discovery order (first match wins)
const paths = [
// 1. Workspace .agents/skills/
...workspaceRoots.map(r => path.join(r, ".agents", "skills")),
// 2. Global ~/.config/agents/skills/
path.join(homedir, ".config", "agents", "skills"),
// 3. Global ~/.config/amp/skills/
path.join(homedir, ".config", "amp", "skills"),
// 4. Local .claude/skills/
...workspaceRoots.map(r => path.join(r, ".claude", "skills")),
// 5. Global ~/.claude/skills/
path.join(homedir, ".claude", "skills"),
// 6. Plugin cache
path.join(homedir, ".claude", "plugins", "cache"),
// 7. Toolbox
process.env.AMP_TOOLBOX || path.join(homedir, ".config", "amp", "tools"),
// 8. Custom paths
...(settings["skills.path"] || [])
];
for (const skillPath of paths) {
const found = await scanSkillDirectory(skillPath);
for (const skill of found) {
if (!seenNames.has(skill.name)) {
seenNames.add(skill.name);
skills.push(skill);
}
}
}
// 9. Built-in skills (lowest priority)
for (const builtin of getBuiltinSkills()) {
if (!seenNames.has(builtin.name)) {
seenNames.add(builtin.name);
skills.push(builtin);
}
}
return { skills, errors };
}
Parsing SKILL.md
function parseSkillFile(content: string): { frontmatter: object; content: string } {
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
if (!match || !match[1] || !match[2]) {
throw new Error("SKILL.md must have YAML frontmatter");
}
const frontmatter = yaml.parse(match[1]);
if (!frontmatter.name || !frontmatter.description) {
throw new Error("SKILL.md frontmatter must include name and description");
}
return { frontmatter, content: match[2] };
}
Skill Loading
async function loadSkill(
skillName: string,
args: string | undefined,
availableSkills: Skill[]
): Promise<ToolResult> {
const skill = availableSkills.find(s => s.name === skillName);
if (!skill) {
return {
error: `Skill "${skillName}" not found. Available: ${availableSkills.map(s => s.name).join(", ")}`
};
}
// Build loaded skill XML
const output = [
`<loaded_skill name="${skill.name}">`,
skill.content,
args ? `\nArguments: ${args}` : "",
`</loaded_skill>`
].join("\n");
return { output };
}
Available Skills Prompt
function formatAvailableSkills(skills: Skill[]): string | null {
const visible = skills.filter(s => !s.frontmatter["disable-model-invocation"]);
if (visible.length === 0) return null;
const list = visible.map(s => {
const hint = s.frontmatter["argument-hint"] ? ` ${s.frontmatter["argument-hint"]}` : "";
return ` <skill>
<name>${s.name}</name>
<description>${s.description}</description>
</skill>`;
}).join("\n");
return `The following skills provide specialized instructions for specific tasks.
Use the skill tool to load a skill when the task matches its description.
<available_skills>
${list}
</available_skills>`;
}
File Watching
Skills support hot-reload with file watching:
const SKILL_WATCH_DEBOUNCE = 200; // ms
function watchSkills(
skillPaths: string[],
onChange: (skills: Skill[]) => void
): void {
const watcher = chokidar.watch(skillPaths, {
ignoreInitial: true,
depth: 2 // Only watch SKILL.md level
});
const debouncedReload = debounce(async () => {
const { skills } = await discoverSkills(workspaceRoots, settings);
onChange(skills);
}, SKILL_WATCH_DEBOUNCE);
watcher.on("change", debouncedReload);
watcher.on("add", debouncedReload);
watcher.on("unlink", debouncedReload);
}
Constants
| Constant | Value | Purpose |
|---|---|---|
| Max skill name | 64 chars | Name length limit |
| Max description | 1024 chars | Description length limit |
| Max files per skill | 100 | File count limit |
| Max single file | 512 KB | Single file size limit |
| Max total size | 2 MB | Total skill size limit |
| Watch debounce | 200 ms | File change debounce |
MCP Server Bundling
Skills can bundle MCP servers via mcp.json:
{
"mcpServers": {
"my-server": {
"command": "node",
"args": ["./server.js"],
"env": {}
}
}
}
When the skill is loaded, its MCP servers are started and their tools become available.
When to Add Skills
Add skill system when:
- Domain expertise - Specialized knowledge for specific domains
- Workflow automation - Common multi-step processes
- Tool bundles - Groups of MCP tools that work together
- Project customization - Project-specific instructions
Enhancement based on Amp Code v0.0.1769212917 patterns