Implemented AgentSkillSpec loader that parses SKILL.md frontmatter and progressive disclosure sections (discover/activate/deactivate) into structured SkillStep objects with stable 1-based ordering. Mapped Agent Skills to AgentSkillToolDescriptor with namespaced naming (namespace/short_name), source="agent_skill", read-only defaults, and AgentSkillResourceSlot bindings for scripts/, references/, and assets/ directories. All resource slots are unconditionally read_only. Added explicit validation for missing frontmatter fields (name, description) and invalid namespace format with actionable error messages. Added docs/reference/agent_skills.md covering folder layout, SKILL.md parsing rules, progressive disclosure model, and tool mapping. Added Behave scenarios covering valid/invalid SKILL.md parsing, namespaced naming, step ordering, missing frontmatter errors, progressive disclosure lifecycle, tool mapping, and resource binding slots. Added Robot Framework integration tests using the deploy-to-staging example skill folder (robot/agent_skills_loader.robot). Added ASV benchmarks for parsing throughput, folder load, progressive disclosure lifecycle, and resource listing (benchmarks/agent_skills_loader_bench.py). ISSUES CLOSED: #160
8.7 KiB
Agent Skills Standard Loader
The Agent Skills loader (cleveragents.skills.agent_skills_loader) implements the
AgentSkills.io standard for packaging instruction-driven,
multi-step workflows as SKILL.md files with optional resource sub-directories.
Agent Skills differ from MCP tools: instead of a single function call, they load procedural instructions that an LLM agent follows—potentially invoking multiple tools in sequence.
Folder Layout
my-skill/
├── SKILL.md ← required — frontmatter + instructions
├── scripts/ ← optional — Python/shell scripts the agent may run
│ └── deploy.py
├── references/ ← optional — supplementary Markdown documents
│ └── runbook.md
└── assets/ ← optional — any binary or static resources
└── config.json
Only SKILL.md is required. The three resource sub-directories are discovered
automatically when present and made available on demand during Tier 3 disclosure.
SKILL.md Format
SKILL.md files follow Markdown with a YAML frontmatter block:
---
name: local/deploy-to-staging
description: Deploy the current branch to the staging environment.
steps: # optional — structured steps with stable ordering
- Verify all tests pass
- Push the branch to the remote
- Run the CI pipeline
- Execute scripts/deploy.py to deploy
- Check the health endpoint
version: 1.0.0 # optional
compatibility: # optional
min_agent_version: "3.0.0"
metadata: # optional — arbitrary key/value pairs
author: team
environment: staging
allowed-tools: # optional — restrict which tools the skill may call
- builtin/shell
- builtin/git-status
---
# Deployment Details
Additional prose context for each step, loaded on activation.
Required Frontmatter Fields
| Field | Type | Description |
|---|---|---|
name |
string |
Namespaced name: namespace/short-name (e.g. local/deploy-staging) |
description |
string |
Short human-readable description of the skill |
Optional Frontmatter Fields
| Field | Type | Default | Description |
|---|---|---|---|
steps |
list[string] |
[] |
Ordered step instructions parsed into SkillStep objects with stable 1-based indexing |
version |
string |
"0.0.0" |
Semantic version of the skill |
compatibility |
mapping |
{} |
Compatibility constraints (e.g. min_agent_version) |
metadata |
mapping |
{} |
Arbitrary key/value pairs (author, tags, etc.) |
allowed-tools |
list[string] |
[] |
Tools the skill is permitted to invoke |
Structured Steps
When the steps key is present, each list entry becomes a SkillStep with a
stable 1-based index and non-empty content string:
spec = AgentSkillSpec.from_file(Path("my-skill/SKILL.md"))
for step in spec.steps:
print(f"{step.index}. {step.content}")
# 1. Verify all tests pass
# 2. Push the branch to the remote
# ...
Steps are stored in insertion order and never reordered — step.index is
always equal to the step's position in the original list.
Namespaced Naming
Skill names follow the same namespace/short-name convention as tools and actors
(see Tool Model):
local/deploy-to-staging ← user-defined, default namespace
devops/code-review-pipeline ← custom namespace
team/onboarding-workflow ← team namespace
Rules:
- Both
namespaceandshort-namemust start with an alphanumeric character. - Allowed characters: letters, digits, hyphens (
-), underscores (_). - No spaces or special characters.
Three-Tier Progressive Disclosure
The loader implements the three-tier model defined in the specification
(docs/specification.md § AgentSkillAdapter):
| Tier | Method | What loads | Token cost |
|---|---|---|---|
| 1 — Metadata | discover() |
name + description only |
~50–100 tokens per skill |
| 2 — Instructions | activate() |
Full SKILL.md Markdown body |
Recommended < 5000 tokens |
| 3 — Resources | list_resources() |
Paths from scripts/, references/, assets/ |
Variable, on demand |
This means an actor can have dozens of Agent Skills available but only pay the token cost for their metadata at startup. Full instructions load only when the LLM determines a task matches the skill.
Tool Mapping
Agent Skills are registered in the Tool Registry with source="agent_skill".
The AgentSkillToolDescriptor carries:
| Field | Value | Notes |
|---|---|---|
source |
"agent_skill" |
Fixed |
name |
From frontmatter name |
e.g. local/deploy-to-staging |
description |
From frontmatter description |
Used in system prompt |
read_only |
True |
Default — skill itself does not write |
writes |
False |
Default |
agent_skill_path |
Absolute folder path | Runtime uses this to locate SKILL.md |
allowed_tools |
From frontmatter allowed-tools |
Optional restrict list |
body |
Empty at discover; full body after activate | Progressive disclosure |
resource_slots |
List of AgentSkillResourceSlot |
One slot per discovered resource directory |
Resource Binding Slots
Each discovered sub-directory (scripts/, references/, assets/) becomes a
read-only resource binding slot in the tool descriptor:
td = loader.to_tool_descriptor()
for slot in td.resource_slots:
print(slot.name) # "scripts", "references", or "assets"
print(slot.resource_type) # "agent_skill_scripts", etc.
print(slot.access) # "read_only"
print(slot.path) # absolute path on disk
Slots are only created for directories that exist — if a skill has no
references/ folder, no references slot is emitted. All slots are
unconditionally access="read_only".
Read-Only Default Rationale
The skill descriptor is read-only by default because the skill's instructions
do not themselves perform writes — the LLM agent decides which (potentially
write-capable) tools to invoke during execution. Individual tools in allowed-tools
may write; the skill descriptor's own capability metadata does not.
Python API
from pathlib import Path
from cleveragents.skills.agent_skills_loader import AgentSkillLoader, AgentSkillSpec
# Parse a SKILL.md string directly
spec = AgentSkillSpec.from_string("""
---
name: local/my-skill
description: Does something useful.
---
Step 1: Do this.
""")
# Load a full folder
loader = AgentSkillLoader.from_folder(Path("./skills/my-skill"))
# Tier 1 — discover (metadata only)
descriptor = loader.discover()
print(descriptor.name) # "local/my-skill"
print(descriptor.body) # "" (empty)
# Tier 2 — activate (load instructions)
activated = loader.activate()
print(activated.body) # "Step 1: Do this."
# Tier 3 — list resources on demand
resources = loader.list_resources()
for path in resources:
print(path.name)
# Deactivate to free token budget
loader.deactivate()
print(loader.active_body) # None
# Convert to Tool Registry descriptor
tool_descriptor = loader.to_tool_descriptor()
print(tool_descriptor.source) # "agent_skill"
print(tool_descriptor.read_only) # True
print(tool_descriptor.agent_skill_path) # "/abs/path/to/my-skill"
Path Safety
All file paths returned by list_resources(), script_paths, reference_paths,
and asset_paths are absolute paths that have been verified to reside within
the skill folder. The loader uses Path.resolve() + rglob() to prevent path
traversal outside the skill boundary.
Error Handling
| Condition | Exception | Message pattern |
|---|---|---|
SKILL.md not found in folder |
ValueError |
mentions "SKILL.md" |
| Folder path does not exist | FileNotFoundError |
mentions "not found" |
Missing --- frontmatter |
ValueError |
mentions "frontmatter" |
Missing required name field |
ValueError |
mentions "name" |
Missing required description field |
ValueError |
mentions "description" |
Invalid namespace/name format |
ValueError |
mentions "namespace" |
| Malformed YAML in frontmatter | ValueError |
mentions "YAML" |
Related
- Skills Protocol —
SkillDefinition,SkillMetadata - Skill CLI —
agents skillcommands - Tool Model —
Tool,ToolSource.AGENT_SKILL - MCP Adapter — MCP tool source (counterpart)
docs/specification.md§ AgentSkillAdapter — authoritative specADR-028— Agent Skills Standard adoption decision