The open-source skills engine that gives any LLM agent a Claude Code-like experience.
Define skills as Markdown. Load them into any model. Ship agent capabilities without vendor lock-in.
Every AI agent platform is building its own skills/plugins system — Claude Code has skills, Cursor has rules, ChatGPT has GPTs, Codex has tasks. They're all incompatible, proprietary, and locked to one provider.
SkillEngine extracts the best patterns from Claude Code's skill system into a standalone, framework-agnostic engine that works with any LLM. Write once, run on OpenAI, Anthropic, MiniMax, or your local model.
Your Skills (Markdown + YAML)
│
▼
┌──────────┐
│ SkillEngine │ ← Framework-agnostic engine
└────┬─────┘
│
┌────┴────────────────────────┐
│ │ │ │
OpenAI Anthropic MiniMax Local
- Markdown-based skills — Define agent capabilities as simple
SKILL.mdfiles with YAML frontmatter - On-demand loading — Only skill names/descriptions in system prompt; full content loaded when the LLM needs it
- Slash commands —
/pdf,/pptx, etc. — user-invocable skills, just like Claude Code - Per-skill model & tools — Each skill can specify its own model and allowed tools
- Context fork — Run skills in isolated subagent contexts
- Dynamic injection —
$ARGUMENTS,$1..$Nsubstitution +!`command`shell expansion - Eligibility filtering — Auto-filter skills by OS, binaries, env vars, and config
- Hot-reload — File watcher reloads skills on save, no restart needed
- Multi-source — Load from bundled, managed, workspace, and plugin directories
- Validation — Enforces naming rules, description limits, and metadata schemas
# Core engine only (recommended minimal install)
uv add skillengine
# Add provider adapters
uv add -e ".[openai]"
uv add -e ".[anthropic]"
# Add bundled skill dependency packs
uv add -e ".[skills-pdf]"
uv add -e ".[skills-pptx]"
uv add -e ".[skills-media]"
# or everything at once
uv add -e ".[skills-all]"
# pip equivalents
pip install "skillengine[openai,skills-all]"Create skills/my-skill/SKILL.md:
---
name: my-skill
description: "Summarize any document into bullet points"
user-invocable: true
allowed-tools:
- Read
- Write
---
# Document Summarizer
Read the file at $ARGUMENTS and produce a concise bullet-point summary.
Focus on key decisions, action items, and open questions.import asyncio
from pathlib import Path
from skillengine import create_agent
async def main():
agent = await create_agent(
skill_dirs=[Path("./skills")],
system_prompt="You are a helpful assistant.",
watch_skills=True,
)
# Natural language — LLM loads skills on demand
response = await agent.chat("Help me create a PDF report")
print(response.content)
# Slash command — direct skill invocation
response = await agent.chat("/pdf extract text from invoice.pdf")
print(response.content)
asyncio.run(main())uv run python examples/agent_demo.py --interactiveCommands: /skills (list all), /pdf, /pptx (invoke skill), /clear, /quit
System Prompt (lightweight) On-Demand Loading (full content)
┌─────────────────────────┐ ┌──────────────────────────────┐
│ Available skills: │ LLM calls │ SKILL.md full content │
│ - pdf: PDF extraction │ ──────────► │ + $ARGUMENTS substituted │
│ - pptx: Slide creation │ skill() │ + !`commands` expanded │
│ - my-skill: Summarizer │ tool │ + env vars injected │
└─────────────────────────┘ └──────────────────────────────┘
Only names and descriptions live in the system prompt (configurable budget: default 16K chars). The LLM calls skill(name="pdf", arguments="report.pdf") to load full content when needed — progressive disclosure that keeps context lean.
---
name: skill-name # ≤64 chars, lowercase + digits + hyphens
description: "One-line summary" # ≤1024 chars, shown to LLM
model: claude-sonnet-4-5-20250514 # Per-skill model override
context: fork # Run in isolated subagent
argument-hint: "<query>" # Autocomplete hint for slash commands
user-invocable: true # Enable /skill-name slash command
allowed-tools: # Restrict available tools
- Read
- Grep
- Bash
hooks: # Lifecycle hooks
PreToolExecution: "echo pre"
PostToolExecution: "echo post"
metadata:
emoji: "🔧"
requires:
bins: [git, gh] # ALL must exist
any_bins: [npm, pnpm] # At least ONE
env: [GITHUB_TOKEN] # Required env vars
os: [darwin, linux] # Supported platforms
primary_env: "API_KEY" # Auto-inject for this skill
---| Placeholder | Description |
|---|---|
$ARGUMENTS |
Full arguments string |
$1, $2, ... $N |
Positional arguments |
${CLAUDE_SESSION_ID} |
Current session ID |
!`command` |
Replaced with stdout before sending to LLM |
| Skill | Description | Deps |
|---|---|---|
| PDF extraction, merging, splitting, form filling | pypdf, pdfplumber, reportlab | |
| pptx | PowerPoint creation and editing | python-pptx, markitdown |
| algorithmic-art | Generative art with p5.js | p5.js, HTML/JS |
| slack-gif-creator | Animated GIF creation | PIL/Pillow |
| web-artifacts-builder | React + Tailwind + shadcn/ui apps | Node.js, Vite |
┌─────────────────────────────────────────────────┐
│ AgentRunner │
│ - System prompt with skill discovery │
│ - On-demand skill loading via tool call │
│ - Slash commands + argument substitution │
│ - Per-skill model switching + tool restriction │
│ - Context fork for isolated execution │
├─────────────────────────────────────────────────┤
│ SkillsEngine │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Loader │ │ Filter │ │ Runtime │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ SkillSnapshot │ │
│ │ - Discovered skills + metadata │ │
│ │ - System prompt fragment │ │
│ └─────────────────────────────────────┘ │
├─────────────────────────────────────────────────┤
│ Event System (P0 roadmap) │
│ before_tool_call · after_tool_result │
│ context_transform · turn_start · turn_end │
└─────────────────────────────────────────────────┘
│ │ │
OpenAI Anthropic MiniMax / Local
from skillengine.loaders import SkillLoader
class YAMLSkillLoader(SkillLoader):
def can_load(self, path: Path) -> bool:
return path.suffix == ".yaml"
def load_skill(self, path: Path, source: SkillSource) -> SkillEntry:
...from skillengine.filters import SkillFilter
class TeamSkillFilter(SkillFilter):
def filter(self, skill, config, context) -> FilterResult:
if "team-only" in skill.metadata.tags:
if not self.is_team_member():
return FilterResult(skill, False, "Team members only")
return FilterResult(skill, True)from skillengine.runtime import SkillRuntime
class DockerRuntime(SkillRuntime):
async def execute(self, command, cwd, env, timeout):
# Execute in Docker container
...The full docs live under docs/ in three layers:
The original P0–P3 milestones (event system, structured streaming, model registry, context pipeline, tool streaming, steering/abort, dynamic adapter registry) have all shipped. The next four releases close the remaining gaps versus mainstream agent SDKs. See ROADMAP.md for the full plan.
| Release | Theme | Headline items |
|---|---|---|
| v0.3 Interop | Show up in the wider ecosystem | MCP client + server, mcp:// package source, typed/structured output, A2A handoffs compat, 3-layer docs |
| v0.4 Production | Observability, validation, regression | Guardrails as first-class abstraction, end-to-end tracing (OTel/LangSmith/Logfire), eval harness + scorers, summarizing compaction, cost dashboard, mypy strict cleanup |
| v0.5 Capability | Workflows, browser, marketplace | DAG/durable workflows, Playwright + computer-use tools, signed skill marketplace, secret management, scheduler upgrade |
| v1.0 Stability | API freeze + LTS | Public-surface freeze, SemVer commitment, benchmarks regression gates, auto-updated model catalog, Web UI parity with TUI |
git clone https://github.com/sawzhang/skillengine.git
cd skillengine
uv sync
# Run tests
pytest
# Linting
ruff check src/
ruff format src/
mypy src/MIT