Skip to content

Skills

Agent skills framework for structured tool definitions. See the Agent Skills guide for usage examples.

Core

Skill dataclass

Skill(metadata, instructions, path)

Full skill definition including instructions body.

list_scripts

list_scripts()

List script filenames in the skill's scripts/ directory.

list_references

list_references()

List reference filenames in the skill's references/ directory.

read_reference

read_reference(filename)

Read a reference file by name.

Raises:

Type Description
SkillPathError

If filename is not a plain name inside the skill's references/ directory. Subclasses ValueError.

FileNotFoundError

If the reference file does not exist.

resolve_script

resolve_script(script_name)

Resolve a script inside the skill's scripts/ directory.

ScriptExecutor implementations should build their command from this rather than joining the name themselves. Execution policy — sandboxing, timeouts, allowed interpreters — stays the integrator's call, but which file gets run should not depend on each integrator repeating the same containment check.

Raises:

Type Description
SkillPathError

If script_name is not a plain name inside the skill's scripts/ directory. Subclasses ValueError.

FileNotFoundError

If the script does not exist.

SkillMetadata dataclass

SkillMetadata(name, description, license=None, compatibility=None, allowed_tools=None, extra_metadata=dict())

Lightweight metadata parsed from SKILL.md frontmatter.

allowed_tools class-attribute instance-attribute

allowed_tools = None

Tool access patterns this skill gates (RFC §24.2).

Both frontmatter forms are accepted: a YAML list, which is what the RFC's own example uses, and a comma-separated scalar. Entries are ToolPolicy globs, so search_* covers every tool whose name starts with search_.

required_tool_names property

required_tool_names

Exact prerequisite names from the optional requires metadata.

These describe dependencies, not grants. Applications resolve skill availability against their authorized catalogue before using it.

gated_tool_names property

gated_tool_names

The patterns this skill gates, one per entry.

Accepts either frontmatter form. Returns an empty list when the field is None or blank. The entries are patterns, not names — match them with :meth:gates, never with in.

gates

gates(tool_name)

Whether this skill gates tool_name (RFC §24.2 glob semantics).

Uses the same fnmatch matching as :class:~roomkit.tools.policy.ToolPolicy, so a skill and a policy agree on what a pattern covers.

ScriptResult

Bases: BaseModel

Result of executing a skill script.

SkillRegistry

SkillRegistry(*, requires_match=serves_exactly)

Discover, load, and manage Agent Skills.

Lightweight metadata is parsed on discover/register. Full skill instructions are loaded lazily on first get_skill() call and cached for subsequent access.

requires_match says whether a skill's requires name is served by the tools a conversation offers (RFC §24.3): an exact tool name by default, a host's own reading otherwise (a hub served by any tool named <hub>_*, say). Every door checks requires through it.

has_served property

has_served

Whether a server's skills were registered here.

execution_tool_patterns property

execution_tool_patterns

The patterns of the host's tools that run code, named when its servers were registered (execution_tools): gated like the sandbox's while a served skill is active (RFC §24.7).

listed_names property

listed_names

Names of registered skills that the prompt manifest shows.

unavailable_skills property

unavailable_skills

Mapping of unavailable skill name -> reason.

skill_names property

skill_names

Names of all registered skills.

skill_count property

skill_count

Number of registered skills.

has_entries property

has_entries

Whether the model has anything to be told: a skill it can activate, or one marked unavailable, whose reason it gives instead of guessing.

discover

discover(*directories, strict=True)

Scan directories for subdirectories containing SKILL.md.

A malformed skill is a deployment error, not a runtime condition, so the default is to stop. Skipping it instead removes the skill from the catalogue while the agent keeps answering: the model is never told the capability is missing, and neither is anyone reading the conversation. The failure surfaces hours later as an agent that quietly cannot do something it was configured to do.

Discovery commits only once every candidate has parsed, so a strict failure leaves the registry exactly as it was rather than half filled.

