How to Use and Configure Skills in the Open Agents System: A Complete Guide
Open Agents skills are reusable Markdown-based modules stored in .claude/skills/ or .agents/skills/ directories that expose custom functionality through YAML front-matter configuration and automatic discovery mechanisms.
The Open Agents framework by Vercel Labs enables developers to extend LLM-powered agents through a flexible skill system. Skills function as self-contained logic units written in Markdown, allowing you to inject custom capabilities without modifying the core agent codebase. This guide covers the complete architecture, configuration options, and implementation patterns based on the actual source code in the vercel-labs/open-agents repository.
Understanding Open Agents Skills Architecture
The Open Agents skills system follows a discover-and-execute pipeline that bridges file-system storage with runtime LLM tool invocation.
Skill Discovery Process
Skills are discovered at runtime by the discoverSkills() function in packages/agent/skills/discovery.ts (lines 13-90). This utility scans directories returned by getSandboxSkillDirectories() from apps/web/lib/skills/directories.ts, searching for SKILL.md (preferred) or skill.md files.
For each discovered file, the system:
- Parses YAML front-matter using
parseSkillFrontmatter()(lines 25-74) - Builds a
SkillMetadataobject containing name, description, path, and options - Excludes built-in commands (
model,resume,new) to prevent shadowing - Deduplicates skill names case-insensitively
Caching and Performance
Discovered metadata is cached per-session via createSkillsCache() in apps/web/lib/skills-cache.ts (lines 59-120). The implementation uses an in-memory LRU map with a 4-hour TTL and optional Redis backing through createRedisClient. This prevents redundant file-system operations during active sessions.
REST API Exposure
Skills become available through the GET /api/sessions/:sessionId/skills endpoint defined in apps/web/app/api/sessions/[sessionId]/skills/route.ts (lines 30-84). The handler returns cached skills or triggers fresh discovery when ?refresh=1 is passed. Skills marked with userInvocable: false are filtered from the response.
Client-Side Consumption
The React hook useSessionSkills in apps/web/hooks/use-session-skills.ts (lines 14-44) wraps the REST endpoint with SWR caching. It exposes skills, isLoading, error, and a refresh() method for UI components.
Skill Invocation Flow
When an LLM invokes a skill, it calls the skill tool defined in packages/agent/tools/skill.ts (lines 55-107). The execution flow:
- Receives
{ skill: string, args?: string }parameters - Locates the skill in
experimental_context.skills - Reads the skill file inside the sandbox
- Processes the body through
extractSkillBody()inpackages/agent/skills/loader.ts - Substitutes
$ARGUMENTSwith user input viasubstituteArguments() - Injects the directory path via
injectSkillDirectory()(lines 38-40) - Returns the processed script to the LLM
Configuring Skills in Open Agents
Skills are configured through YAML front-matter and directory placement, supporting both project-local and global scopes.
Front-Matter Configuration Options
Each SKILL.md begins with YAML front-matter defining metadata and behavior. The schema is enforced by skillFrontmatterSchema in packages/agent/skills/types.ts (lines 7-34):
---
name: pdf
description: Generate a PDF from markdown
version: "1.0"
disable-model-invocation: false # Prevents automatic LLM invocation if true
user-invocable: true # Controls slash-command visibility
allowed-tools: "file,network" # Comma-separated tool whitelist
context: fork # Execution mode (optional)
agent: "my-agent" # Custom agent type (optional)
---
The frontmatterToOptions() function (lines 72-89) transforms kebab-case YAML keys into camelCase SkillOptions for internal use.
Directory Placement and Scoping
Skills support two placement strategies:
Project-Local Skills (single project only):
.claude/skills/your-skill/.agents/skills/your-skill/
Global Skills (available to all projects):
~/.agents/skills/your-skill/
The getSandboxSkillDirectories() function in apps/web/lib/skills/directories.ts (lines 19-27) assembles both locations:
return [
...getProjectSkillDirectories(sandbox.workingDirectory),
getGlobalSkillsDirectory(homeDirectory),
];
Installing Global Skills
Administrators can pre-install community skills using the installGlobalSkills() function in apps/web/lib/skills/global-skill-installer.ts (lines 15-38). This executes npx skills add <source> --skill <name> -g -y --copy inside the sandbox:
await installGlobalSkills({
sandbox,
globalSkillRefs: [
{ source: "vercel-labs/markdown-pdf", skillName: "pdf" }
],
});
Practical Implementation Examples
Creating a Custom Skill
Create a file structure like:
.project-root/
└─ .claude/
└─ skills/
└─ pdf/
├─ SKILL.md
└─ render.js
SKILL.md:
---
name: pdf
description: Convert markdown to a PDF document
allowed-tools: "file"
---
Skill directory: $SKILL_DIR
```js
// render.js will be executed by the model
const md = $ARGUMENTS;
const pdf = await convertMarkdownToPdf(md);
await writeFile(`${process.env.SKILL_DIR}/output.pdf`, pdf);
The `$ARGUMENTS` placeholder receives user input, while `$SKILL_DIR` is injected automatically by `injectSkillDirectory()`.
### Invoking Skills via Tool Calls
When the LLM decides to use a skill, it generates a tool call:
```json
{
"name": "skill",
"arguments": {
"skill": "pdf",
"args": "# My Report\n\nHello world!"
}
}
The skillTool.execute method processes this by reading the file, substituting arguments, and returning the executable content.
React Component Integration
Consume skills in the frontend using the provided hook:
import { useSessionSkills } from "@/hooks/use-session-skills";
function SkillPicker({ sessionId, sandboxConnected }) {
const { skills, isLoading, error, refresh } = useSessionSkills(
sessionId,
sandboxConnected,
);
if (isLoading) return <p>Loading skills…</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<select onChange={(e) => console.log(e.target.value)}>
{skills?.map((s) => (
<option key={s.name} value={s.name}>
{s.name} – {s.description}
</option>
))}
</select>
);
}
Global Skill Installation via API
Install skills programmatically for CI/CD or admin interfaces:
import { installGlobalSkills } from '@/lib/skills/global-skill-installer';
await installGlobalSkills({
sandbox,
globalSkillRefs: [
{ source: "owner/repo", skillName: "analyze-csv" }
],
});
After installation, the skill appears in subsequent calls to /api/sessions/:id/skills.
Summary
- Skills in Open Agents are Markdown files with YAML front-matter stored in
.claude/skills/or.agents/skills/directories - Discovery happens via
discoverSkills()inpackages/agent/skills/discovery.ts, with results cached throughcreateSkillsCache() - Configuration controls invocation behavior through
disable-model-invocation,user-invocable, andallowed-toolsfront-matter keys - Invocation occurs through the skill tool (
packages/agent/tools/skill.ts), which processes$ARGUMENTSand$SKILL_DIRplaceholders - Global installation supports reusable skills across projects via
installGlobalSkills()and the~/.agents/skills/directory
Frequently Asked Questions
What file format should I use for creating Open Agents skills?
Create a SKILL.md file (preferred) or skill.md file containing YAML front-matter between triple dashes followed by Markdown content. The front-matter must include at minimum a name and description field, with optional configuration flags like disable-model-invocation or user-invocable to control visibility and automatic execution.
How does the Open Agents system handle skill arguments and file paths?
The system substitutes the $ARGUMENTS placeholder with whatever text the user provides after the slash command. The skill directory path is injected automatically through the $SKILL_DIR placeholder (processed by injectSkillDirectory() in packages/agent/skills/loader.ts), allowing scripts to reference files relative to the skill location using process.env.SKILL_DIR.
Can I restrict which tools a skill is allowed to use?
Yes, use the allowed-tools front-matter key with a comma-separated list of permitted tools (e.g., allowed-tools: "file,network"). This restricts the skill's capabilities according to the allowedTools option defined in packages/agent/skills/types.ts. If omitted, the skill inherits default tool permissions from the agent context.
Where should I place skills for global access across all projects?
Place skills in the ~/.agents/skills/ directory or install them programmatically using installGlobalSkills() from apps/web/lib/skills/global-skill-installer.ts. Global skills appear in all sessions, while project-local skills placed in .claude/skills/ or .agents/skills/ only appear for that specific workspace.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →