Agent Skills ↗︎ are on-demand instructions, references, and scripts for an agent. Each skill is a directory with a SKILL.md file. The agent sees a short catalog of skill names and descriptions. It loads a full skill only when a task matches it, so a large library of skills does not fill every prompt.
For background on the format and how agents use it, refer to Agent Skills concepts.
A skill is a directory named after the skill. It holds a SKILL.md file and, optionally, other files:
release-notes/
SKILL.md
references/style-guide.md
scripts/format-release-notes.js
assets/logo.pngSKILL.md starts with YAML frontmatter, followed by Markdown instructions:
---
name: release-notes
description: Turn a list of changes into release notes. Use when the user asks for release notes or a changelog entry.
metadata:
author: my-team
---
1. Read `references/style-guide.md`.
2. Group the changes under Features, Fixes, and Other.The files in scripts/, references/, and assets/ are the skill's resources. The model reads them when the instructions call for them.
Skills come from a source. Return sources from getSkills() in a Think agent:
import { Think, skills } from "@cloudflare/think";
export class MyAgent extends Think {
getSkills() {
return [
skills.workspace(this.workspace),
skills.r2(this.env.SKILLS_BUCKET, { prefix: "skills/" }),
];
}
}import { Think, skills } from "@cloudflare/think";
export class MyAgent extends Think<Env> {
getSkills() {
return [
skills.workspace(this.workspace),
skills.r2(this.env.SKILLS_BUCKET, { prefix: "skills/" }),
];
}
}| Source | Reads skills from |
|---|---|
skills.workspace() |
Files in a @cloudflare/computer Workspace |
skills.r2() |
Objects in an R2 bucket |
skills.manifest() |
Skills defined in code |
Sources are applied in order. The first source to provide a skill name wins. Later duplicates are skipped with a logged warning. Think lists every source again before each turn, so new, edited, and deleted skills take effect on the next turn.
The Pi harness takes the same sources through skills(sources) from agents/harness/pi.
When at least one skill is available, the agent gets these tools:
| Tool | Purpose |
|---|---|
activate_skill |
Load a skill's instructions and its list of resources |
read_skill_resource |
Read a resource by { name, path } or a qualified path such as name/path |
run_skill_script |
Run a bundled script, when a script runner is set |
The catalog is added to the system prompt as its own context block, below your getSystemPrompt() instructions. When a skill changes mid-conversation, Think does not rebuild the system prompt, so the model provider's prompt cache stays warm. The new catalog reaches the model as a reminder on the next turn and moves into the system prompt at the next compaction or history clear.
Use getSystemPrompt() or a context block from configureContext() for instructions that apply to every turn. Use skills for procedures, references, and scripts that only some tasks need.
Every SKILL.md is checked against these rules from the Agent Skills specification ↗︎:
namemust be 1 to 64 lowercase letters, digits, and hyphens. It cannot start or end with a hyphen or contain--. It must match the skill's directory name.descriptionis required and can be at most 1,024 characters.compatibility, when present, must be 1 to 500 characters.metadata, when present, must map string keys to string values.allowed-tools, when present, must be a space-separated string.
Skills load the way the specification's guide for agents ↗︎ recommends. Skills written for other agents often break a rule, so a skill that breaks one still loads, and the agent logs a warning:
| Problem | What happens |
|---|---|
name breaks the format, is too long, or does not match the directory |
Loads, with a warning |
name is missing |
Loads with the directory name, with a warning |
description or compatibility is too long |
Loads, with a warning |
license, metadata, or allowed-tools is invalid |
Loads without that field, with a warning |
A value contains an unquoted : , so the YAML does not parse |
Loads after quoting the value, with a warning |
description is missing |
Skipped and logged |
name contains /, \, or control characters, or is . or .. |
Skipped and logged |
| The frontmatter is missing or still not YAML | Skipped and logged |
Skipped skills are not shown to the model. Frontmatter keys that the specification does not define are kept in skill.extraFields. A skill with disable-model-invocation: true is not shown to the model.
To check a skill before you write or publish it, parse it with parseSkill() from agents/skills. A skill follows the specification when it parses with no warnings:
import { parseSkill } from "agents/skills";
const parsed = parseSkill(markdown, { directory: "release-notes" });
if (!parsed.ok) {
console.log(parsed.error.message); // the skill cannot be used
} else {
for (const warning of parsed.warnings) {
console.log(warning.code, warning.message); // for example, "name-mismatch"
}
}import { parseSkill } from "agents/skills";
const parsed = parseSkill(markdown, { directory: "release-notes" });
if (!parsed.ok) {
console.log(parsed.error.message); // the skill cannot be used
} else {
for (const warning of parsed.warnings) {
console.log(warning.code, warning.message); // for example, "name-mismatch"
}
}The parser, sources, and catalog live in agents/skills and do not depend on Think. loadSkillCatalog() merges sources. createSkillTools() from agents/skills/tools/ai-sdk builds the AI SDK ↗︎ tools for a catalog:
import { streamText } from "ai";
import { loadSkillCatalog, workspace } from "agents/skills";
import { createSkillTools } from "agents/skills/tools/ai-sdk";
// `computerWorkspace` is a Workspace from @cloudflare/computer.
const catalog = await loadSkillCatalog([workspace(computerWorkspace)]);
const result = streamText({
model,
system: catalog.prompt() ?? undefined,
tools: createSkillTools(catalog),
messages,
});import { streamText } from "ai";
import { loadSkillCatalog, workspace } from "agents/skills";
import { createSkillTools } from "agents/skills/tools/ai-sdk";
// `computerWorkspace` is a Workspace from @cloudflare/computer.
const catalog = await loadSkillCatalog([workspace(computerWorkspace)]);
const result = streamText({
model,
system: catalog.prompt() ?? undefined,
tools: createSkillTools(catalog),
messages,
});