Parameters:

Name Type Description Default
directories str | Path

Directories to scan. Only immediate subdirectories containing a SKILL.md are considered.

()
strict bool

Stop on the first unreadable directory or invalid skill. Set False to log and skip each instead — appropriate when skills come from a source you do not control.

True

Returns:

Type Description
int

The number of skills registered.

Raises:

Type Description
SkillDiscoveryError

In strict mode, when a directory cannot be scanned or a candidate escapes it through a symlink.

SkillParseError

In strict mode, when a SKILL.md cannot be parsed.

SkillValidationError

In strict mode, when metadata is invalid.

register

register(skill_dir)

Register a single skill directory.

Parses frontmatter only (lightweight). Replaces any existing skill with the same name.

Raises:

Type Description
SkillParseError

If SKILL.md cannot be found or parsed.

SkillValidationError

If metadata fails validation.

add

add(skill)

Register a skill built in memory (a store, a marketplace), replacing any earlier one of the same name (RFC §24.3).

Its path is where its scripts and references are read from. Adding it clears its marks, as registering a directory does. Logged at debug: a store adds its whole catalogue at once, and says so itself.

Raises:

Type Description
SkillValidationError

The name holds :, which only a skill an MCP server serves is named with (RFC §24.7).

copy

copy(names=None, *, marks=True)

A registry of names, every registered skill when None (RFC §24.3).

It finds every skill the source finds, one discovered but not yet loaded included: each name keeps its path and, once loaded, its skill. With marks, the copy keeps the source's unlisted marks and the unavailable reasons of the names it copies; without, every skill it copies is available and listed (a hand-picked set, say), and a name the source marks unavailable is not copied at all, nor asked of its server.

add_mcp_server async

add_mcp_server(server, label, *, allow_dynamic=False, approve_execution=None, execution_tools=())

Register the skills an MCP server serves, under label (RFC §24.7).

Only the listing is read: a skill's SKILL.md is fetched when it is activated, and checked then against the entry the listing gave. Each skill is named <label>:<path> (hf:hf-cli). An entry the extension does not allow, one past its limits, and a dynamic one unless allow_dynamic are registered as unavailable with their reason, the server's other skills staying usable. Registering a server again replaces its entries: a skill it no longer offers, or offers in a form this host does not take, retires; one it still offers keeps, while loaded, the entry it was loaded from.

Parameters:

Name Type Description Default
server MCPSkillServer

The connected server (:class:~roomkit.tools.mcp.MCPToolProvider).

required
label str

The host's name for the server: an identifier, never the name the server gives itself.

required
allow_dynamic bool

Take skills whose content the server generates, which nothing can verify.

False
approve_execution ApproveExecution | None

Asked before a tool that runs code is served while one of this server's skills is active; without it, such a call is refused.

None
execution_tools Sequence[str]

Patterns of the host's own tools that run code, gated like the sandbox's.

()

Returns:

Type Description
int

How many of the server's skills are available.

Raises:

Type Description
SkillValidationError

label is not an identifier, or names another server already.

SkillDiscoveryError

The server does not declare the extension, or its listing cannot be read.

load_skill async

load_skill(name)

The skill name, loaded: what an activation reads (RFC §24.4).

A host's own skill is parsed as :meth:get_skill parses it. A skill an MCP server serves is read from its server now, and checked against its entry, unless it is loaded already: it then keeps the entry it was loaded from (RFC §24.7). A <label>:<path> the listing did not hold, under a label this registry registered (not a copy narrowed to some names), is asked of its server with skills/get and kept out of the manifest. A skill marked unavailable is never loaded, one loaded before included, nor one the catalog this registry shares no longer holds, or holds in a form this host does not take.

Raises:

Type Description
SkillError

A served skill that is not found on its server, or whose content does not match its entry; the message names the skill only.

read_file async

read_file(name, filename)

The text of filename, a file of skill name (RFC §24.6, §24.7).

