> ## Documentation Index
> Fetch the complete documentation index at: https://rasa.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# skill.md

> Complete reference for the skill file: frontmatter properties and body format.

`skill.md` is the only required file in a skill folder. It is YAML frontmatter
followed by a markdown body.

```markdown skills/card_replace/skill.md theme={null}
---
name: Card Replace
description: Replace a credit card -- lost, stolen, damaged, or not received
requires: "session.project.authenticated"
complete_when: "session.card_replace.replacement_submitted"

import_tools:
  - get_customer_info
  - mcp/banking:get_balance

tool_constraints:
  - lock_card:
      requires: "session.card_replace.selected_card_id"
---

Help the customer replace a credit card.
```

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

| Property | Required | Type | Description |
| - | - | - | - |
| `name` | Yes | string | Human-readable display name |
| `description` | Yes | string | What the skill does. The orchestrator routes on this. |
| `requires` | No | condition string | Memory condition gating whether the skill can be activated |
| `complete_when` | No | condition string | Condition that marks the skill finished |
| `import_tools` | No | list of strings | Shared project-root tools and/or `mcp/<server>:<tool>` imports |
| `tool_constraints` | No | list | Per-tool gating, confirmation, and outcome rules |
| `utter` | No | list | Verbatim responses fired on skill activation or a memory condition |
| `disabled` | No | bool | Exclude the skill from routing without deleting it |

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](https://rasa.com/docs/mantle/reference/conditions.md) string checked before the orchestrator
offers the skill:

```yaml theme={null}
requires: "session.project.authenticated"
```

### `complete_when`

A condition that, once true, marks the skill's prose flow complete:

```yaml theme={null}
complete_when: "session.card_replace.replacement_submitted"
```

While an [ordered block](https://rasa.com/docs/mantle/build-guide/ordered-blocks.md) 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:

| Form | Meaning |
| - | - |
| `get_customer_info` | A shared `@tool` from the agent-root `tools/` folder |
| `mcp/<server-id>:<tool-name>` | One tool from an MCP server declared under [`mcp_servers`](https://rasa.com/docs/mantle/reference/integrations-yml#mcp_servers) |

```yaml theme={null}
import_tools:
  - get_customer_info           # from tools/*.py at the agent root
  - mcp/banking:get_balance     # remote MCP tool on server id `banking`
```

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](https://rasa.com/docs/mantle/concepts/tools.md#mcp-tools) for a short end-to-end example.

### `tool_constraints`

A list of single-key mappings, tool name to options:

```yaml theme={null}
tool_constraints:
  - lock_card:
      requires: "session.card_replace.selected_card_id"
  - process_card_replacement:
      requires: "session.card_replace.order_confirmed == True"
      requires_confirmation:
        enabled: true
        utter_for_confirmation: utter_ask_replacement_confirmation
      on_success: utter_replacement_disclaimer
```

| Option | Description |
| - | - |
| `requires:` | Condition string. The tool is hidden from the LLM and blocked at dispatch until it holds. |
| `requires_confirmation:` | Mapping. `enabled: true` pauses the call for explicit approval, resumed via `resolve_tool_confirmation`. Optional `utter_for_confirmation` / `utter_on_user_denial` name responses; omit them to let the LLM phrase the ask. |
| `on_success:` | Response name delivered after the tool succeeds. |
| `on_failure:` | Response name delivered after the tool fails. |

See [Tool Constraints](https://rasa.com/docs/mantle/build-guide/tool-constraints.md) 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:

```yaml theme={null}
utter:
  - utter_recording_notice:
      on: activate
  - utter_stolen_warning:
      when: "session.card_replace.replacement_reason == 'stolen'"
```

| Trigger | Fires |
| - | - |
| `on: activate` | When the skill becomes active |
| `when:` | The first time the condition becomes true after a memory write |

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`

```yaml theme={null}
disabled: true
```

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:

```markdown theme={null}
if: session.card_replace.replacement_reason == 'stolen'
Tell the customer the card will be locked for their protection.
```

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:

| Token | Meaning |
| - | - |
| `@memory.project.<field>` | Project memory |
| `@memory.<skill_id>.<field>` | That skill's field under normal access rules |

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:`](https://rasa.com/docs/mantle/reference/prompt-templates) and
[`### Project Memory`](https://rasa.com/docs/mantle/reference/prompt-templates) 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:

```markdown theme={null}
Use the already chosen card @memory.card_replace.selected_card_id for this request.
```

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](https://rasa.com/docs/mantle/reference/responses-yml.md).

### Jumps to blocks and skills

| Token | Target | Behaviour |
| - | - | - |
| `@block.<block_id>` | An ordered block in **this** skill | Enters the block, returns to prose when it completes |
| `@skill.<skill_id>` | Another skill | Runs it and returns; parent resumes |

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:

| Trigger | Parked as | Resumes |
| - | - | - |
| `@skill.<skill_id>` | Delegation | Automatically, when the referenced skill finishes |
| `@block.<block_id>` | Same-skill reference | Automatically, at the prose that entered the block |
| The user changing topic | Digression | The engine offers a resume and the customer decides |

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

```
:::ordered_block id=pick_card
steps:
  - id: check_eligibility
    execute_tool: check_account_replacement_eligibility
  - id: END
:::
```

The id is a **fence attribute**. Duplicate ids in one file are an error, as is
setting `routable:` inside the block. See
[Ordered Blocks](https://rasa.com/docs/mantle/build-guide/ordered-blocks.md) 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:

| Removed key | Use instead |
| - | - |
| `force_collection` | `ask_before_filling: true` |
| `tool:` on a collect step | Record values via the `set_fields` framework tool, optionally with a `run_after_setting_<field>` post-write hook in `tools.py`, or add a separate `execute_tool` step |

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:

| Body | Result |
| - | - |
| Prose only | One routable prose flow |
| Prose + blocks | Prose is routable; blocks are internal, entered via `@block.` |
| Blocks only | Blocks are routable; a block named `main` is the entry point |

## Companion files

| File | Purpose |
| - | - |
| `memory.yml` | [Memory schema](https://rasa.com/docs/mantle/reference/memory-yml.md) for this skill |
| `responses.yml` | [Response templates](https://rasa.com/docs/mantle/reference/responses-yml.md) |
| `tools.py` or `tools/*.py` | [Tools](https://rasa.com/docs/mantle/reference/tools.md), auto-discovered |
| `references/*.md` | Knowledge indexed for `search_knowledge` |

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](https://rasa.com/docs/mantle/concepts/references.md) for the retrieval flow and the embedding
model.

## See also

* [Conditions](https://rasa.com/docs/mantle/reference/conditions.md): the expression grammar
* [Ordered Blocks](https://rasa.com/docs/mantle/build-guide/ordered-blocks.md): step types and branching
* [References](https://rasa.com/docs/mantle/concepts/references.md): how knowledge is indexed and searched
* [Import MCP tools](https://rasa.com/docs/mantle/concepts/tools.md#mcp-tools): allowlisting remote tools
* [`integrations.yml`](https://rasa.com/docs/mantle/reference/integrations-yml#mcp_servers): MCP server config


## Related topics

- [Skills](https://rasa.com/docs/mantle/skills.md)
- [Authoring files](https://rasa.com/docs/mantle/reference/schemas/index.md)
- [Skills & Routing](https://rasa.com/docs/mantle/orchestration/skills.md)
- [Rasa MCP Tools API Reference](https://rasa.com/docs/reference/api/rasa-mcp-tools.md)
- [Instructions](https://rasa.com/docs/mantle/concepts/instructions.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.