A standalone skills execution engine for LLM agents
Project description
SkillEngine
A standalone, framework-agnostic skills execution engine for LLM agents. Provides a Claude Code-like experience with automatic skill discovery, loading, and execution.
Features
- Claude Code-like Experience:
AgentRunnerprovides auto-loading, slash commands, and tool execution - On-Demand Skill Loading: LLM calls the
skilltool to load full skill content only when needed (progressive disclosure) - Framework Agnostic: Works with any LLM provider (OpenAI, Anthropic, MiniMaxi, local models)
- Markdown-based Skills: Define skills as simple Markdown files with YAML frontmatter
- $ARGUMENTS Substitution: Support
$ARGUMENTS,$1.$N,${CLAUDE_SESSION_ID}in skill content - Per-Skill Model & Tools: Each skill can specify its own
modelandallowed-tools - Context Fork: Run skills in isolated subagent contexts with
context: fork - Dynamic Content Injection:
!command`` syntax executes shell commands before skill content is sent to LLM - Description Budget: Configurable char budget for skill descriptions in system prompt (default 16K)
- Skill Validation: Enforces naming rules (โค64 chars, lowercase+digits+hyphens) and description limits (โค1024 chars)
- User-invocable Skills: Slash commands like
/pdf,/pptxfor direct skill invocation - Eligibility Filtering: Automatic filtering based on OS, binaries, env vars, and config
- Environment Injection: Securely inject API keys and env vars for skill execution
- File Watching: Hot-reload skills when files change
- Multiple Sources: Load skills from bundled, managed, workspace, and plugin directories
Installation
# With uv (recommended)
uv add skillengine
# Basic installation
pip install skillengine
# With all dependencies
pip install skillengine[openai]
Quick Start
1. Create .env file
# For MiniMaxi API (OpenAI-compatible)
OPENAI_BASE_URL=https://api.minimaxi.com/v1
OPENAI_API_KEY=your-api-key
MINIMAX_MODEL=MiniMax-M2.1
# Or for OpenAI
OPENAI_API_KEY=your-openai-key
2. Use AgentRunner (Recommended)
import asyncio
from pathlib import Path
from skillengine import create_agent
async def main():
# Create agent with automatic skill loading
agent = await create_agent(
skill_dirs=[Path("./skills")],
system_prompt="You are a helpful assistant.",
watch_skills=True, # Hot-reload on file changes
)
# Chat with automatic tool execution
response = await agent.chat("Help me create a PDF report")
print(response.content)
# Use slash commands
response = await agent.chat("/pdf extract text from invoice.pdf")
print(response.content)
asyncio.run(main())
3. Run Interactive Mode
# Run the demo
uv run python examples/agent_demo.py --interactive
Commands in interactive mode:
/skills- List all available skills/pdf,/pptx, etc. - Invoke specific skills/clear- Clear conversation history/quit- Exit
Example Skills
The examples/skills/ directory contains ready-to-use skills:
| Skill | Description | Tools |
|---|---|---|
| PDF text 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 for Slack | PIL/Pillow |
| web-artifacts-builder | React + Tailwind + shadcn/ui apps | Node.js, Vite, pnpm |
Testing Skills
# Run all skill tests
uv run python examples/test_skills.py
# Test individual skills interactively
uv run python examples/agent_demo.py --interactive
Skill Definition Format
Create skills/my-skill/SKILL.md:
---
name: my-skill
description: "A helpful skill for doing things"
metadata:
emoji: "๐ง"
requires:
bins: ["some-cli"]
env: ["API_KEY"]
primary_env: "API_KEY"
user-invocable: true
---
# My Skill
Instructions for the LLM on how to use this skill...
Process: $ARGUMENTS
Current git branch: !`git branch --show-current`
Skill Metadata Options
---
name: skill-name # Unique identifier (โค64 chars, lowercase+digits+hyphens)
description: "Brief desc" # One-line description for LLM (โค1024 chars)
# Claude Agent Skills extensions
model: claude-sonnet-4-5-20250514 # Per-skill model override
context: fork # "fork" to run in isolated subagent
argument-hint: "<query>" # Autocomplete hint for slash commands
allowed-tools: # Restrict tools available during skill execution
- Read
- Grep
- Glob
hooks: # Per-skill lifecycle hooks
PreToolExecution: "echo pre"
PostToolExecution: "echo post"
metadata:
emoji: "๐ง" # Visual indicator
homepage: "https://..." # Project URL
always: false # Always include (override eligibility)
requires:
bins: # Required binaries (ALL must exist)
- git
- gh
any_bins: # At least ONE must exist
- npm
- pnpm
env: # Required environment variables
- GITHUB_TOKEN
os: # Supported platforms
- darwin
- linux
primary_env: "API_KEY" # Primary env var for API key injection
user-invocable: true # Can user invoke via /skill-name
disable-model-invocation: false # Hide from LLM system prompt
---
Variable Substitution
Skill content supports dynamic placeholders:
| Placeholder | Description |
|---|---|
$ARGUMENTS |
Full arguments string passed to the skill |
$1, $2, ... $N |
Individual positional arguments (whitespace-split) |
${CLAUDE_SESSION_ID} |
Current session ID |
!`command` |
Replaced with command's stdout before sending to LLM |
API Reference
AgentRunner
from skillengine import AgentRunner, AgentConfig, create_agent
# Quick creation
agent = await create_agent(
skill_dirs=[Path("./skills")],
system_prompt="You are helpful.",
watch_skills=True,
)
# Or with full config
config = AgentConfig(
model="MiniMax-M2.1",
base_url="https://api.minimaxi.com/v1",
api_key="...",
max_turns=20,
enable_tools=True,
skill_description_budget=16000, # Max chars for skill descriptions in system prompt
)
agent = AgentRunner(engine, config)
# Methods
response = await agent.chat("Hello") # Single message
response = await agent.chat("/pdf help") # Slash command
async for chunk in agent.chat_stream("Hi"): # Streaming
print(chunk, end="")
await agent.run_interactive() # Interactive mode
# Skill validation
errors = AgentRunner.validate_skill(skill)
if errors:
print(f"Invalid skill: {errors}")
Skill Tool (On-Demand Loading)
The LLM automatically gets a skill tool that loads full skill content on demand:
Tools available to LLM:
- execute # Run shell commands
- execute_script # Run multi-line scripts
- skill # Load skill content on demand (name, arguments)
- <skill>:<action> # Deterministic skill actions
Only skill names and descriptions are in the system prompt. The LLM calls skill(name="pdf", arguments="report.pdf") to load the full SKILL.md content when needed.
SkillsEngine (Low-level)
from skillengine import SkillsEngine, SkillsConfig
engine = SkillsEngine(
config=SkillsConfig(
skill_dirs=[Path("./skills")],
watch=True,
)
)
# Load and filter skills
snapshot = engine.get_snapshot()
print(f"Loaded {len(snapshot.skills)} skills")
print(snapshot.prompt) # For LLM system prompt
# Execute commands
result = await engine.execute("echo 'Hello'")
print(result.output)
# With environment injection
with engine.env_context():
result = await engine.execute("gh pr list")
Configuration
Environment Variables
# LLM API
OPENAI_BASE_URL=https://api.minimaxi.com/v1
OPENAI_API_KEY=your-key
MINIMAX_MODEL=MiniMax-M2.1
# Or standard OpenAI
OPENAI_API_KEY=your-openai-key
YAML Config
skill_dirs:
- ./skills
- ~/.agent/skills
watch: true
watch_debounce_ms: 250
entries:
github:
enabled: true
api_key: "ghp_..."
env:
GITHUB_ORG: "my-org"
prompt_format: xml # xml, markdown, or json
default_timeout_seconds: 30
Architecture
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ AgentRunner โ
โ - System prompt: skill names + descriptions โ
โ - Skill tool: on-demand full content loading โ
โ - $ARGUMENTS substitution + !`cmd` injection โ
โ - context: fork โ isolated child agent โ
โ - Slash commands (/pdf, /pptx) โ
โ - Per-skill model switching + tool restriction โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ SkillsEngine โ
โ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ โ
โ โ Loader โ โ Filter โ โ Runtime โ โ
โ โโโโโโฌโโโโโ โโโโโโฌโโโโโ โโโโโโฌโโโโโ โ
โ โ โ โ โ
โ v v v โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ SkillSnapshot โ โ
โ โ - skills: List[Skill] โ โ
โ โ - prompt: str (metadata only) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
v
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ LLM Providers โ
โ OpenAI โ MiniMaxi โ Anthropic โ Custom โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Extending
Custom Loader
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:
# Custom loading logic
...
Custom Filter
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)
Custom Runtime
from skillengine.runtime import SkillRuntime
class DockerRuntime(SkillRuntime):
async def execute(self, command, cwd, env, timeout):
# Execute in Docker container
...
Development
# Clone and install
git clone https://github.com/sawzhang/skillengine.git
cd skillengine
uv sync
# Run tests
pytest
# Run skill tests
uv run python examples/test_skills.py
# Linting
ruff check src/
ruff format src/
mypy src/
License
MIT License - see LICENSE for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file skillengine-0.1.0.tar.gz.
File metadata
- Download URL: skillengine-0.1.0.tar.gz
- Upload date:
- Size: 14.7 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5159697c302ee3305a288d6c4c569c74c05ac6caf714aab58ce54f7b697d7db9
|
|
| MD5 |
9b5ab3e1336373b1cc5d6ae170e60a5a
|
|
| BLAKE2b-256 |
60af8a32a39b02048ad1337f100eb8f8ff468376c28be5fddc547db132b63f64
|
File details
Details for the file skillengine-0.1.0-py3-none-any.whl.
File metadata
- Download URL: skillengine-0.1.0-py3-none-any.whl
- Upload date:
- Size: 190.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6b23da5d6c2c5e2b09fb1c42a5eddaf275df6d25bc6f826cc0f8201e035738c5
|
|
| MD5 |
c44ea525bf9206360d1cdf57914a29cc
|
|
| BLAKE2b-256 |
5841ee9a14117b42b25ba919aebd387cf6e842eed3d1b1e00ce61ef1f712dbeb
|