Skip to main content
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
The file must open with --- 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:
While an ordered block is active, the engine does not offer 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 in the skill’s own 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_tools entries resolve to the same bare name (for example mcp/a:echo and mcp/b:echo, or mcp/banking:get_balance next to a local get_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 named get.balance cannot be imported, even though the server id may contain dots.
Connecting, authenticating, or listing tools for a referenced server happens when the agent starts, not at train time. If that fails, startup stops. The same 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

The skill loads but is excluded from routing. Useful for parking a work-in-progress skill without deleting it.

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:
The marker scopes only the paragraph directly beneath it, up to the next blank line. Write one 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:
Keep 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

The id is a fence attribute. Duplicate ids in one file are an error, as is setting 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

A skill.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_knowledge is offered at all depends on the project having an index, not on the active skill having a references/ folder.
  • Each retrieved snippet reaches the model with its source file path and a scope of either global or 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 References for the retrieval flow and the embedding model.

See also