Skills (Agent Skills)
Skills are markdown-based instruction packages that follow the Agent Skills open standard. Unlike gadgets (which are executable code), skills extend agent capabilities through prompt injection and context management. They are interoperable with Claude Code, Codex CLI, Gemini CLI, and 30+ other tools.
Quick Start
Section titled “Quick Start”import { AgentBuilder, Skill, SkillRegistry, discoverSkills } from 'llmist';
// Create a skill from contentconst reviewSkill = Skill.fromContent(`---name: code-reviewdescription: Review code for bugs and best practices---
When reviewing code, check for:1. Logic errors and edge cases2. Security vulnerabilities3. Performance issues4. Readability and naming`, '/skills/code-review/SKILL.md');
// Register and use with an agentconst registry = SkillRegistry.from([reviewSkill]);
const agent = new AgentBuilder() .withModel('sonnet') .withSkills(registry) .ask('Review this function: function add(a, b) { return a + b; }');
for await (const event of agent.run()) { if (event.type === 'text') process.stdout.write(event.content);}SKILL.md Format
Section titled “SKILL.md Format”Each skill is a directory containing a SKILL.md file with YAML frontmatter and a markdown body:
my-skill/ SKILL.md # Required: metadata + instructions scripts/ # Optional: executable helpers references/ # Optional: documentation loaded on demand assets/ # Optional: templates, resourcesFrontmatter Fields
Section titled “Frontmatter Fields”---name: my-skill # Required. Lowercase, hyphens, max 64 chars.description: What it does # Required. Max 1024 chars. Used for auto-triggering.argument-hint: "[filename]" # Shown during autocompleteallowed-tools: # Restrict which gadgets the agent can use - Bash - ReadFilemodel: sonnet # Override model when skill is activecontext: fork # "fork" runs in isolated subagentagent: Explore # Subagent type for fork modepaths: # Auto-activate on file pattern match - "src/**/*.ts"disable-model-invocation: false # true = only user can invokeuser-invocable: true # false = background knowledge onlyshell: bash # Shell for !`command` preprocessingversion: 1.0.0 # Semantic version---Dynamic Substitution
Section titled “Dynamic Substitution”Skill instructions support dynamic content:
---name: searchdescription: Search for filesargument-hint: "<pattern>"---
Search for: $ARGUMENTSLook in directory: ${SKILL_DIR}First arg: $0, second arg: $1
Current branch: !`git branch --show-current`$ARGUMENTS- Full argument string$0,$1,$ARGUMENTS[N]- Positional arguments${SKILL_DIR},${CLAUDE_SKILL_DIR}- Skill directory path!`command`- Shell command (executed before LLM sees instructions)
Three-Tier Progressive Disclosure
Section titled “Three-Tier Progressive Disclosure”Skills manage context window budget through lazy loading:
| Tier | Content | When Loaded | Budget |
|---|---|---|---|
| 1 | Name + description | Always (all skills) | ~100 tokens each |
| 2 | Full SKILL.md body | On activation | <5K tokens |
| 3 | Scripts, references, assets | On demand | Unlimited |
const skill = registry.get('my-skill');
// Tier 1: always availableconsole.log(skill.name, skill.description);
// Tier 2: lazy loaded from diskconst instructions = await skill.getInstructions();
// Tier 3: loaded individuallyconst resources = skill.getResources();const doc = await skill.getResource('references/api.md');Skill Registry
Section titled “Skill Registry”import { SkillRegistry, Skill } from 'llmist';
const registry = new SkillRegistry();registry.register(skill);registry.registerMany([skill1, skill2]);
// Lookupconst s = registry.get('my-skill'); // Case-insensitiveregistry.has('my-skill'); // Boolean checkregistry.getAll(); // All skillsregistry.getNames(); // All names
// Filteringregistry.getModelInvocable(); // Skills the LLM can auto-triggerregistry.getUserInvocable(); // Skills the user can invoke via /nameregistry.findByFilePath('src/App.tsx'); // Match by path patterns
// Metadata summaries for system promptconst summaries = registry.getMetadataSummaries(8000); // char budget
// Compose registriesregistry.merge(otherRegistry);Skill Discovery
Section titled “Skill Discovery”Skills are discovered from standard locations:
import { discoverSkills, loadSkillsFromDirectory } from 'llmist';
// Standard locations (user + project)const registry = discoverSkills({ projectDir: process.cwd(), // Scans .llmist/skills/ // Also scans ~/.llmist/skills/ automatically additionalDirs: ['./extra-skills'],});
// Or load from a specific directoryconst skills = loadSkillsFromDirectory('/path/to/skills', { type: 'directory', path: '/path/to/skills',});Discovery order (later overrides earlier on name collision):
~/.llmist/skills/- User-level.llmist/skills/- Project-level- Additional directories - Explicit
Agent Builder Integration
Section titled “Agent Builder Integration”const builder = new AgentBuilder() .withModel('sonnet') .withSkills(registry) // Register all skills .withSkill('code-review', 'PR #42') // Pre-activate a specific skill .withSkillsFrom('./my-skills') // Load from directory .ask('Help me with this task');When skills are registered, a LoadSkill meta-gadget is automatically added. The LLM can invoke it like any gadget to activate a skill mid-conversation.
How It Works
Section titled “How It Works”┌─────────────────────────────────────────────────┐│ Skill Layer ││ skill.activate() → injects instructions ││ → registers bundled gadgets │└──────────────┬──────────────────────────────────┘ │ composes with┌──────────────▼──────────────────────────────────┐│ Gadget Layer ││ gadget.execute() → runs code, returns result │└─────────────────────────────────────────────────┘- Skill metadata (Tier 1) is included in the LoadSkill gadget description
- LLM decides to invoke LoadSkill based on the task
- Skill instructions (Tier 2) are loaded and returned as the gadget result
- LLM follows the instructions in the next iteration
Skills integrate with the hooks system:
const agent = new AgentBuilder() .withSkills(registry) .withHooks({ observers: { onSkillActivated: (ctx) => { console.log(`Skill ${ctx.skillName} activated`); }, }, controllers: { beforeSkillActivation: async (ctx) => { if (ctx.skillName === 'dangerous-skill') { return { action: 'skip', reason: 'Not allowed' }; } return { action: 'proceed' }; }, }, interceptors: { interceptSkillInstructions: (instructions, ctx) => { return instructions + '\n\nAlways be concise.'; }, }, }) .ask('Do the thing');Testing Skills
Section titled “Testing Skills”import { mockSkill, MockSkillBuilder, testSkillActivation } from '@llmist/testing';
// Quick mockconst skill = mockSkill({ name: 'test' }, 'Test instructions with $ARGUMENTS.');
// Fluent builderconst skill = new MockSkillBuilder() .withName('deploy') .withDescription('Deploy to production') .withInstructions('Deploy $ARGUMENTS now.') .withModel('flash') .build();
// Test activationconst activation = await testSkillActivation(skill, { arguments: 'v2.0' });expect(activation.resolvedInstructions).toContain('Deploy v2.0 now.');CLI Usage
Section titled “CLI Usage”# List available skillsllmist skill list
# Show skill detailsllmist skill info gmail-read
# Invoke a skill in the REPL# Type /skill-name [args] at the prompt/code-review src/app.tsConfigure skills in ~/.llmist/cli.toml:
[skills]sources = [ "~/custom-skills", "./project-skills",]
[skills.overrides.my-skill]model = "flash"enabled = trueEcosystem Compatibility
Section titled “Ecosystem Compatibility”Skills using the standard SKILL.md format work across:
- Claude Code (
.claude/skills/) - Codex CLI (
.agents/skills/) - Gemini CLI (extensions)
- Cursor, VS Code Copilot, JetBrains Junie, and 30+ other tools
llmist uses .llmist/skills/ for its standard location but can load skills from any directory.