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:

  1. Parses YAML front-matter using parseSkillFrontmatter() (lines 25-74)
  2. Builds a SkillMetadata object containing name, description, path, and options
  3. Excludes built-in commands (model, resume, new) to prevent shadowing
  4. 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:

  1. Receives { skill: string, args?: string } parameters
  2. Locates the skill in experimental_context.skills
  3. Reads the skill file inside the sandbox
  4. Processes the body through extractSkillBody() in packages/agent/skills/loader.ts
  5. Substitutes $ARGUMENTS with user input via substituteArguments()
  6. Injects the directory path via injectSkillDirectory() (lines 38-40)
  7. 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() in packages/agent/skills/discovery.ts, with results cached through createSkillsCache()
  • Configuration controls invocation behavior through disable-model-invocation, user-invocable, and allowed-tools front-matter keys
  • Invocation occurs through the skill tool (packages/agent/tools/skill.ts), which processes $ARGUMENTS and $SKILL_DIR placeholders
  • 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →