Skip to main content
Some moments need specific wording: a recording notice at the start of a call, a fraud warning when a card is reported stolen, a legal disclaimer after a transaction. Responses let you declare that text once in responses.yml and have the framework deliver it exactly as written. The LLM never rewrites it.

Defining responses

Response text lives in the skill’s responses.yml:
skills/card_replace/responses.yml
Each name maps to a list of variants. At delivery the engine picks one variant: the first whose 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

Add condition 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 frontmatter utter: 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:
Rephrase applies to whichever variant is selected at delivery, not always the first variant. Do not set this on anything compliance-sensitive.

Overriding built-in messages

The bundled default skills (wrap-up, resume-after-digression, silence timeout) ship their own responses. Every responses.yml merges into one registry keyed by name, so declaring a name in your project-root file replaces the built-in one:
responses.yml
Session start is different. The bundled skill has no greeting to override. To make the agent speak first, replace the skill itself. See Start a conversation. That shared registry is also why response names are worth prefixing with the skill they belong to.

Reference

For the merge order, both interpolation forms, conditional variants, and every field, see the responses.yml reference.