A host's own skill reads a reference by name, confined to its references/ directory. A served skill reads a path inside the skill (templates/invoice.md) that the entry it was loaded from lists, from its own server, checked against that entry; it must be loaded (activated) first.

Raises:

Type Description
KeyError

No skill name is loaded or found here.

SkillPathError

The name escapes the skill, or is not one of a served skill's files (nothing is read then).

FileNotFoundError

A host's skill has no such reference.

SkillChangedError

A served skill's file failed verification, or the catalog this registry shares no longer holds the skill in a form this host takes: the skill is retired here and must be activated again.

is_served

is_served(name)

Whether name is, or could be, a skill an MCP server serves.

origin

origin(name)

The server that serves name, or None for a host's own skill.

get_metadata

get_metadata(name)

Get metadata for a skill by name.

get_skill

get_skill(name)

Get full skill (with instructions), loading lazily if needed.

mark_unavailable

mark_unavailable(name, reason)

Record a skill that exists but cannot be used in this context.

The skill leaves the available set entirely — skill_names, all_metadata() and get_skill() no longer see it — and only surfaces through unavailable_skills, the prompt manifest and the skill-tool error paths, so callers can say WHY it is missing instead of leaving a silent gap (e.g. a requires gate dropping a skill whose tools are not granted in this execution context).

What it gated stays closed: no activation can open it any more (:meth:gated_tool_names).

gated_tool_names

gated_tool_names(activated=())

The tool patterns skills keep closed (RFC §24.2): those of every registered skill not in activated, and the closed ones (:meth:closed_tool_names). Both doors read skill gating through this one rule.

closed_tool_names

closed_tool_names(activated=())

The tool patterns only skills marked unavailable gate, which nothing can open (RFC §24.2). A pattern a skill in activated gates is that skill's to open: an unavailable skill gating the same pattern does not hold it closed.

unopenable_tool_names

unopenable_tool_names()

The closed patterns no available skill gates as well: no activation can open them (RFC §24.2).

mark_unlisted

mark_unlisted(name)

Keep name activatable but out of the prompt manifest.

The third visibility state, between available and unavailable: the skill stays registered — activate_skill, get_skill() and skill_names still see it — but to_prompt_xml() and listed_names do not. For catalogues where advertising every entry would drown the ones that matter: a host can keep quiet about a skill while any path that names it (a recommender nudge, a user asking for it) still activates it, which is what lets it earn its listing back.

Unknown names are ignored — there is nothing to hide.

get_unavailable_reason

get_unavailable_reason(name)

Reason a skill is unavailable in this context, or None.

all_metadata

all_metadata()

Return metadata for all registered skills.

to_prompt_xml

to_prompt_xml()

Generate spec-compliant XML block.

Skills marked unavailable are listed in a separate <unavailable_skills> block with their reason, so the model can explain the gap instead of guessing at a name that will answer "not found". Skills marked unlisted are simply absent — activatable, but not advertised. Content is HTML-escaped to prevent injection.

ScriptExecutor

Bases: ABC

Execute skill scripts with integrator-defined policy.

No default implementation is provided — the execution policy (sandboxing, timeouts, allowed interpreters) is always the integrator's responsibility.

execute abstractmethod async

execute(skill, script_name, arguments=None)

Run a script from a skill's scripts/ directory.

Parameters:

Name Type Description Default
skill Skill

The skill that owns the script.

required
script_name str

Filename of the script to run.

required
arguments dict[str, str] | None

Optional key-value arguments to pass.

None

Returns:

Type Description
ScriptResult

Result of the script execution.

close async

close()

Release resources. Override in subclasses that hold connections.

RunSkillScriptTool

RunSkillScriptTool(skills, executor)

Runs a skill's script, as run_skill_script, with executor.

