Skip to content

ant_ai.skills.loader

SkillLoader

Loads Agent Skills from one or more directories of skill folders.

Each skill must be a sub-directory containing at least a SKILL.md file with YAML frontmatter providing name and description fields, followed by Markdown instructions as the body.

Folders that are missing SKILL.md, have invalid frontmatter, or fail spec validation are silently skipped.

Parameters:

Name Type Description Default
skills_dir str | Path | list[str | Path] | list[Path]

Path (or list of paths) to directories that contain skill sub-folders.

required
Source code in src/ant_ai/skills/loader.py
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
class SkillLoader:
    """
    Loads Agent Skills from one or more directories of skill folders.

    Each skill must be a sub-directory containing at least a ``SKILL.md`` file
    with YAML frontmatter providing ``name`` and ``description`` fields, followed
    by Markdown instructions as the body.

    Folders that are missing ``SKILL.md``, have invalid frontmatter, or fail
    spec validation are silently skipped.

    Args:
        skills_dir: Path (or list of paths) to directories that contain skill sub-folders.
    """

    def __init__(self, skills_dir: str | Path | list[str | Path] | list[Path]) -> None:
        if isinstance(skills_dir, list):
            self._skills_dirs: list[Path] = [Path(d).resolve() for d in skills_dir]
        else:
            self._skills_dirs: list[Path] = [Path(skills_dir).resolve()]

    def load(self) -> list[AgentSkill]:
        """
        Walk each skills directory and parse every valid skill folder.

        Returns:
            A list of `AgentSkill` instances across all directories, sorted by folder name within each directory.
        """
        skills: list[AgentSkill] = []
        for skills_dir in self._skills_dirs:
            skills.extend(self._load_dir(skills_dir))
        return skills

    def _load_dir(self, skills_dir: Path) -> list[AgentSkill]:
        skills: list[AgentSkill] = []
        if not skills_dir.is_dir():
            return skills

        for entry in sorted(skills_dir.iterdir()):
            if not entry.is_dir():
                continue
            skill_md: Path = entry / "SKILL.md"
            if not skill_md.is_file():
                continue
            skill: AgentSkill | None = self._parse_skill(entry, skill_md)
            if skill is not None:
                skills.append(skill)

        return skills

    def _parse_skill(self, skill_dir: Path, skill_md: Path) -> AgentSkill | None:
        try:
            raw: str = skill_md.read_text(encoding="utf-8")
        except OSError:
            return None

        match: re.Match[str] | None = _FRONTMATTER_RE.match(raw)
        if not match:
            return None

        try:
            frontmatter: dict = yaml.safe_load(match.group(1)) or {}
        except yaml.YAMLError:
            return None

        name: str | None = frontmatter.get("name")
        description: str | None = frontmatter.get("description")
        if not name or not description:
            return None
        if str(name) != skill_dir.name:
            return None

        instructions: str = match.group(2).strip()
        scripts: list[Path] = self._collect_scripts(skill_dir)

        raw_allowed: str | None = frontmatter.get("allowed-tools", "")
        allowed_tools: list[str] = str(raw_allowed).split() if raw_allowed else []

        raw_metadata: dict | None = frontmatter.get("metadata") or {}
        metadata: dict[str, str] = (
            {str(k): str(v) for k, v in raw_metadata.items()}
            if isinstance(raw_metadata, dict)
            else {}
        )

        try:
            return AgentSkill(
                name=str(name),
                description=str(description),
                instructions=instructions,
                skill_dir=skill_dir.resolve(),
                scripts=scripts,
                license=frontmatter.get("license"),
                compatibility=frontmatter.get("compatibility"),
                metadata=metadata,
                allowed_tools=allowed_tools,
            )
        except ValidationError:
            return None

    def _collect_scripts(self, skill_dir: Path) -> list[Path]:
        scripts_dir: Path = skill_dir / "scripts"
        if not scripts_dir.is_dir():
            return []
        return sorted(p for p in scripts_dir.iterdir() if p.is_file())

load

load() -> list[AgentSkill]

Walk each skills directory and parse every valid skill folder.

Returns:

Type Description
list[AgentSkill]

A list of AgentSkill instances across all directories, sorted by folder name within each directory.

Source code in src/ant_ai/skills/loader.py
35
36
37
38
39
40
41
42
43
44
45
def load(self) -> list[AgentSkill]:
    """
    Walk each skills directory and parse every valid skill folder.

    Returns:
        A list of `AgentSkill` instances across all directories, sorted by folder name within each directory.
    """
    skills: list[AgentSkill] = []
    for skills_dir in self._skills_dirs:
        skills.extend(self._load_dir(skills_dir))
    return skills