SkillsToolset lets an agent discover and load skill instructions at runtime instead of embedding every capability in the system prompt up-front. It is a TypeScript port of pydantic-ai-skills.
What it’s for
As you build more capable agents, the system prompt grows. Eventually you hit a wall: you can’t fit every skill’s instructions into context, the model gets confused by too many capabilities at once, and updating one skill means re-deploying the whole agent.
SkillsToolset solves this with progressive disclosure:
- At startup the agent is told only that skills exist — not what they contain.
- The agent calls
skills_list to see what’s available (names + one-line descriptions).
- It calls
skills_load to read the full instructions for the skill it needs right now.
- It follows those instructions, then loads a different skill if the task changes.
This keeps the active context small, makes it trivial to add or update skills without touching the agent, and lets the agent choose the right capability for each sub-task dynamically.
Typical use cases:
- Coding agents that can switch between “write tests”, “write docs”, “refactor” skill modes
- Research agents with separate skills for web search, citation formatting, and summarisation
- Customer support bots with different skills per product line
- Any agent where capabilities should be modular and updateable independently
Installation
// Sub-path (community only — lighter import)
import { SkillsToolset } from "@vibesjs/sdk/community";
// Or from the main entry point
import { SkillsToolset } from "@vibesjs/sdk";
No extra package needed — SkillsToolset is bundled inside @vibesjs/sdk.
Quick start
Point the toolset at a directory of skill files:
import { Agent } from "@vibesjs/sdk";
import { SkillsToolset } from "@vibesjs/sdk/community";
import { anthropic } from "@ai-sdk/anthropic";
const agent = new Agent({
model: anthropic("claude-sonnet-4-6"),
systemPrompt: `You are a helpful assistant.
When you need a specific capability, first call skills_list to see what is available,
then call skills_load to get the full instructions for the skill you need.`,
toolsets: [new SkillsToolset(".claude/agents")],
});
const result = await agent.run("Search the web for the latest news on TypeScript 6.");
// The agent will call skills_list, find "web-search", call skills_load("web-search"),
// then follow those instructions to complete the task.
| Tool | What it does |
|---|
skills_list | Returns all skill names and descriptions. Call this first to discover what’s available. |
skills_load | Returns the full markdown content of a named skill so the agent can follow its instructions. |
skills_list
Takes no parameters. Returns a JSON array of { name, description } objects:
[
{ "name": "web-search", "description": "Search the web for current information" },
{ "name": "calculator", "description": "Perform arithmetic calculations" }
]
skills_load
Parameters:
name string The skill name (as returned by skills_list)
Returns the full markdown content of the skill file so the agent can follow its instructions. If the skill is not found, returns a JSON error object with a suggestion to call skills_list.
Skills are plain markdown files with optional YAML frontmatter. Organize them in any directory structure you like.
Flat file (my-skill.md)
---
name: web-search
description: Search the web for current information
---
# Web Search
When the user asks about recent events or facts you are uncertain about:
1. Identify the key search query from the user's request.
2. Use the `web_fetch` tool to retrieve results.
3. Summarise findings in 2–3 sentences, citing the source URL.
Packaged skill (data-analysis/SKILL.md)
For more complex skills that include reference files, use a subdirectory:
.claude/agents/
├── web-search.md ← simple flat skill
└── data-analysis/
└── SKILL.md ← packaged skill
The subdirectory name (data-analysis) is used as the skill name when the frontmatter name field is absent.
Frontmatter fields
| Field | Required | Description |
|---|
name | No | Skill name shown in skills_list (defaults to filename without .md) |
description | No | One-line summary shown in skills_list |
The @vibesjs/sdk agent skill (vibes-sdk.md) is itself a valid skill file — you can load it with SkillsToolset by pointing it at your .claude/agents/ directory.
Directory loader: DirectorySkillLoader
When you pass a string to SkillsToolset, it creates a DirectorySkillLoader internally. You can also construct one directly if you need to pass it to other code:
import { DirectorySkillLoader, SkillsToolset } from "@vibesjs/sdk/community";
const loader = new DirectorySkillLoader("/path/to/skills");
// Check what's available without an agent
const skills = await loader.listSkills();
console.log(skills);
// [{ name: "web-search", description: "Search the web..." }, ...]
// Load a specific skill
const skill = await loader.loadSkill("web-search");
console.log(skill?.content);
Custom loader
Pass any object implementing SkillLoader to load skills from any source — a remote registry, a database, or a generated list:
import type { SkillLoader, Skill, SkillMeta } from "@vibesjs/sdk/community";
class RemoteSkillLoader implements SkillLoader {
async listSkills(): Promise<SkillMeta[]> {
const res = await fetch("https://my-registry.example.com/skills");
return res.json();
}
async loadSkill(name: string): Promise<Skill | null> {
const res = await fetch(`https://my-registry.example.com/skills/${name}`);
if (!res.ok) return null;
return res.json();
}
}
const agent = new Agent({
model: anthropic("claude-sonnet-4-6"),
toolsets: [new SkillsToolset(new RemoteSkillLoader())],
});
import { Agent } from "@vibesjs/sdk";
import { TodoToolset, SkillsToolset } from "@vibesjs/sdk/community";
const agent = new Agent({
model: anthropic("claude-sonnet-4-6"),
toolsets: [
new SkillsToolset(".claude/agents"), // dynamic capability loading
new TodoToolset(), // task tracking while working
],
});
With dependency injection
SkillsToolset is generic over TDeps so its type signature aligns with any agent:
type Deps = { userId: string };
const agent = new Agent<Deps>({
model: anthropic("claude-sonnet-4-6"),
toolsets: [new SkillsToolset<Deps>(".claude/agents")],
});
Types
interface SkillMeta {
name: string; // unique identifier
description: string; // shown in skills_list
}
Skill
interface Skill extends SkillMeta {
content: string; // full markdown content returned by skills_load
}
SkillLoader
interface SkillLoader {
listSkills(): Promise<SkillMeta[]>;
loadSkill(name: string): Promise<Skill | null>;
}
Want to build your own?
SkillsToolset was ported from the pydantic-ai ecosystem using the port-pydantic-ai-community-plugins agent skill. Install the skill to get step-by-step guidance for porting any pydantic-ai plugin into a community toolset:
mkdir -p .claude/agents && curl -fsSL https://raw.githubusercontent.com/a7ul/vibes/main/packages/sdk/skills/port-pydantic-ai-community-plugins.md -o .claude/agents/port-pydantic-ai-community-plugins.md
See the Agent Skill docs for more detail.