responses.yml and
have the framework deliver it exactly as written. The LLM never rewrites it.
Defining responses
Response text lives in the skill’sresponses.yml:
skills/card_replace/responses.yml
condition is true (YAML order), or the single
default variant with no condition when none match.
Placeholders in braces are filled from memory at the moment the message is sent.
Use {session.<skill_id>.<entry>} or {session.project.<entry>} to read any
value the skill can see, or the short {entry} form for the skill’s own values.
Interpolate only values you know are set by then: a placeholder with no value is
left in the text as written.
Conditional wording
Addcondition on a variant when the message should change with session
memory — for example, different replacement-fee copy for lost vs stolen cards.
When any variant is conditioned, exactly one variant must omit condition (the
fallback). That fallback may sit anywhere in the list.
This is different from a skill utter: trigger with when: in skill.md:
when: fires a whole response once when memory first becomes true;
condition on a variant picks which wording to deliver each time a
reference (action step, collect ask, tool outcome, confirmation prompt, etc.)
resolves that response name. Both use the same condition expression grammar.
See the responses.yml reference
for selection order, train-time validation, and unsupported keys.
Delivering a response
Four things can fire a response, and which one you reach for depends on what the wording is tied to. On skill activation, or when a value becomes true. A frontmatterutter:
trigger. This is the one that needs no ordered block:
skills/card_replace/skill.md
on: activate fires once when the skill becomes active. when: fires the first
time its condition becomes true after a memory write, so a warning tied to a
value lands the moment that value is recorded, wherever in the conversation that
happens.
Around a tool call. on_success: and on_failure: on a tool_constraints
entry deliver a response after the tool runs, and requires_confirmation: names
the approval question and the line for a declined one. See
Tool Constraints.
At a fixed point in a sequence. Ordered-block steps:
action: delivers the response and advances. utterance: on a collect: step
is the question asked for that value.
When to use a response
Reach for one when the wording is a business requirement: compliance, legal, brand-mandated disclosures. Use it when the exact words are the point. For everything else, let the LLM phrase things from your instructions. Declaring every message as a response produces an agent that sounds like a scripted phone tree, which is the thing Mantle exists to avoid. If you want declared text that still adapts to context, a response can opt into LLM rewording:Dynamic buttons
When the tap targets under a message depend on live data, setbuttons: {source: <tool_name>} on the response variant. Examples include the
shipping windows a warehouse still has open, or the last four accounts a
caller used. The engine calls that local tool at delivery time, and the tool
supplies the choices with context.set_buttons([Button(...)]).
skills/delivery/responses.yml
Button with ref writes its value to that fully qualified memory field
on tap, so a collect step advances without a follow-up set_fields call from
the LLM. A button without ref sends value (or title when value is
omitted) as plain user input. One set_buttons() call can mix both kinds.
Source tools are engine-only. They are hidden from the LLM tool inventory and
the model cannot dispatch them directly. A source tool cannot come from MCP or
carry requires_confirmation, and rasa train rejects any of those cases.
If the source tool is missing, a barge-in cancels it, it raises, or it
supplies no valid buttons, the engine still delivers the response text
without buttons. The engine drops invalid entries individually, so valid
siblings survive.
See Dynamic buttons for the
YAML and Python shape, validation rules, and failure behavior.
Overriding built-in messages
The bundled default skills (wrap-up, resume-after-digression, silence timeout) ship their own responses. Everyresponses.yml merges into one registry keyed by
name, so declaring a name in your project-root file replaces the built-in one:
responses.yml
Reference
For the merge order, both interpolation forms, conditional variants, and every field, see theresponses.yml reference.