Skip to main content

Filesystem-based agent skill loader: spec + reference implementation.

Project description

outskilled

Filesystem-based agent skill loader. One spec, one reference implementation, with a first-party adapter for pydantic-ai.

A skill is a folder containing a SKILL.md file — YAML frontmatter describing what the skill does plus a markdown body explaining how to do it. outskilled discovers skills (arbitrary nesting supported), validates them against the spec, and renders a system-prompt-ready manifest. The pydantic-ai adapter wires the manifest into an Agent's instructions and registers a load_skill tool the model uses to fetch a skill's body on demand (plus opt-in tools for listing skills and reading skill resources). Two lines of glue and the model can route to the right skill.

References

Spec & ecosystem:

Pydantic-AI:

Status

v0.2 — spec stabilising. Adds optional when_to_use and always_load frontmatter fields and a pydantic-ai adapter alongside the framework-agnostic registry. v0.1 skills continue to validate unchanged.

Quickstart

from pathlib import Path
from outskilled import SkillRegistry

# Either pass roots directly...
reg = SkillRegistry([Path("./skills")])

# ...or load them from a config file that lives with your skills:
reg = SkillRegistry.from_config("./skills/skills.yaml")

# Level 1: render the manifest for the system prompt.
print(reg.manifest_xml())

# Level 2: load a skill's body on demand. (The `search` skill ships
# with the example bundle under examples/agent_demo/skills/.)
body = reg.load("search")

SkillRegistry walks every directory under its roots, treats any directory containing SKILL.md as a skill, and validates against the spec. Reserved subdirectories inside a skill (references/, scripts/, assets/) are not descended into. Arbitrary nesting is supported — the path from a skill root to a skill's parent directory is the category path. The same loader handles flat layouts and N-level categories without a nested=True flag.

skills.yaml

A small declarative config (lives inside your skills directory, so the bundle is portable):

# skills/skills.yaml
roots:
  - .                  # paths are resolved relative to this file
  # - ../shared-skills # optional extra roots

SkillRegistry.from_config(path) resolves the listed paths relative to the config file's directory.

Pydantic-AI integration

Install the extra:

pip install "outskilled[pydantic-ai]"

Attach a registry to an existing Agent:

from pydantic_ai import Agent
from outskilled.pydanticai import attach_skills

agent = Agent("anthropic:claude-sonnet-4-6")
attach_skills(agent, "skills/skills.yaml")

Or build an Agent with skills already wired up:

from outskilled.pydanticai import skill_aware_agent

agent = skill_aware_agent(
    "anthropic:claude-sonnet-4-6",
    skills="skills/skills.yaml",
)

What attach_skills adds (each independently togglable):

  • Instructions (Level 1, on by default): an @agent.instructions function that returns the rendered <available_skills> manifest, sorted for deterministic prompt caching.
  • load_skill(name) tool (Level 2, on by default): the model calls this to fetch a skill body on demand. Path-traversal-safe.
  • Inlined always-loaded bodies (on by default): any skill with always_load: true in its frontmatter has its body inlined into the instructions. Independent of the manifest flag — bodies are inlined even when the manifest is suppressed.
  • list_skills tool (opt-in): re-renders the manifest at runtime.
  • read_skill_resource tool (Level 3, opt-in): exposes files under a skill's references/, scripts/, assets/ with path safety.

attach_skills is not idempotent — calling it twice on the same Agent raises SkillError. Build a new Agent if you need a different skill set.

Try it

A self-contained demo lives under examples/agent_demo/ — four skills across three nesting depths, an offline plumbing check, and a real routing eval against Claude Sonnet 4.6.

pip install -e ".[pydantic-ai]"

# Plumbing check (no API key). Verifies the manifest + load_skill
# round-trip, NOT whether an LLM routes correctly.
python examples/agent_demo/run_demo.py

# Real routing eval against an actual model.
export ANTHROPIC_API_KEY=...
python examples/agent_demo/run_live.py

See examples/agent_demo/README.md for the two-script split and what each one does / doesn't prove.

Install

pip install -e .

Python 3.12+. One runtime dep: PyYAML.

Spec

See SPEC.md for the canonical rules: directory layout, frontmatter schema, validation, manifest format, progressive disclosure.

Layout

outskilled/
├── SPEC.md
├── README.md
├── LICENSE
├── pyproject.toml
├── src/outskilled/
│   ├── __init__.py     # public API
│   ├── errors.py       # typed exceptions
│   ├── models.py       # Skill dataclass
│   ├── parser.py       # frontmatter extraction
│   ├── validator.py    # spec §3 rules
│   ├── manifest.py     # XML + markdown renderers
│   ├── registry.py     # discovery + composition
│   └── pydanticai.py   # optional pydantic-ai adapter
├── examples/
│   └── agent_demo/     # end-to-end demo + offline & live runners
├── tests/
└── .github/workflows/
    └── publish.yml     # PyPI publish on Release

Development

pip install -e ".[dev]"
pytest

Release

Publishing is automated via .github/workflows/publish.yml. To cut a release:

  1. Bump version in pyproject.toml and __version__ in src/outskilled/__init__.py (keep them in sync — the workflow checks the tag against pyproject.toml).
  2. Commit, merge to main.
  3. On GitHub, Releases → Draft a new release, create a tag matching the bumped version (e.g. v0.2.1), publish.

The workflow runs the test suite, builds sdist + wheel, and uploads to PyPI via trusted publishing (OIDC — no API token in secrets).

One-time PyPI setup (only needed on the first release): on PyPI, Your projects → outskilled → Publishing → Add a new publisher:

Field Value
Owner phiweger
Repository outskilled
Workflow filename publish.yml
Environment name pypi

For the very first release (before the project exists on PyPI), use the pending publisher flow under Your account → Publishing → Add a pending publisher.

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

outskilled-0.1.0.tar.gz (35.9 kB view details)

Uploaded Source

Built Distribution

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

outskilled-0.1.0-py3-none-any.whl (17.5 kB view details)

Uploaded Python 3

File details

Details for the file outskilled-0.1.0.tar.gz.

File metadata

  • Download URL: outskilled-0.1.0.tar.gz
  • Upload date:
  • Size: 35.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for outskilled-0.1.0.tar.gz
Algorithm Hash digest
SHA256 12869b10e436f4554d978fa6b63966255316abb5c0a1ea8aeaa298d2d6dc8635
MD5 5c1f5ff1dec74f4408e81cb44f0a0da7
BLAKE2b-256 9bba0eb06056c69cf5b56adc3ffb5f29e4711dc64be6e86d276806edd4d30fbb

See more details on using hashes here.

Provenance

The following attestation bundles were made for outskilled-0.1.0.tar.gz:

Publisher: publish.yml on phiweger/outskilled

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file outskilled-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: outskilled-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 17.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for outskilled-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a11f4a15840409479ec9a6113b4ff016f574accb8a503b266f6d63794353e658
MD5 b5c852b4da4be7b9153081ba0ca76197
BLAKE2b-256 3a4f4e07df4ce33886971c50986061759301a2cb5b449e43b8e8a8c49229cc11

See more details on using hashes here.

Provenance

The following attestation bundles were made for outskilled-0.1.0-py3-none-any.whl:

Publisher: publish.yml on phiweger/outskilled

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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