Skip to main content

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: AgentRunner provides auto-loading, slash commands, and tool execution
  • On-Demand Skill Loading: LLM calls the skill tool 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 model and allowed-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, /pptx for 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 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

skillengine-0.1.0.tar.gz (14.7 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

skillengine-0.1.0-py3-none-any.whl (190.0 kB view details)

Uploaded Python 3

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

Hashes for skillengine-0.1.0.tar.gz
Algorithm Hash digest
SHA256 5159697c302ee3305a288d6c4c569c74c05ac6caf714aab58ce54f7b697d7db9
MD5 9b5ab3e1336373b1cc5d6ae170e60a5a
BLAKE2b-256 60af8a32a39b02048ad1337f100eb8f8ff468376c28be5fddc547db132b63f64

See more details on using hashes here.

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

Hashes for skillengine-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6b23da5d6c2c5e2b09fb1c42a5eddaf275df6d25bc6f826cc0f8201e035738c5
MD5 c44ea525bf9206360d1cdf57914a29cc
BLAKE2b-256 5841ee9a14117b42b25ba919aebd387cf6e842eed3d1b1e00ce61ef1f712dbeb

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page