v2.0.0 — rebuilt on pydantic-ai-harness
v2 stops reimplementing Agent Skills and becomes a companion to
pydantic-ai-harness.
harness now ships a Skills capability that covers levels 1 and 2 of the
Agent Skills spec — discovery, SKILL.md parsing, validation, the catalog,
and instruction rendering — and Pydantic AI's own load_capability tool loads a skill on demand.
v1 did all of that itself, which meant a parallel implementation guaranteed to drift from upstream.
So v2 delegates it, and keeps only what harness deliberately does not do:
- Remote skill sources — Git and S3 registries that materialize skill libraries locally, with
composition (filter, prefix, rename, merge) - Bundled files — a skill package's
references/,assets/andscripts/, indexed and served
to the model - Sandboxed script execution — keeping untrusted scripts off the host
${SKILL_DIR}resolution — harness leaves the placeholder in the instructions; this
substitutes the real path- Programmatic skills — skills defined in Python, joining the same deferred catalog
If your skills are instructions and nothing else — no references/, no scripts/, no remote
sources — you no longer need this package. Use pydantic_ai_harness.Skills directly: smaller
dependency, identical behaviour.
⚠️ Breaking changes
This is a clean break. There are no deprecation shims.
Requirements
| v1 | v2 | |
|---|---|---|
pydantic-ai-slim |
>=1.105 |
>=2.38 |
pydantic-ai-harness |
not used | >=0.28, required |
| Python | >=3.10 |
>=3.10 |
harness sets a pydantic-ai-slim>=2.37 floor of its own; this package needs 2.38 for
AbstractCapability.visit_and_replace, which SkillsCapability overrides.
The entry point
# v1
from pydantic_ai_skills import SkillsToolset
agent = Agent(model, toolsets=[SkillsToolset(directories=['./skills'])])
# v2
from pydantic_ai_skills import SkillsCapability
agent = Agent(model, capabilities=[SkillsCapability('./skills')])Removed, and what replaces it
| v1 | v2 |
|---|---|
SkillsToolset |
SkillsCapability, passed via capabilities= |
list_skills tool |
The deferred-capability catalog, in the prompt |
load_skill tool |
Pydantic AI's load_capability |
SkillsDirectory, discover_skills() |
harness discovers; packages.index_libraries() indexes bundled files |
parse_skill_md(), validate_skill_metadata() |
harness parses and always validates |
Skill.from_file() |
harness loads directory-backed skills |
instruction_template= |
harness renders # Skill: <name> |
max_depth= |
Immediate children only, matching harness |
validate= |
harness always validates |
exclude_tools= |
resources=False / scripts=False |
reload(), auto_reload= |
Construct a new SkillsCapability |
defer_loading= on the capability |
Every skill is individually deferred |
SkillRegistry.search/get/install/update |
SkillRegistry.sync() -> Path |
SkillRegistry.get_skills() |
skill_infos() / skill_names() |
script_executor= on a registry |
script_executor= on SkillsCapability |
Behaviour changes worth checking before you upgrade
- How a skill is loaded. The model now calls
load_capability(id='pdf-processing'), not
load_skill('pdf-processing'). Update any prompts or evals that name the old tool. - Discovery is one level deep. v1 searched up to
max_depthforSKILL.md; v2 looks only at a
library's immediate children, matching harness. Flatten nested layouts, or pass each subdirectory
as its own library. - Name validation is strict. harness rejects a
SKILL.mdwhose frontmatternamedisagrees with
its directory. v1 only warned, so a skill that loaded with a warning may now raise at construction. - Registries return directories, not
Skillobjects.sync()subsumesinstall/update: it
clones or pulls, downloads or re-syncs, and returns the library path. It is synchronous and safe to
call repeatedly. - Cloning is lazy. v1 cloned in
__init__whenauto_install=True; v2 clones on the first
sync(), so constructing a registry no longer touches the network. - Composition wrappers stage real directories. A
filtered()predicate now receives aSkillInfo
rather than a fullSkill, andprefixed()/renamed()rewrite each staged package's frontmatter
name, because harness requires it to match the directory.
New in v2
SkillsCapability |
One composite over harness's Skills, carrying the bundled-file tools |
LocalSkillsRegistry |
A local directory that can take part in composition |
GitSkillsRegistry.revision() |
The commit SHA the local clone is on |
S3SkillsRegistry.revision(name) |
The newest object modification time for a skill |
SkillRegistry.__or__ |
a | b builds a CombinedRegistry |
require_loaded= |
Refuse file reads for a skill the model has not loaded, keeping bundled files behind the same boundary as the instructions |
resolve_skill_dir= |
Substitute ${SKILL_DIR} / ${CLAUDE_SKILL_DIR} in instructions with the real path |
list_bundled_files= |
Append a "Bundled files" section naming a skill's resources and scripts to its instructions |
@skill |
A standalone decorator for Python-defined skills, replacing @toolset.skill() |
Bundled-file ergonomics. The file tools key on skill-relative paths (scripts/aggregate.py),
while a SKILL.md names its own files in prose. So each loaded skill's instructions now list the
exact resource_name / script_name values — behind load_capability, never in the always-on
catalog — and both tools additionally accept an unambiguous shorthand (aggregate and
aggregate.py reach scripts/aggregate.py; two files sharing a name resolve to neither, and the
retry names both).
Migration
Full guide, including agent-spec changes and the reloading pattern for long-lived servers:
[Migrating from v1](https://dougtrajano.github.io/pydantic-ai-skills/migration-v2/)
Also in this release
- CI now runs Python 3.10–3.14 against a floor/latest pair of
pydantic-ai-harness+
pydantic-ai-slim; the two move together, because pinningpydantic-ai-slimalone becomes
unsatisfiable as harness raises its own floor tests/test_harness_compat.pypins the harness surface this package depends on — harness is on
0.xand documents that its API may change between minor releases- A SonarQube CLI skill for contributors (#84), plus upstream-watch entries (#75, #77, #80, #82) and
dependency bumps (#72, #73, #81). The Sonar skill is repository tooling under.claude/skills/
and is not part of the distributed package.
Full Changelog: 1.4.0...2.0.0