SKILL.md Format and Discovery
March 17, 2026 · View on GitHub
Overview
A SKILL.md file is a dual-purpose document: it describes a skill's API for Claude Code (dev-time) and simultaneously serves as the system prompt for a sub-LLM when a SkillMDToolBox tool is invoked at runtime.
Any Python package can ship a SKILL.md. Once installed, it is automatically discovered by SkillMDLoader and converted into an AgentTool accessible to any ReActLoopEngine that has SkillMDToolBox registered.
Format Specification
A SKILL.md must begin with a YAML-style frontmatter block:
---
name: <slug-friendly skill name>
description: <one-line tool description shown to the LLM>
---
<body: any markdown content>
Frontmatter
Parsed by _parse_skill_md() — ovos_agentic_loop/skills/loader.py:30. The parser:
- Requires a leading
---\ndelimiter and a closing---\ndelimiter (regex^---\s*\n(.*?)\n---\s*\n(.*)with DOTALL). - Extracts
key: valuepairs matching^(\w[\w-]*):\s*(.+)$—loader.py:61. Nested YAML is not supported. - Returns
None(skips the file) if eithernameordescriptionis missing —loader.py:67.
Required fields:
| Field | Description |
|---|---|
name | Skill identifier. Slugified to form the tool name seen by the LLM. |
description | One-line description. Used verbatim as AgentTool.description. |
Optional fields: Any additional key: value pairs are stored in SkillMDEntry.raw_frontmatter — loader.py:27 — but not used by the toolbox.
Body
Everything after the second --- delimiter, stripped of leading/trailing whitespace — loader.py:56. The body:
- Becomes the
MessageRole.SYSTEMprompt in the sub-LLM call —toolbox.py:122. - Can contain any markdown: headings, lists, code blocks, instructions, examples.
- Should describe the skill's capabilities, constraints, and output format.
Authoring Guide
A well-formed SKILL.md for a skill that searches the web:
---
name: web-search
description: Use when the agent needs to search the web for current or factual information.
author: OpenVoiceOS
version: 1.0
---
## Instructions
You are a web search assistant. Given a task, search the web and return concise, factual answers.
- Always cite source URLs.
- Limit responses to the most relevant facts.
- If multiple conflicting sources exist, note the discrepancy.
- Prefer recent sources (within the last 2 years) for time-sensitive topics.
## Output Format
Respond with:
1. A direct answer to the question.
2. A list of source URLs.
Authoring Rules
namemust be unique across all installed SKILL.md files. Duplicate names produce duplicate tools — no error is raised.descriptionis shown directly to the LLM. Make it specific enough that the agent selects the right tool.- The body replaces the entire system prompt of the sub-LLM. Do not rely on any outer persona context being visible.
- Tool name is derived from
namevia_slugify()—toolbox.py:12. Characters outside[a-z0-9]become underscores."web-search"→"web_search".
Discovery Strategies
SkillMDLoader (loader.py:143) runs two discovery strategies in order, then appends any extra_paths from config:
1. Entry-point discovery — _discover_via_entry_points() — loader.py:79
Packages register SKILL.md paths under the opm.agents.skill_md entry-point group:
[project.entry-points."opm.agents.skill_md"]
my-web-search = "my_package.skills:SKILL_MD_PATH"
Where SKILL_MD_PATH is a module attribute holding the absolute path string.
Fallback: if loading the attribute fails, the raw ep.value string is treated as a path.
This is the recommended method for packages that want zero-ambiguity discovery.
2. Package-data scan — _discover_via_package_data() — loader.py:114
Walks all installed distributions' recorded file manifests looking for files named exactly SKILL.md — loader.py:134. Uses importlib.metadata.distributions(). Resolves paths via f.locate().resolve(). Files that do not exist on disk are silently skipped.
This is the zero-config fallback: if a package ships SKILL.md in its package root and it appears in the distribution RECORD, it will be found automatically.
3. Extra paths — SkillMDLoader.extra_paths — loader.py:174
Set at construction time or via config["extra_skill_md_paths"] in SkillMDToolBox — toolbox.py:84. Useful for development, testing, or ad-hoc skill injection.
Deduplication
All three strategies are concatenated and deduplicated (preserving order) before parsing — loader.py:183.
Parsed Result — SkillMDEntry
SkillMDEntry (loader.py:9) is a dataclass with fields:
| Field | Type | Description |
|---|---|---|
name | str | From frontmatter |
description | str | From frontmatter |
body | str | Markdown body (the system prompt) |
path | str | Absolute path to the source file |
raw_frontmatter | Dict[str, str] | All parsed frontmatter key-value pairs |
Runtime Tool Invocation
When the agent loop calls a SKILL.md-backed tool:
SkillMDToolBox.discover_tools()—toolbox.py:128— has already built oneAgentToolperSkillMDEntry.- The
tool_calllambda captures theSkillMDEntryvia default-argument closure —toolbox.py:144. _invoke_skill(entry, args)—toolbox.py:96— assembles[SYSTEM: entry.body, USER: args.task ...]and callsself._brain.continue_chat(messages).- Returns
SkillCallOutput(result=response.content, skill_used=entry.name).
The brain's response is the sub-LLM's answer guided entirely by the SKILL.md body. The outer loop sees it as a string observation via str(result) — react.py:193.
Shipping a SKILL.md in Your Package
- Create
your_package/SKILL.mdwith valid frontmatter. - Add to
pyproject.toml:[project.entry-points."opm.agents.skill_md"] my-skill-name = "your_package:SKILL_MD_PATH" - In
your_package/__init__.py(or any importable module):import os SKILL_MD_PATH: str = os.path.join(os.path.dirname(__file__), "SKILL.md") - Ensure
SKILL.mdis listed inpackage_dataorMANIFEST.inso it appears in the distribution RECORD.
After pip install -e ., SkillMDLoader will discover and parse it.