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

Unknown keys (for example a descripton: typo) fail compile with skill.markdown.unknown_frontmatter_key. name and description must be YAML strings — a number or boolean is rejected the same way a non-boolean hidden is. Write description as a routing summary, including the phrasings a customer would actually use. Skill retrieval embeds the skill name and this text (and, by default, the skill’s memory field schema). Skill-level requires is not supported. Use precondition when another skill must run first, hidden: true to keep a skill off the routing catalogue, and tool_constraints requires: to gate tools.

precondition

A semantic name. Bind it in agent.yml (orchestrator.preconditions) so the engine parks this skill, runs resolve_with, then continues or abandons. When resolve_with is already running, the engine reuses that skill instead of interrupting it.

hidden

When true, the skill is omitted from the routing catalogue. Deterministic call: / link: steps and a precondition resolve_with may still start it. Prose @skill mentions of a hidden skill fail validation.

complete_when

Skill-level completion contract. Must be a mapping with at least one of:
Prefer one check per list item — a single duty you can verify from the conversation (a tool ran, a required question was asked, and so on). Several short items work better than one long sentence that combines duties with “and” or “or”. Put memory logic in condition:; keep criteria for things that need reading the conversation. Duplicate strings are rejected at compile time. When a check fails, the tool error can name that item so the agent knows what to do next. Bare strings are rejected at compile time. Skill-level complete_when also requires an autonomous or hybrid skill (one with LLM-driven prose steps). Fully controlled ordered-block-only skills complete via block END without this gate — remove skill-level complete_when, or add an autonomous/hybrid step and exit via complete_skill. When authored:
  • complete_skill is hard-gated: unmet condition or failed criteria return a tool error and leave the skill active.
  • While an ordered block is active, complete_skill is not offered. The engine finishes the block via END; a hybrid skill offers complete_skill again after returning to prose.
  • While a tool confirmation is pending for the active skill, complete_skill is refused — the LLM needs to resolve the tool confirmation first.
  • cancel_skill is never gated by complete_when.
  • Condition only (no criteria): the skill auto-completes when the condition becomes true.
  • Non-empty criteria: auto-complete is disabled; exit only via a successful complete_skill (condition + judge). The judge fails closed: if the check times out, errors, or returns a malformed answer, complete_skill is refused (the skill stays active) rather than treated as complete.
Step-level complete_when on ordered-block instructions: steps is a plain condition string — no mapping, no criteria. If you want to ensure that certain ordered blocks finished before a skill completes, add the corresponding criteria.

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.
on: activate delivers the response after this skill’s memory is reset for the new run. Conditional variants that read this skill’s fields see initial_value (or null) — not a previous run’s values — so they typically fall through to the default variant. Prefer project memory or a matching initial_value for activation CRVs; use when: for skill values written after activation. See Conditional variants.

disabled

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

always_include_in_prompt

When skill retrieval is on, this skill’s startable entries stay in the routing catalogue and activate list even if search did not retrieve it. Default is false. hidden, disabled, and engine-managed still win: an always-included skill is omitted when it is not in the legal routing catalogue. This is a skill-level flag only — not an ordered-block property.

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. Incomplete lookalikes (@skill, @block.foo.bar, @tool.lock.card) cannot be substituted — rasa train fails. Use exactly one segment after the prefix. You can name a tool in plain prose or with @tool.<name> when you want the exact callable spelled in the instructions. rasa train checks @tool tokens in two steps: token shape must be exactly one segment after @tool. (see incomplete lookalikes above), and the name must resolve to a tool visible while this skill is active (skill-local and declared shared @tool functions, MCP imports, and engine framework tools such as set_fields). A shape or name mistake fails train instead of leaving literal @tool… text at runtime.

How the parent is parked

The engine parks the current skill differently depending on what put the new one on top: A @skill. target must not be a hidden skill. 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. Hits are sections of the file, not the whole document. 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, chunking, and the embedding model.

See also