Skills¶
A skill is a unit of know-how in the Agent Skills shape: YAML frontmatter, a markdown body, and resources. The three go to three different places, and that is the point:
| Part | Where it goes | Why |
|---|---|---|
frontmatter (key, name, description, when_to_use, version, requires, parameters_schema, examples, the resource index) |
your store, your editor, the prompt's heading | metadata a host versions and shows |
| body (markdown, a Jinja template over the skill's config) | the system prompt, once per skill, rendered | what the model follows |
| resources (reference documents, templates) | not the prompt — fetched through the read_skill tool on the turn they are needed |
an agent with twelve skills carries twelve bodies and no appendices |
That last row is progressive disclosure, and it is why a skill is not just a longer prompt.
Author one¶
SKILL.md:
---
name: Local GAAP close
description: Close the books under local GAAP with the firm's checklist.
when_to_use: When a client asks for a month-end or year-end close under local GAAP.
version: 3
requires:
tools: [dw_workspace_write]
parameters_schema:
type: object
required: [entity]
properties:
entity: {type: string, description: The legal entity being closed.}
fiscal_year_end: {type: string, enum: ["12-31", "06-30"], default: "12-31"}
additionalProperties: false
resources:
checklist.md: A step-by-step close checklist.
templates/memo.md: {kind: template, description: The close memo the partner signs.}
---
# Closing under local GAAP
You are closing **{{ entity }}** with a fiscal year ending {{ fiscal_year_end }}.
Read `checklist.md` before starting.
from fireflyframework_agentic.skills import load_skill, parse_skill
skill = load_skill("skills/x_local_gaap") # SKILL.md + the declared resources, loaded lazily
skill = parse_skill(text, key="x_local_gaap") # text only; attach resources with with_resources()
- The key matches
^[a-z][a-z0-9_]*$; it comes fromkey=or the frontmatter'skey. - Resource paths are relative and stay inside the skill:
..and absolute paths are refused, andload_skillexposes only the resources the frontmatter declares — the directory is not a file share. - Frontmatter keys the framework does not read are kept under
skill.metadata.
Bind and render¶
bound = skill.with_config({"entity": "Acme SL"})
bound.render() # the body with the config and schema defaults substituted
skill.validate_config({...}) # every problem at once, [] when valid
skill.unmet(tools={...}, connectors={...}, skills={...}) # what the host does not provide
skill.digest() # SHA-256 over everything the model can see
parameters_schema is the JSON-schema subset an editor can render as a form — type,
required, properties with enum, minimum/maximum, minLength/maxLength, default,
additionalProperties: false. A config that fails it raises SkillConfigError on render, because
a body rendered from a wrong config is worse than no body. The body is a Jinja template with
StrictUndefined: an undefined variable is an authoring error, and {{ x | default(...) }} covers
optional values.
Put it on an agent¶
from fireflyframework_agentic.agents import FireflyAgent
from fireflyframework_agentic.skills import build_read_skill_tool, render_skills_section
skills = [skill.with_config({"entity": "Acme SL"})]
agent = FireflyAgent(
"closer",
model=model,
instructions="You are a careful accountant.\n\n" + render_skills_section(skills),
tools=[build_read_skill_tool(skills)],
)
render_skills_section produces a ## Skills section: one ### per skill with its key, version,
when-to-use and rendered body, followed by the resource list as the exact
read_skill(key="…", path="…") calls that fetch them. read_skill is a plain BaseTool — no
approval, no side effects, reads only what the skills declared, answers a wrong key or path with a
ModelRetry naming what exists, lists a skill's resources when path is empty, and cuts a long
resource at max_chars saying so. Pass listeners= so a host's ledger sees these reads like any
other call.