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:
- https://agentskills.io/home
- https://agentskills.io/skill-creation/best-practices
- https://github.com/agentskills/agentskills
- https://code.claude.com/docs/en/skills
- https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills
- https://resources.anthropic.com/hubfs/The-Complete-Guide-to-Building-Skill-for-Claude.pdf
Pydantic-AI:
- https://pydantic.dev/docs/ai/overview/coding-agent-skills/
- https://github.com/pydantic/skills
- https://pydantic.dev/docs/ai/core-concepts/capabilities/
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.instructionsfunction 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: truein its frontmatter has its body inlined into the instructions. Independent of the manifest flag — bodies are inlined even when the manifest is suppressed. list_skillstool (opt-in): re-renders the manifest at runtime.read_skill_resourcetool (Level 3, opt-in): exposes files under a skill'sreferences/,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:
- Bump
versioninpyproject.tomland__version__insrc/outskilled/__init__.py(keep them in sync — the workflow checks the tag againstpyproject.toml). - Commit, merge to
main. - 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.
Metadata
Release files for outskilled 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| outskilled-0.1.0.tar.gz | 35.9 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| outskilled-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 53.3 kB
Release files / outskilled-0.1.0.tar.gz
| Download URL | outskilled-0.1.0.tar.gz |
|---|---|
| Size | 35.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
12869b10e436f4554d978fa6b63966255316abb5c0a1ea8aeaa298d2d6dc8635
|
|
BLAKE2b-256 checksum How to use checksums |
9bba0eb06056c69cf5b56adc3ffb5f29e4711dc64be6e86d276806edd4d30fbb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 20, 2026.
Transparency logRelease files / outskilled-0.1.0-py3-none-any.whl
| Download URL | outskilled-0.1.0-py3-none-any.whl |
|---|---|
| Size | 17.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a11f4a15840409479ec9a6113b4ff016f574accb8a503b266f6d63794353e658
|
|
BLAKE2b-256 checksum How to use checksums |
3a4f4e07df4ce33886971c50986061759301a2cb5b449e43b8e8a8c49229cc11
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 20, 2026.
Transparency logSecurity for outskilled 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for outskilled-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 20, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.