Skills
Skills are packaged AI agent capabilities per the Agent Skills spec. Each skill is a directory containing a SKILL.md file with YAML frontmatter and Markdown instructions. The framework discovers skills through a pluggable backend, injects a compact catalog into the system prompt (~50 tokens/skill), and provides a tool for the LLM to activate skills on demand.
Creating a Skill
Create a directory with a SKILL.md file:
.skills/
code-review/
SKILL.md
data-analysis/
SKILL.md
Each SKILL.md has YAML frontmatter and a Markdown body:
--- name: code-review description: Reviews code for quality, style, and potential issues. --- You are a code review assistant. Review the code for: - Style issues - Potential bugs - Performance problems
Required frontmatter fields:
-
nameโ lowercase alphanumeric with hyphens, 1-64 chars (must match directory name) -
descriptionโ 1-1024 chars, helps the LLM decide when to activate
Additional frontmatter keys are passed through as metadata.
Configuring an Agent
Use the skills block DSL to configure skills:
class MyAgent < Riffer::Agent model "openai/gpt-5-mini" instructions "You are a helpful assistant." skills do backend Riffer::Skills::FilesystemBackend.new(".skills") end end
Multiple directories can be scanned (first-path-wins for duplicates):
skills do backend Riffer::Skills::FilesystemBackend.new(".skills", "~/.riffer/skills") end
Dynamic Backend via Proc
skills do backend ->(context) { tenant_backend(context[:tenant_id]) } end
Custom Adapter
The adapter controls how the skill catalog is rendered in the system prompt and which tool the LLM calls to activate a skill. The adapter is auto-selected by provider (Markdown for most, XML for Anthropic). Override with:
skills do backend Riffer::Skills::FilesystemBackend.new(".skills") adapter Riffer::Skills::XmlAdapter end
Activated Skills
Load skill instructions into the system prompt at startup (no tool call needed):
skills do backend Riffer::Skills::FilesystemBackend.new(".skills") activate ["code-review"] end
Accepts a Proc for dynamic resolution:
skills do backend Riffer::Skills::FilesystemBackend.new(".skills") activate ->(context) { context[:active_skills] || [] } end
How It Works
-
Discovery โ At the start of
generate/stream, the backendโslist_skillsreturns frontmatter for all available skills. -
Catalog injection โ The adapter formats the catalog and appends it to the system prompt.
-
Activation โ When the LLM matches a task to a skill, it calls the
skill_activatetool with the skill name. The tool returns the full SKILL.md body. -
Execution โ The LLM follows the skillโs instructions to complete the task.
Custom Backends
Implement Riffer::Skills::Backend for non-filesystem storage:
class DatabaseBackend < Riffer::Skills::Backend def list_skills # Return Array[Riffer::Skills::Frontmatter] end def read_skill(name) # Return String (skill body) # Raise Riffer::ArgumentError if not found end end
Custom Adapters
Subclass Riffer::Skills::Adapter to customize how the skill catalog is rendered and which tool the LLM uses to activate skills:
class CustomAdapter < Riffer::Skills::Adapter def render_catalog(skills) # Return String (skill catalog for the system prompt) # Use `activate_tool.name` to reference the activation tool end def activate_tool # Return a Riffer::Tool subclass # Defaults to Riffer::Skills::ActivateTool Riffer::Skills::ActivateTool end end
The built-in adapters are Riffer::Skills::MarkdownAdapter (default) and Riffer::Skills::XmlAdapter (used by Anthropic).
Accessing Skills in Tools
The skills context (Riffer::Skills::Context) is available via context[:skills] during execution:
class SkillSearchTool < Riffer::Tool identifier "skill_search" description "Searches available skills." params do required :query, String end def call(context:, query:) skills_context = context[:skills] # Riffer::Skills::Context matches = skills_context.skills.values.select { |s| s.description.include?(query) } json(matches.map { |s| {name: s.name, description: s.description} }) end end