Skip to content

aimu.skills

Filesystem-discovered agent skills, plus runtime skill authoring. See how-to: build a personal assistant for the self-improvement pattern.

aimu.skills.AgentSkill dataclass

AgentSkill(name: str, description: str, path: Path, compatibility: str = '', license_info: str = '', metadata: dict = dict(), allowed_tools: tuple[str, ...] = ())

A single discovered Agent Skill from the filesystem.

The fields mirror the Agent Skills specification's frontmatter, with two renames where the spec's key is not a usable Python attribute: license becomes license_info and allowed-tools becomes allowed_tools.

script_tool_names

script_tool_names() -> list[str]

Return the :func:script_tool_name for every .py / .sh in scripts/.

Scripts whose names collapse to the same tool name are listed once, first sorted path winning, matching the skills-server registration. That covers foo.py / foo.sh (.py sorts first) and foo-bar.py / foo_bar.py (the hyphen sorts first).

load_body

load_body() -> str

Read SKILL.md, strip YAML frontmatter, return the markdown body.

aimu.skills.SkillManager

SkillManager(skill_dirs: Optional[list[str]] = None, include: Optional[Iterable[str]] = None)

Discovers and manages Agent Skills from the filesystem.

With no skill_dirs argument, scans the standard search paths at project and user scope: .agents/skills/, .claude/skills/, ~/.agents/skills/, ~/.claude/skills/. Project-level paths win on name collision. Pass explicit skill_dirs to override all defaults.

Discovery logs (at INFO) the number of skills found and the paths searched, so a missing skill directory is easy to spot. Malformed SKILL.md files, and files that violate the Agent Skills specification, raise :class:SkillLoadError rather than being silently skipped.

include narrows discovery to the named skills, so a host giving one agent a subset does not have to filter in three places: :meth:catalog_prompt and the skills server both read :attr:skills. A name in include that no search path provides raises, because the alternative is an agent quietly holding fewer skills than it asked for.

Usage::

manager = SkillManager()                                # auto-discover
manager = SkillManager(skill_dirs=["/path/to/skills"])  # explicit
manager = SkillManager(include=["pdf-processing"])      # only these
print(manager.catalog_prompt())
body = manager.get_skill_body("pdf-processing")

refresh

refresh() -> dict[str, AgentSkill]

Invalidate the cache and re-discover skills, returning the new map.

Lets a skill authored at runtime (see :func:aimu.skills.write_skill) become visible mid-run without constructing a fresh manager.

catalog_prompt

catalog_prompt() -> str

Return an XML skill catalog suitable for injection into a system prompt.

Each entry lists the skill name, description, and any script-derived tool names the model can call directly (without first calling activate_skill).

get_skill_body

get_skill_body(name: str) -> str

Return the full instructions body of a named skill.

Raises :class:SkillNotFoundError if the skill doesn't exist.

aimu.skills.SkillLoadError

Bases: ValueError

Raised when a SKILL.md file is malformed and cannot be parsed.

aimu.skills.SkillNotFoundError

Bases: KeyError

Raised when a requested skill name does not exist.

aimu.skills.build_skills_server

build_skills_server(manager: SkillManager, env: Optional[dict] = None) -> FastMCP

Build an in-process FastMCP server from a SkillManager.

Registered tools
  • activate_skill(name): returns the full SKILL.md body for the named skill
  • {skill}__{stem}(args=""): runs a Python or shell script from a skill's scripts/ dir, named by aimu.skills.skill.script_tool_name (both halves slugified)

The returned FastMCP instance can be passed directly to MCPClient(server=...).

aimu.skills.script_tool_name

script_tool_name(skill_name: str, script_stem: str) -> str

Return the tool name a skill's script is registered under: {skill}__{stem}, slugified.

The single source of this name. Three surfaces need it and must agree, because the catalogue is what the model reads: :meth:AgentSkill.script_tool_names advertises it, :func:aimu.skills.mcp.build_skills_server registers it, and add_skill_script reports it back after writing a script. A name advertised but not registered is a tool call that cannot succeed, so they are built here rather than formatted in three places.

Because both halves are slugified, two stems that differ only in their separator (backup-db and backup_db) map to one tool name; callers dedupe on the result.

Authoring

aimu.skills.write_skill

write_skill(name: str, description: str, body: str, *, skills_dir: Union[str, Path], overwrite: bool = False, metadata: Optional[dict] = None, scripts: Optional[dict[str, str]] = None) -> Path

Write a new SKILL.md under skills_dir/<name>/ and return its path.

The file carries YAML frontmatter (name, description, optional metadata) followed by the markdown body, matching the format :class:~aimu.skills.manager.SkillManager discovers.

Validates that name is a slug (lowercase-with-hyphens, no path separators, which also prevents traversal) and that description is non-empty. Refuses to overwrite an existing skill unless overwrite=True. The written file is round-tripped through the manager parser, so an authored skill is guaranteed discoverable (a parse failure raises :class:~aimu.skills.manager.SkillLoadError).

scripts maps "<slug>.py" / "<slug>.sh" filenames to source, written into scripts/ (each becomes a {skill}__{stem} tool). .sh files are marked executable.

aimu.skills.make_skill_authoring_tool

make_skill_authoring_tool(manager: SkillManager, skills_dir: Union[str, Path]) -> Callable

Return an async @tool that authors a skill and refreshes manager.

The tool writes a new SKILL.md under skills_dir via :func:write_skill, then calls :meth:SkillManager.refresh so the skill is discoverable in the same run. Both manager and skills_dir are captured by closure (no module globals).

Note: after refresh, activate_skill (and any fresh-conversation catalog rebuild) will surface the new skill, but a skill catalog already injected into an in-flight system prompt is not retroactively updated. See :class:~aimu.aio.SkillAgent.