skill.md is the only required file in a skill folder. It is YAML frontmatter
followed by a markdown body.
skills/card_replace/skill.md
--- on the first line and close the frontmatter
with a second ---. A missing delimiter fails with
skill.markdown.invalid_frontmatter_delimiter.
The skill id is the folder name
The skill id — used for routing, memory namespacing, and@skill. references — is always the parent directory name, never the name: field.
A skill in skills/card_replace/ with name: Card Replace is:
- referenced in prose as
@skill.card_replace - namespaced in memory as
session.card_replace.<entry>
Frontmatter properties
Write
description as a routing summary, including the phrasings a customer
would actually use. It is the only thing the orchestrator matches against.
requires
A condition string checked before the orchestrator
offers the skill:
complete_when
A condition that, once true, marks the skill’s prose flow complete:
complete_skill to the model. The engine finishes the
block via END, and a hybrid skill offers complete_skill again after
returning to its prose. If the model still calls complete_skill mid-block,
the engine refuses the call with a tool error. cancel_skill remains available throughout.
import_tools
Allowlist of tools this skill may use beyond its own auto-discovered
tools.py / tools/*.py. Each entry is either:
tools.py or tools/*.py are auto-discovered and must
not be listed. A shared or MCP tool that is not declared here is not attached to
the skill — there is no auto-import of every tool on a server.
For an MCP entry, the LLM and tool_constraints see the bare tool name
(get_balance). Name it in prose the same way you name a local tool. The
mcp/<server>: prefix is only for the allowlist and for resolving which server
to call.
Collisions and load failures
Within one skill, bare names must be unique across local tools, shared imports, and MCP imports.rasa train fails when:
- Two
import_toolsentries resolve to the same bare name (for examplemcp/a:echoandmcp/b:echo, ormcp/banking:get_balancenext to a localget_balance) - An MCP entry references a server id missing from
integrations.yml - An MCP tool name is not a valid LLM function name. The tool name is sent to
the model as-is, so it may only contain letters, digits,
_, and-(at most 64 characters). A remote tool namedget.balancecannot be imported, even though the server id may contain dots.
mcp/<server>:<tool> string may appear in more than one skill. Each
skill that needs the tool must list it in its own import_tools.
To share a Python tool across skills, define it once in the agent-root tools/
folder and declare it in each skill’s import_tools.
See Import MCP tools for a short end-to-end example.
tool_constraints
A list of single-key mappings, tool name to options:
See Tool Constraints for worked examples.
An entry that is not a single-key mapping, or whose options are not a mapping,
fails with
skill.markdown.invalid_tool_constraints.
utter
Fires a verbatim response from responses.yml on a skill lifecycle event or
when a memory condition first becomes true:
An entry may set both. These are distinct from a tool’s
on_success: /
on_failure:, which attach to a gated tool in tool_constraints.
disabled
Body format
The body is markdown. It supports:Prose paragraphs
Always visible to the LLM. Blank lines separate paragraphs; each paragraph is a scoping unit.if: markers
A paragraph whose first line is if: <condition> is included only when the
condition holds:
if: paragraph per case; for exclusive either/or branching, use
an ordered block’s next: branches.
Memory in prose (@memory)
To put a live memory value into LLM-facing instructions, use:
Access matches what the active skill can read elsewhere: project fields; other
skills’ public fields; this skill’s public and private fields. Another
skill’s private fields and
@memory.system.* are rejected at train time.
At prompt build the engine replaces the token with the current value (same
stringification as the ### Memory: and
### Project Memory listings). Unset
or unreadable references are left as the literal token and logged. For a set
PII field, the whole token is replaced with [set] (for example
Contact @memory.project.phone becomes Contact [set]). That differs from
the Project section, which lists project.phone: [set] as a line.
Incomplete forms such as @memory.project (missing the field segment) cannot
be substituted — rasa train fails.
Example:
session.* for if: conditions and structured YAML (for example tool
parameters). Putting bare session.* in free prose also fails train — use
@memory.… instead. Response templates still use {session.*} — see
Responses.
Jumps to blocks and skills
Both take the id:
@block. names a block declared in the same file, and
@skill. names the target skill’s directory name.
Tools are named in plain prose rather than with a token, since they are already
in the model’s schema while the skill is active.
How the parent is parked
The engine parks the current skill differently depending on what put the new one on top:
A
@skill. target is offered only while its own requires: condition holds.
Public memory is readable across skills in every case, so the difference is
control rather than data: the first two are part of the skill’s design, the
third is the customer steering the conversation.
Ordered blocks
routable: inside the block. See
Ordered Blocks for step types.
Steps inside ordered blocks use the same YAML schema as top-level flow steps.
Unknown step properties are rejected at skill load and during
rasa data validate — misspelled or obsolete keys fail with a clear error
instead of being silently ignored.
Two removed collect-step keys have dedicated migration messages:
Any other unrecognized step property produces a generic validation error naming
the unknown key.
A skill needs prose or a block
Askill.md with neither prose instructions nor an ordered block fails with
skill.markdown.missing_instructions.
The combination determines routability:
Companion files
Each of the first three is scoped to the skill.
memory.yml declares entries
under this skill’s namespace, tools.py is attached to this skill, and
responses.yml merges into the shared registry under the names it declares.
references/ is project-wide
skills/<id>/references/**/*.md is indexed into the same index as the
agent-root references/ folder, and search_knowledge queries that whole index
on every call whatever skill is active. The folder location decides where the
files sit in your project, and the index a search reads is the same either way.
Two consequences worth designing around:
- Whether
search_knowledgeis offered at all depends on the project having an index, not on the active skill having areferences/folder. - Each retrieved snippet reaches the model with its
sourcefile path and ascopeof eitherglobalor the owning skill id. The model can tell where a chunk came from, so write each document to stand on its own rather than relying on the skill it sits under to supply the context.
See also
- Conditions: the expression grammar
- Ordered Blocks: step types and branching
- References: how knowledge is indexed and searched
- Import MCP tools: allowlisting remote tools
integrations.yml: MCP server config