Skip to main content
responses.yml holds named text templates the framework delivers verbatim. Use it for wording that must be exact: legal disclosures, compliance notices, regulated confirmations. Everything else should stay prose so the agent sounds natural.
skills/card_replace/responses.yml
The top-level key is responses:. Each name maps to a list of variants. At delivery the engine selects one variant from that list (see Conditional variants below). A response with a single variant and no condition always selects that single variant. An entry is either a mapping with a text key, or a plain string:

Conditional variants

Add an optional condition on a variant to gate its wording on current memory. Conditions use the same expression grammar as skill requires: and prose if: lines.

How a variant is selected

When a response is delivered, the engine evaluates variants in YAML order:
  1. Skip variants with no condition during this pass.
  2. For each conditioned variant, evaluate its condition against readable memory. The first variant whose condition is true is delivered.
  3. If none match, deliver the default variant — the one that omits condition.
  4. If every variant is unconditioned, deliver the first variant.
If evaluating a condition raises an error at delivery time (for example a comparison the current memory type cannot satisfy), that variant is skipped and evaluation continues. When no conditioned variant matches, the default is used. If there is no default and nothing can be selected (every variant is conditioned and none match, or every condition fails evaluation), delivery fails closed the same way as a missing response name: the engine utters the response name as literal text and does not rephrase. metadata.rephrase applies to whichever variant is selected, not always the first variant.

Default variant rules

When any variant on a response has a condition, exactly one variant must omit condition. That variant is the fallback when no condition matches. It may appear anywhere in the YAML list — first, last, or between conditioned variants. The engine skips default variants while evaluating conditions, then delivers that single fallback variant. rasa train rejects:
  • empty or non-list response entries (must be a non-empty variant list)
  • empty or whitespace-only text on any variant
  • invalid or empty condition syntax (must be a string expression)
  • memory references in a condition that are not declared in skill or project memory.yml
  • conditioned variants with no default, or more than one default
Train errors during project validation (rasa data validate, packaging): All project responses.yml files are checked; findings accumulate across files. Runtime model load (packaged agent) still uses strict registry parsing. Unknown variant keys there raise ValidationError with code responses.registry.invalid_variant and stop loading that file.

Unsupported variant keys

Only text, metadata, condition, and buttons are permitted on a variant. channel: is not supported in this release. Adding it is reported at project validation time and causes strict registry load to fail at runtime. See Dynamic buttons for the accepted buttons: shape.

Dynamic buttons

Use a local source tool when the tap targets are only known at delivery time. The variant names the tool instead of listing choices inline:
skills/delivery/responses.yml
The source tool builds the buttons and hands them back with context.set_buttons():
skills/delivery/tools.py
Button fields decide the tap payload: One set_buttons() call may mix memory-writing (ref) and plain-text buttons, as in the example. Calling set_buttons() again inside the same tool invocation replaces the previously supplied list. Messages the tool emits with context.send() remain separate and carry no buttons. Source tools are internal to the engine:
  • the engine omits them from the LLM tool schemas, so the model cannot dispatch them directly
  • rasa train requires each source to resolve to a local tool for the owning skill, or for every skill that references a project-root response
  • rasa train rejects MCP tools and tools with requires_confirmation as sources
At delivery, the engine still sends the response text when the source is unavailable. This covers:
  • the source tool is missing at runtime
  • a barge-in cancels the invocation, it times out, or it raises
  • the tool returns no buttons, or every button it returned is invalid
The engine drops invalid entries individually, so valid siblings survive. A ref that is unknown or not writable by the active skill counts as invalid. The engine logs it and skips the button rather than propagating the error.
A variant’s buttons: value is either a static list or a {source: ...} mapping, never both. Put mixed static and runtime choices in the source tool’s single set_buttons() call.

Where the file lives

At load, every file is merged into one flat registry, later sources overriding earlier ones on duplicate names:
  1. Packaged system defaults
  2. Each bundled default skill’s responses.yml
  3. Each project skill’s responses.yml, in directory order
  4. The project-root responses.yml
This is how you override a built-in message: declare the same response name in your own file. Redefining utter_silence_timeout_check_in, for example, replaces the check-in the bundled default_silence_timeout skill delivers. The bundled default_session_start skill has no greeting response. To greet, override the skill as described in Start a conversation. Because the registry is flat, response names are global rather than scoped to the skill that declares them. Prefix names with the skill (utter_card_replace_completed) so two skills keep their wording distinct.

Interpolation

A placeholder in braces is replaced with a memory value at delivery time. Two forms work, and they resolve differently:
Use the namespaced form to read another skill’s public field. The bare form is convenient for a skill’s own entries and follows the same resolution order a tool’s context.memory.get() uses. A placeholder whose value is unset at delivery time is left in the text as written, so interpolate only values you know are set by the time the response is sent, typically ones written earlier in the same ordered block. Interpolation applies to responses. For live values in skill.md instruction prose, use @memory.… instead — see skill.md. Do not put bare session.* in free prose; train fails and points you at @memory.

metadata.rephrase

A response may opt into being reworded by the LLM instead of delivered verbatim:
With rephrase: true the orchestrator makes a tool-free LLM call to reword the selected variant’s text in context, and falls back to the literal text if that call fails or comes back empty. Leave it off (the default) for anything compliance-sensitive: that is the entire point of declaring the response.

How a response is delivered

Four things reference a response by name: The two step fields differ:
utter: is accepted as a step-level alias for action: and is normalised at load. Prefer action:. rasa train validates every response name referenced from any of the four places above, and runs per-variant checks on project-authored responses.yml files (non-empty text, valid conditions, default-variant rules). A name that is not in the registry, or a malformed variant list, is caught before the agent runs.

See also