Satisfies :class:roomkit.tools.Tool: the schema every channel declares and the handler every channel calls. A script outside its skill answers an error; a skill the registry does not offer refuses the call (:class:~roomkit.core.exceptions.ToolRefusedError), as on any channel.

name class-attribute

name = TOOL_RUN_SCRIPT

The name every channel declares and calls it under.

definition property

definition

The run_skill_script schema.

handler async

handler(name, arguments)

Run the script arguments names; the script's result as JSON.

Served over MCP

Skills an MCP server publishes under the MCP Skills extension. See the Skills over MCP guide.

MCPSkill dataclass

MCPSkill(metadata, instructions, path, origin='', entry=None, files=dict(), read_total=0, retirements=0)

Bases: Skill

A skill whose SKILL.md an MCP server served and the registry verified.

It holds the entry it was loaded from for as long as it is loaded: every file it reads is one that entry lists, checked against it. Nothing of it is on disk: its files are read through :meth:~roomkit.skills.SkillRegistry.read_file, never through path, and it has no script a host can run.

origin class-attribute instance-attribute

origin = ''

The host's label for the server that serves it.

entry class-attribute instance-attribute

entry = None

The entry it was loaded from.

files class-attribute instance-attribute

files = field(default_factory=dict, repr=False)

The bytes of each file read so far, verified, by URI.

read_total class-attribute instance-attribute

read_total = 0

Bytes read so far: what bounds a dynamic skill, which lists no sizes.

retirements class-attribute instance-attribute

retirements = field(default=0, repr=False)

How many times a failed check had retired its name when it was loaded: a later one retires it in every registry holding it.

held property

held

The entry it was loaded from.

list_scripts

list_scripts()

None: a served skill's files are never run (RFC §24.7).

list_references

list_references()

Every file its entry lists but SKILL.md, as a path inside the skill; none for a dynamic skill, which lists nothing.

read_reference

read_reference(filename)

Refused: a served skill's file is read from its server, verified, through :meth:~roomkit.skills.SkillRegistry.read_file.

resolve_script

resolve_script(script_name)

Refused: a served skill's files are never written to disk to be run.

SkillExecutionRequest dataclass

SkillExecutionRequest(name, label, uri, fingerprint, room_id, tool)

What a host is asked before a tool that runs code is served while a skill an MCP server serves is active in the conversation.

Attributes:

Name Type Description
name str

The skill's name in the registry (hf:hf-cli).

label str

The host's label for the server that serves it.

uri str

The URI of the skill's SKILL.md on that server.

fingerprint str | None

The SHA-256 of the skill's files' URIs and digests, sorted (:attr:SkillEntry.fingerprint): an approval holds for it only. None for a dynamic skill, which no approval can bind.

room_id str | None

The conversation the skill is active in.

tool str

The tool about to run code.

ServedOrigin dataclass

ServedOrigin(label, server, allow_dynamic, approve_execution, execution_tools)

A server a host registered skills from, under the host's label for it.

MCPSkillServer

Bases: Protocol

The three things a registry asks of a connected MCP server.

:class:~roomkit.tools.mcp.MCPToolProvider is one. Nothing here relies on a session: a skill's identity, the entry it was loaded from and the content verified are the registry's, so a transport without a session serves them alike.

server_extensions property

server_extensions

The extensions the server declared when it connected, by identifier.

request async

request(method, params=None)

Send a request the provider does not model; its result as the server sent it.

read_resource async

read_resource(uri)

The server's resources/read result for uri.

SkillChangedError

SkillChangedError(name)

Bases: SkillVerificationError

A file of an active served skill failed verification: the skill is retired, its entry read again, and it must be activated again.

SkillVerificationError

Bases: SkillError

Content an MCP server served for a skill does not match its entry: a length, a digest, a frontmatter, or a file the entry does not list (RFC §24.7). The content is not used.

Exceptions

SkillParseError

Bases: SkillError

Failed to parse SKILL.md content.

SkillValidationError

Bases: SkillError

SKILL.md metadata failed validation.