Skip to content

v2.0.0

Latest

Choose a tag to compare

@DougTrajano DougTrajano released this 06 Sep 01:43
c2e5761

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:

  1. Remote skill sources — Git and S3 registries that materialize skill libraries locally, with
    composition (filter, prefix, rename, merge)
  2. Bundled files — a skill package's references/, assets/ and scripts/, indexed and served
    to the model
  3. Sandboxed script execution — keeping untrusted scripts off the host
  4. ${SKILL_DIR} resolution — harness leaves the placeholder in the instructions; this
    substitutes the real path
  5. 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_depth for SKILL.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.md whose frontmatter name disagrees with
    its directory. v1 only warned, so a skill that loaded with a warning may now raise at construction.
  • Registries return directories, not Skill objects. sync() subsumes install/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__ when auto_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 a SkillInfo
    rather than a full Skill, and prefixed() / 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 pinning pydantic-ai-slim alone becomes
    unsatisfiable as harness raises its own floor
  • tests/test_harness_compat.py pins the harness surface this package depends on — harness is on
    0.x and 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