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())

A single discovered Agent Skill from the filesystem.

script_tool_names

script_tool_names() -> list[str]

Return {skill}__{script_stem} tool names for every .py / .sh in scripts/.

foo.py and foo.sh share the {skill}__foo name; .py sorts first and wins, so the name is listed once (matching the skills-server registration).

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)

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 raise :class:SkillLoadError rather than being silently skipped.

Usage::

manager = SkillManager()                                # auto-discover
manager = SkillManager(skill_dirs=["/path/to/skills"])  # explicit
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) -> 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_name}__{script_stem}(args=""): runs a Python or shell script from a skill's scripts/ dir

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

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.