A standalone skills execution engine for LLM agents
Project description
SkillEngine
A framework-agnostic skills execution engine for LLM agents
Build Claude Code-like experiences with automatic skill discovery, on-demand loading, tool execution, and multi-provider support.
pip install skillengine
Highlights
- Progressive Disclosure — Only skill names + descriptions in the system prompt; full content loaded on-demand via the
skilltool - Multi-Provider — OpenAI, Anthropic, Google, DeepSeek, MiniMax + any OpenAI-compatible endpoint
- Plugin Everything — Loaders, filters, runtimes, adapters, extensions — all swappable
- Production Ready — Sessions, context compaction, cost tracking, prompt caching, sandbox isolation, event hooks, hot-reload
Quick Start
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.",
)
response = await agent.chat("Help me create a PDF report")
print(response.content)
# Slash commands invoke skills directly
response = await agent.chat("/pdf extract text from invoice.pdf")
print(response.content)
asyncio.run(main())
Installation
pip install skillengine # Core
pip install skillengine[openai] # + OpenAI adapter
pip install skillengine[anthropic] # + Anthropic adapter
pip install skillengine[openai,anthropic,websockets,memory,web,sandbox] # Everything
Architecture
┌─────────────────────────────┐
│ AgentRunner │
│ on-demand skills · slash │
│ commands · fork isolation │
│ per-skill model & tools │
└──────────┬──────────────────┘
│
┌──────────▼──────────────────┐
│ SkillsEngine │
│ │
│ Loader → Filter → Runtime │
│ ↓ │
│ SkillSnapshot │
└──────────┬──────────────────┘
│
┌────────┬───────┬───────┼───────┬──────────┐
│ │ │ │ │ │
OpenAI Anthropic Google DeepSeek MiniMax Custom
| Layer | Base Class | Built-in | Purpose |
|---|---|---|---|
| Loader | SkillLoader |
MarkdownSkillLoader |
Parse SKILL.md (Markdown + YAML frontmatter) |
| Filter | SkillFilter |
DefaultSkillFilter |
Eligibility: bins, env vars, OS, config |
| Runtime | SkillRuntime |
BashRuntime CodeModeRuntime BoxLiteRuntime |
Execute with timeout, streaming, sandboxing |
| Adapter | LLMAdapter |
OpenAIAdapter AnthropicAdapter |
Provider integration with tool calling |
Defining Skills
Create skills/my-skill/SKILL.md:
---
name: my-skill
description: "Does useful things"
model: claude-sonnet-4-20250514 # Per-skill model override
context: fork # Isolated subagent
allowed-tools: [Read, Grep, Glob] # Tool restrictions
user-invocable: true # Enable /my-skill slash command
argument-hint: "<query>"
metadata:
emoji: "🔧"
primary_env: "API_KEY"
requires:
bins: ["git"] # ALL must exist
any_bins: ["npm", "pnpm"] # At least ONE
env: ["GITHUB_TOKEN"]
os: ["darwin", "linux"]
---
# My Skill
Instructions for the LLM when this skill is loaded.
Process: $ARGUMENTS
Current branch: !`git branch --show-current`
| Placeholder | Replaced with |
|---|---|
$ARGUMENTS |
Full argument string |
$1 $2 ... $N |
Positional arguments |
${CLAUDE_SESSION_ID} |
Session ID |
!`cmd` |
Command stdout (before sending to LLM) |
Core API
AgentRunner (high-level)
from skillengine import AgentRunner, AgentConfig, create_agent
config = AgentConfig(
model="gpt-4o",
api_key="...",
max_turns=20,
thinking_level="medium", # off / minimal / low / medium / high / xhigh
skill_description_budget=16000, # Max chars for skill metadata in system prompt
)
agent = AgentRunner(engine, config)
response = await agent.chat("Hello") # Single turn
async for chunk in agent.chat_stream("Explain this"): # Streaming
print(chunk, end="")
await agent.run_interactive() # REPL
SkillsEngine (low-level)
from skillengine import SkillsEngine, SkillsConfig
engine = SkillsEngine(config=SkillsConfig(
skill_dirs=[Path("./skills")],
watch=True,
prompt_format="xml", # xml / markdown / json
))
snapshot = engine.get_snapshot()
print(snapshot.prompt) # Inject into your system prompt
result = await engine.execute("echo hello") # Execute a command
Key Features
Model Registry & Cost Tracking
Built-in catalog for 12+ models across 5 providers with pricing, context windows, and capabilities.
from skillengine import ModelRegistry, TokenUsage
registry = ModelRegistry()
model = registry.get("gpt-4o")
# model.context_window → 128000
# model.cost.input → 2.5 ($/M tokens)
cost = registry.calculate_cost("gpt-4o", TokenUsage(input_tokens=1000, output_tokens=500))
Extended Thinking
Maps thinking budgets across providers transparently.
AgentConfig(model="claude-opus-4-20250514", thinking_level="high")
| Level | Anthropic budget | OpenAI effort |
|---|---|---|
| minimal | 1,024 | low |
| low | 2,048 | low |
| medium | 4,096 | medium |
| high | 8,192 | high |
| xhigh | 16,384 | high |
Events
13 lifecycle events for observability and control flow.
from skillengine import EventBus, BEFORE_TOOL_CALL
bus = EventBus()
@bus.on(BEFORE_TOOL_CALL)
async def guard(event):
if "rm -rf" in event.arguments.get("command", ""):
return ToolCallEventResult(block=True, message="Blocked")
Events: AGENT_START AGENT_END TURN_START TURN_END BEFORE_TOOL_CALL AFTER_TOOL_RESULT INPUT CONTEXT_TRANSFORM TOOL_EXECUTION_UPDATE SESSION_START SESSION_END MODEL_CHANGE COMPACTION
Context Compaction
from skillengine import TokenBudgetCompactor, SlidingWindowCompactor
compactor = TokenBudgetCompactor(max_tokens=100_000) # Token budget
compactor = SlidingWindowCompactor(window_size=50) # Keep N recent messages
Sessions
JSONL append-only tree with branching.
from skillengine.session import SessionManager
mgr = SessionManager(store_dir=Path("./sessions"))
session = mgr.create_session()
branch = mgr.branch(session.id, entry_index=5) # Fork from earlier point
CodeModeRuntime
Cloudflare code-mode-mcp pattern: 2 tools (search + execute) instead of N. Token cost O(1).
from skillengine import CodeModeRuntime
runtime = CodeModeRuntime(spec=openapi_spec, ctx={"client": httpx.Client()})
await runtime.search("[p for p in spec['paths'] if '/users' in p]")
await runtime.run("result = ctx['client'].get('/users')")
Sandbox Execution
VM-level isolation via BoxLite micro-VMs.
from skillengine import BoxLiteRuntime, SecurityLevel, SandboxedAgentRunner
runtime = BoxLiteRuntime(security_level=SecurityLevel.STANDARD)
agent = SandboxedAgentRunner(engine, config) # Full agent in sandbox
Built-in Tools
| Tool | Purpose | Tool | Purpose |
|---|---|---|---|
bash |
Shell commands | read |
Read files |
write |
Write files | edit |
Text replacement |
grep |
Pattern search | find |
File discovery |
ls |
Directory listing | skill |
On-demand skill loading |
Extensions
# ~/.skillengine/extensions/my_ext.py
def extension(api):
api.on("turn_end", my_hook)
api.register_command("/my-cmd", handler, description="...")
api.register_tool("my_tool", schema, handler)
api.register_adapter("my-provider", MyAdapter)
Execution Modes
| Mode | Protocol | Use case |
|---|---|---|
| Interactive | TUI (stdin/stdout) | Terminal REPL |
| JSON | JSONL to stdout | Pipelines |
| RPC | JSON-line stdin/stdout | Programmatic control |
CLI
skills list -d ./skills # List skills
skills show pdf -d ./skills # Show detail
skills prompt -d ./skills -f xml # Generate prompt
skills exec "echo hello" # Run command
skills validate -d ./skills # Validate
skills watch -d ./skills # Watch + hot-reload
Extending
Every layer is pluggable. Implement the base class and pass to the engine:
from skillengine.loaders import SkillLoader
from skillengine.filters import SkillFilter
from skillengine.runtime import SkillRuntime
class MyLoader(SkillLoader): # Custom file format
def can_load(self, path): ...
def load_skill(self, path, source): ...
class MyFilter(SkillFilter): # Custom eligibility
def filter(self, skill, config, context): ...
class MyRuntime(SkillRuntime): # Custom execution
async def execute(self, command, cwd, env, timeout): ...
Development
git clone https://github.com/sawzhang/skillengine.git
cd skillengine
uv sync
uv run pytest # 1083 tests
uv run ruff check src/ # Lint
uv run ruff format --check src/ # Format check
uv run mypy src/ # Type check
CI runs on every push and PR: lint + format + mypy on Python 3.12, tests on 3.10/3.11/3.12.
License
MIT
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 Distributions
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.2.1-py3-none-any.whl.
File metadata
- Download URL: skillengine-0.2.1-py3-none-any.whl
- Upload date:
- Size: 218.3 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 |
dd5d79e725628a1a16eadade35bfa455cfbd395203c972db46acee3629abb8b2
|
|
| MD5 |
9565ed34f7eb7be36e07821dfcbbba3b
|
|
| BLAKE2b-256 |
c590d76c221cdd664a6c28f97d0d29b1209d897afb5cf0cc2553fb4772960c14
|