Part 5: Loading `SKILL.md`
The skills are on disk. Now we need Python code that reads them, extracts the frontmatter, and returns content the agent can use.
In the notebook you start small with the name, description, and markdown body, which is enough to implement lazy loading.
A Skill data class
Create a tiny container for one loaded skill:
from dataclasses import dataclass
@dataclass
class Skill:
name: str
description: str
content: str
This class keeps the loader code honest. Every part of the agent can pass
around one Skill object instead of a loose dict with unknown keys.
Parse frontmatter
Set the skills directory:
from pathlib import Path
skills_dir = Path("skills/")
name = "hello"
The main file for a skill is always:
skill_file = skills_dir / name / "SKILL.md"
Use python-frontmatter to read the YAML block and markdown body:
import frontmatter
parsed = frontmatter.load(skill_file)
The parser gives you two pieces:
parsed.metadata- fields from the YAML frontmatter.parsed.content- markdown after the frontmatter.
Wrap those pieces into the Skill object:
skill = Skill(
name=parsed.metadata.get("name", name),
description=parsed.metadata.get("description", ""),
content=parsed.content,
)
This fallback keeps the directory name useful even if a small example skill
omits the name field. A stricter real loader should validate that the field is
present and matches the folder name.
A function version
Before creating a class, build the same logic as a function:
def load_skill(name: str) -> Skill | None:
skill_file = skills_dir / name / "SKILL.md"
if not skill_file.exists():
return None
parsed = frontmatter.load(skill_file)
return Skill(
name=parsed.metadata.get("name", name),
description=parsed.metadata.get("description", ""),
content=parsed.content,
)
Test it with hello:
load_skill("hello")
The output should contain the skill name, the greeting description, and the markdown body.
Listing all skills
To give the agent a list of available skills, scan each subdirectory:
def list_skills() -> list[Skill]:
skills = []
if not skills_dir.exists():
return skills
for skill_dir in sorted(skills_dir.iterdir()):
if not skill_dir.is_dir():
continue
skill = load_skill(skill_dir.name)
if skill:
skills.append(skill)
return skills
This treats every subfolder as a skill folder. A real loader should add error handling, but the teaching version keeps the happy path visible.
Call the listing function:
list_skills()
You should see the five example skills:
coding_standardscounterdeploy_apphellojoke
The SkillLoader class
The class version puts the same operations behind one object:
class SkillLoader:
def __init__(self, skills_dir: Path | str = None):
self.skills_dir = Path(skills_dir)
def load_skill(self, name: str) -> Skill | None:
skill_file = self.skills_dir / name / "SKILL.md"
if not skill_file.exists():
return None
parsed = frontmatter.load(skill_file)
return Skill(
name=parsed.metadata.get("name", name),
description=parsed.metadata.get("description", ""),
content=parsed.content,
)
The class also lists skills:
def list_skills(self) -> list[Skill]:
skills = []
for skill_dir in sorted(self.skills_dir.iterdir()):
if not skill_dir.is_dir():
continue
skill = self.load_skill(skill_dir.name)
skills.append(skill)
return skills
Then it creates the short listing we'll inject into the agent prompt:
def get_description(self) -> str:
skills = self.list_skills()
skills_listing = "\n".join(
f" - {s.name}: {s.description}"
for s in skills
)
return skills_listing
Create and use the loader:
skill_loader = SkillLoader(skills_dir)
print(skill_loader.get_description())
The result should look like this:
- coding_standards: Use this when writing or reviewing code - provides project-specific coding standards
- counter: Count things or list items with numbers
- deploy_app: Deploy the application using deployment scripts and templates
- hello: Skill for ALL greeting requests
- joke: Skill for ALL joke requests
With this description, the model can choose a skill without loading all skill bodies into the prompt.
Resolving extra files in the prototype
The prototype loader adds one important feature: resolving @filename
references inside skill content. In prototype/src/skills.py, load() calls
_resolve_file_references() before returning the tool result.
This is prototype code you import rather than type:
load(name)looks up the skill, resolves its file references against the skill's own directory, and returns aSkillToolResult._resolve_file_references(content, base_dir)replaces references like@scripts/deploy.shwith a concrete path when that file exists, and leaves the reference untouched otherwise.
The full source is in the code repo at
agent-skills/prototype/src/skills.py.
This exposes script and template paths from the loaded skill. The agent can
then use its normal read_file or bash_command tools to read or run those
files.
Continue with Part 6: The skill tool and prompt injection to make the loader available as an agent tool.