AGENTS.md · git:20260727.454ebc8 · 2026-07-27 · sha256 7a64d55d6bfc4d9d

AGENTS.md git:20260727.454ebc8A

Immutable. This exact content is served forever at /api/v1/blob/7a64d55d6bfc4d9d.

# AGENTS.md

**pydantic-ai-skills** is a Python library that implements the full [Agent Skills specification](https://agentskills.io/home) for [Pydantic AI](https://ai.pydantic.dev/) — enabling modular skill discovery, bundled resources, script execution, remote registries, and runtime reload for AI agents.

This file provides guidance to coding agents (Claude Code, etc.) when working with code in this repository. `CLAUDE.md` is a symlink to this file — edit this one.

## Scope

Python library (>=3.10) implementing the [Agent Skills specification](https://agentskills.io/home) for [Pydantic AI](https://ai.pydantic.dev/). Ships the full skill package — bundled resources, script execution, remote registries, programmatic skills, runtime reload — not just `SKILL.md` injection.

Keep this file to agent-critical defaults. Task-specific detail belongs in [docs/](docs/) (see [Link, Don't Duplicate](#link-dont-duplicate)).

## Commands

```bash
pip install -e ".[test,git,s3,dev]"   # full dev install
pytest                                # all tests (coverage is on by default via pytest.ini)
pytest tests/test_toolset.py -v       # one file
pytest tests/test_toolset.py::test_name -v  # one test
pytest -m "not slow"                  # skip slow markers
pre-commit run --all-files            # what CI's lint job runs: ruff + ruff-format + mypy
ruff check pydantic_ai_skills/ && ruff format pydantic_ai_skills/
mkdocs serve                          # docs, needs pip install -e ".[docs]"
```

`pytest.ini` sets `asyncio_mode = auto` — do **not** add `@pytest.mark.asyncio`.

## Architecture

Layers, in dependency order:

| Module | Role |
| --- | --- |
| [types.py](pydantic_ai_skills/types.py) | `Skill`, `SkillResource`, `SkillScript`, `SkillWrapper`; name normalization |
| [_parsing.py](pydantic_ai_skills/_parsing.py) | `SKILL.md` frontmatter parsing |
| [directory.py](pydantic_ai_skills/directory.py) | Filesystem discovery: `SkillsDirectory`, `discover_skills` |
| [local.py](pydantic_ai_skills/local.py) | Script execution via AnyIO subprocess; file-backed resources/scripts |
| [registries/](pydantic_ai_skills/registries/) | `SkillRegistry` ABC + Git/S3 sources + composition wrappers (filtered/prefixed/renamed/combined) |
| [toolset.py](pydantic_ai_skills/toolset.py) | `SkillsToolset` — the real implementation; registers the 4 tools, builds instructions |
| [capability.py](pydantic_ai_skills/capability.py) | `SkillsCapability` — thin `AbstractCapability` wrapper that delegates to `SkillsToolset` |

The four tools exposed to the model: `list_skills`, `load_skill`, `read_skill_resource`, `run_skill_script`.

Invariants to preserve:

- **Skill source priority is programmatic > directories > registries.** Programmatic names are protected (directory skills with the same name are skipped); within directories, last wins with a `UserWarning`; registries never override an existing name. See `_collect_dir_skills_into` and `_load_registry_skills` in [toolset.py](pydantic_ai_skills/toolset.py).
- **`SkillsCapability` stays a delegating wrapper** over `SkillsToolset` so both integration paths behave identically. Add behavior to the toolset, not the capability.
- **Registry failures degrade, they don't raise** — `get_skills()` errors are caught and warned.

## Conventions and Pitfalls

- Every tool function registered in [toolset.py](pydantic_ai_skills/toolset.py) takes `ctx: RunContext[Any]` as its first parameter.
- Single-quoted strings, line length 120, Google-style docstrings (Ruff enforces; `D100`/`D102`/`D104`/`D105`/`D107` are ignored — see [pyproject.toml](pyproject.toml)).
- Skill names: `lowercase-with-hyphens`, max 64 chars, no `anthropic`/`claude` reserved words.
- Do not regress path-traversal and symlink checks in discovery and load paths.
- Script discovery covers supported extensions **and** any executable file — not Python only.
- AnyIO process stream readers must handle `anyio.EndOfStream` explicitly.
- This package imports private pydantic-ai symbols (`pydantic_ai._function_schema`, `_griffe`, `_utils`). [tests/test_pydantic_ai_compat.py](tests/test_pydantic_ai_compat.py) exists so an upstream move fails loudly — keep it in sync with what the code actually imports.
- CI runs Python 3.10–3.14 × pydantic-ai-slim `1.105.0` / `2.0.0` / `latest`. New code must work against the floor (`pydantic-ai-slim>=1.105`), not just latest.
- `gitpython` (`[git]`) and `boto3` (`[s3]`) are optional extras — import them lazily and raise a clear `ImportError` naming the extra, as [registries/s3.py](pydantic_ai_skills/registries/s3.py) does.

## Working Style

- Prefer small, surgical diffs. Every changed line should trace to the request; don't refactor or reformat adjacent code, and mention unrelated dead code rather than deleting it.
- Match surrounding style in [pydantic_ai_skills/](pydantic_ai_skills/) even where you'd write it differently.
- Add or update tests in [tests/](tests/) for any behavior change.
- If the request is ambiguous or a simpler approach exists, say so before implementing.

## Link, Don't Duplicate

- Concepts and architecture: [docs/concepts.md](docs/concepts.md)
- Creating filesystem skills: [docs/creating-skills.md](docs/creating-skills.md)
- Programmatic skills: [docs/programmatic-skills.md](docs/programmatic-skills.md)
- Registries and composition: [docs/registries.md](docs/registries.md)
- Security model: [docs/security.md](docs/security.md)
- Advanced patterns: [docs/advanced.md](docs/advanced.md)
- Contribution workflow: [docs/contributing.md](docs/contributing.md)
- API reference: [docs/api/](docs/api/)

External:

- Pydantic AI integration details: https://ai.pydantic.dev/llms.txt
- Agent Skills specification: https://agentskills.io/home