Skip to content

Packages API Reference

The bundled-file layer: what a skill package ships alongside its SKILL.md.

harness reads a skill's instructions and stops there — it does not enumerate, read, or execute a package's references/, assets/ or scripts/ files. This module indexes exactly those, keyed by the same name harness gives the skill's capability, which is what lets read_skill_resource and run_skill_script resolve a skill the model has loaded.

Discovery mirrors harness's rule: a skill is an immediate child directory of a library containing a SKILL.md.

Index the bundled files of every skill package in libraries.

Scans the immediate child directories of each library for a SKILL.md, exactly as harness's Skills does, so the keys of the returned mapping line up with the id of each deferred capability harness produces.

Later libraries win on a duplicate name, matching the argument order the caller passed to Skills. (harness rejects duplicates among selected skills outright, so a surviving duplicate here belongs to a skill that was excluded from the catalog.)

Parameters:

Name Type Description Default
libraries Sequence[str | Path]

Skill-library directories. Non-existent entries are skipped rather than raising — harness validates library paths itself and reports them with a better message.

required
script_executor SkillScriptExecutor | None

Executor used for the discovered scripts. Defaults to LocalSkillScriptExecutor, which runs them as subprocesses on the host.

None
exclude_resources Iterable[str] | None

Extra glob patterns to exclude from resource discovery, in addition to the built-in :data:DEFAULT_RESOURCE_EXCLUDES.

None

Returns:

Type Description
dict[str, SkillPackage]

Mapping of NFKC-normalized skill name to its

dict[str, SkillPackage]
Source code in pydantic_ai_skills/packages.py
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
def index_libraries(
    libraries: Sequence[str | Path],
    *,
    script_executor: SkillScriptExecutor | None = None,
    exclude_resources: Iterable[str] | None = None,
) -> dict[str, SkillPackage]:
    """Index the bundled files of every skill package in `libraries`.

    Scans the immediate child directories of each library for a `SKILL.md`, exactly as
    harness's `Skills` does, so the keys of the returned mapping line up with the `id`
    of each deferred capability harness produces.

    Later libraries win on a duplicate name, matching the argument order the caller
    passed to `Skills`. (harness rejects duplicates among *selected* skills outright, so
    a surviving duplicate here belongs to a skill that was excluded from the catalog.)

    Args:
        libraries: Skill-library directories. Non-existent entries are skipped rather
            than raising — harness validates library paths itself and reports them with
            a better message.
        script_executor: Executor used for the discovered scripts. Defaults to
            [`LocalSkillScriptExecutor`][pydantic_ai_skills.LocalSkillScriptExecutor],
            which runs them as subprocesses on the host.
        exclude_resources: Extra glob patterns to exclude from resource discovery, in
            addition to the built-in :data:`DEFAULT_RESOURCE_EXCLUDES`.

    Returns:
        Mapping of NFKC-normalized skill name to its
        [`SkillPackage`][pydantic_ai_skills.packages.SkillPackage].
    """
    # `is None`, not `or`: a falsey custom executor (e.g. a pool-backed one that is
    # empty at index time) must not be silently replaced by the host executor, which
    # would run untrusted scripts on the host.
    executor = LocalSkillScriptExecutor() if script_executor is None else script_executor
    packages: dict[str, SkillPackage] = {}

    for configured in libraries:
        library = Path(configured)
        if not library.is_dir():
            continue

        for child in sorted(library.iterdir()):
            if not child.is_dir() or not (child / 'SKILL.md').is_file():
                continue

            name = unicodedata.normalize('NFKC', child.name)
            scripts = _discover_scripts(child, name, executor)
            resources = _discover_resources(
                child,
                exclude_resources=exclude_resources,
                script_uris={script.uri for script in scripts if script.uri},
            )
            packages[name] = SkillPackage(
                name=name,
                directory=child.resolve(),
                resources=tuple(resources),
                scripts=tuple(scripts),
            )

    return packages

The on-disk files of one Agent Skill package.

Built by index_libraries for every immediate child directory of a skill library that contains a SKILL.md. Holds only what harness's Skills does not: the package's directory and its bundled resources and scripts.

Attributes:

Name Type Description
name str

The skill's directory name, NFKC-normalized so it matches the id harness gives the skill's deferred capability.

directory Path | None

The resolved skill directory, or None for a programmatic skill that has no on-disk package. When set, this is the value substituted for ${SKILL_DIR} / ${CLAUDE_SKILL_DIR} in the skill's instructions.

resources tuple[SkillResource, ...]

Bundled text files, keyed in resources_by_name by their skill-relative posix path (e.g. references/FORMS.md).

scripts tuple[SkillScript, ...]

Bundled executables, named by their skill-relative posix path (e.g. scripts/fill_form.py).

Source code in pydantic_ai_skills/packages.py
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
@dataclass(frozen=True)
class SkillPackage:
    """The on-disk files of one Agent Skill package.

    Built by [`index_libraries`][pydantic_ai_skills.packages.index_libraries] for every
    immediate child directory of a skill library that contains a `SKILL.md`. Holds only
    what harness's `Skills` does not: the package's directory and its bundled resources
    and scripts.

    Attributes:
        name: The skill's directory name, NFKC-normalized so it matches the `id` harness
            gives the skill's deferred capability.
        directory: The resolved skill directory, or None for a programmatic skill that has
            no on-disk package. When set, this is the value substituted for
            `${SKILL_DIR}` / `${CLAUDE_SKILL_DIR}` in the skill's instructions.
        resources: Bundled text files, keyed in `resources_by_name` by their
            skill-relative posix path (e.g. `references/FORMS.md`).
        scripts: Bundled executables, named by their skill-relative posix path
            (e.g. `scripts/fill_form.py`).
    """

    name: str
    directory: Path | None = None
    resources: tuple[SkillResource, ...] = ()
    scripts: tuple[SkillScript, ...] = ()

    resources_by_name: dict[str, SkillResource] = field(init=False, repr=False, compare=False)
    scripts_by_name: dict[str, SkillScript] = field(init=False, repr=False, compare=False)

    def __post_init__(self) -> None:
        """Build the name lookups the skill-file tools resolve against."""
        object.__setattr__(self, 'resources_by_name', {resource.name: resource for resource in self.resources})
        object.__setattr__(self, 'scripts_by_name', {script.name: script for script in self.scripts})

__post_init__

__post_init__() -> None

Build the name lookups the skill-file tools resolve against.

Source code in pydantic_ai_skills/packages.py
280
281
282
283
def __post_init__(self) -> None:
    """Build the name lookups the skill-file tools resolve against."""
    object.__setattr__(self, 'resources_by_name', {resource.name: resource for resource in self.resources})
    object.__setattr__(self, 'scripts_by_name', {script.name: script for script in self.scripts})

Discovery rules

Resources — any file under the skill directory, at any depth, that reads as UTF-8 text, other than SKILL.md. Binary files are skipped, as is anything matching an exclude glob. Named by its posix path relative to the skill directory (references/FORMS.md).

Scripts — files in the skill root and its scripts/ subdirectory that either carry a known extension (.py, .sh, .bash, .zsh, .fish, .ps1, .bat, .cmd) or have the executable bit set. Named the same way (scripts/run.py).

A file discovered as a script is never also offered as a resource.

Symlinks that resolve outside the skill directory are skipped with a UserWarning — following one would let a skill hand the model, or execute, an arbitrary file on the host.