{{}} interpolation syntax), see Expressions and functions.
Rich content
ABL supports multi-format output for delivering responses across different channels (web, mobile, voice, messaging platforms). TheVOICE:, RICH_CONTENT:, and ACTIONS: blocks can be attached to any RESPOND statement, COMPLETE condition, or lifecycle handler. A pure reasoning agent (no FLOW:) may also declare top-level RICH_CONTENT: and ACTIONS: blocks that attach to its generated responses.
Overview
A single response can include:- Plain text — the default
RESPONDstring. - Voice configuration — SSML markup or natural language voice instructions.
- Rich content — Markdown, Adaptive Cards, HTML, Slack Block Kit, WhatsApp, or AG-UI.
- Carousels — scrollable card collections with images and buttons.
- Interactive actions — buttons, select menus, and input fields.
- Templates — reusable named response definitions with interpolation.
Voice configuration
Voice configuration provides channel-specific voice output. TheVOICE: block can appear alongside any RESPOND.
Syntax
Voice properties
SSML example
Natural language instructions
For voice platforms that accept style instructions rather than SSML:Rich content formats
The rich-content block provides format-specific variants of a response. The runtime selects the variant matching the delivery channel.RICH_CONTENT: is the canonical block keyword; FORMATS: is the original spelling, kept as an accepted alias. The .agent.yaml format accepts rich_content: or formats: the same way. Most places a rich-content block is authored — directly under a RESPOND: (in a flow step, ON_START, HOOKS:, or an ON_ACTION/ON_ERROR handler), in a tool’s on_result:/on_error: action block, and in a COMPLETE: condition (including one with no RESPOND: at all) — accept either spelling interchangeably.Syntax
Rich content properties
Rich content supports two kinds of properties. Channel-specific string formats carry a payload written for one channel. Structured template formats define content once, and the runtime renders it for each channel. Channel-specific string formats:
Structured template formats (channel-neutral; the runtime renders them per channel):
Structured templates describe content as data, such as a carousel, table, or chart. The runtime converts each template to the native format of the target channel. Templates fall into two groups: basic templates for simple display, and data-rich templates for structured data.
Basic templates — display-only, no aggregation or layout options beyond their own items:
Data-rich templates — designed to present structured/tabular data, with extra options like
adaptive narrowing:
Named templates (in the
TEMPLATES: block) may also declare a RENDERABLES: list — customer-owned structured payloads with name, payload, optional targets (api/sdk_websocket/http_async), fallbackText, and schemaRef. Several structured templates also support collection binding — from:/template: — to render one entry per array item; see Collection binding for exactly which ones.Template modifiers
Two cross-cutting modifiers apply to structured templates:when— a conditional render guard (expression). On an agent-level rich-content block, the template renders only when the expression is truthy at reasoning finalization. Available on the rich-content block and on each structured template. See Expressions and functions for expression syntax.adaptive— onLIST,KPI, andTABLEtemplates whose items/rows are bound to a tool variable, setadaptive: trueto let the reasoning agent narrow which items/rows/columns render for the user’s request.
Collection binding
Several structured templates have an array-valued property —CAROUSEL.cards, QUICK_REPLIES itself, LIST.items, TABLE.columns and TABLE.rows, CHART.data, and FORM.fields. Each of these accepts three forms:
- A literal array — write the items directly, as shown in every static example on this page.
-
A
from/templatebinding — resolve the array from a session variable at render time, and render one entry per array item:fromnames the session/tool-result path holding the array (the{{ }}wrapper is optional — both forms resolve identically).templateis applied once per array item; inside it,{{ }}placeholders resolve against that item’s own fields merged over the outer session data — so an item shaped{name: "Equities", percentage: 45}is referenced as{{name}}/{{percentage}}, not{{item.name}}. If an array element isn’t an object (a bare number or string), it’s exposed as{{value}}instead. -
A bare string reference — when the tool result is already shaped exactly like the target items (for example, a tool that already returns
{label, value, color}objects for a chart), skiptemplate:entirely:
template is optional within a from binding — a binding with only from behaves like the bare-string form above, applied to the resolved array. options on a select-type action element (used inside FORM.fields and ACTIONS) is itself collection-bound the same way, so a select’s options list can also be tool-driven.Carousels
Carousels display a horizontal scrollable collection of cards, each with a title, subtitle, image, and action buttons.Syntax
Carousel card properties
CAROUSEL also accepts a when render guard, and cards supports collection binding — resolve the cards from a tool result instead of writing them statically:
Quick replies
Quick-reply chips give the user a small set of one-tap responses.Syntax
Quick reply properties
QUICK_REPLIES itself supports collection binding — the whole list can come from from/template — but, unlike the other structured templates, it has no when guard: the property is the raw collection, with no wrapping object to hang a render guard on.
Lists
A vertical list of items, each with an optional subtitle, image, and tap action.Syntax
List properties
List item properties
Dynamic example — one list item per branch a tool returns:
Media (image, video, audio)
IMAGE, VIDEO, and AUDIO share an identical shape — a single media item, not a collection.
Syntax
Media properties
IMAGE/VIDEO/AUDIO are each a single object — there’s no array, so collection binding doesn’t apply here. To show several images, use a LIST or CAROUSEL instead.
Files
A downloadable file attachment.Syntax
File properties
KPIs
A single metric tile — a label, a value, and an optional trend indicator.Syntax
KPI properties
KPI is a single value, not an array — there’s no collection binding here (that’s what distinguishes it from TABLE/LIST/CHART). To show multiple KPIs, author multiple KPI blocks or use a TABLE.
Tables
Columns and rows for tabular data.Syntax
Table properties
Table column properties
Dynamic example — columns declared statically, rows bound to a tool result (the two bindings are independent, so it’s common to declare
columns statically and bind only rows):
Charts
Pie, bar, or line charts, rendered as SVG on channels that support them.Currently, charts render on the Web SDK only. Color is optional — the SDK provides a default palette for dynamic charts.
Syntax
Chart properties
Chart data point properties
Dynamic example — one data point per array item a tool returns:
Interactive actions
Interactive actions add buttons, select menus, and input fields to a response. Users interact with these elements, and the agent handles the interactions viaON_ACTION blocks.
Syntax
Action element properties
Select element example
Input element example
Form submission
When theACTIONS block contains input elements, you can specify a submitLabel and submitId for the form submission button:
ON_ACTION handlers
Handle user interactions with action elements in flow steps:ON_ACTION handler properties are described below.
If a handler
RESPOND includes rich FORMATS: before terminal routing, that payload is forwarded as the fallback final channel payload. The terminal target’s own rich payload takes precedence.
Reusable handlers (ACTION_HANDLERS:)
ON_ACTION inside a flow step handles actions for that step only. To define reusable action handlers available across the whole agent, declare a top-level ACTION_HANDLERS: section. Each entry is keyed by the action element id and uses the same grammar as ON_ACTION (an optional CONDITION, shorthand RESPOND/SET/TRANSITION, or a canonical DO: block of ordered actions — SET, CLEAR, LOG, RESPOND, CALL, GOTO/TRANSITION/THEN, HANDOFF, DELEGATE, COMPLETE).
Forms
A form groups several action elements — typicallyinput fields, plus a submit control — into one submission.
Syntax
Form properties
FORM doesn’t have its own submitId — submission is handled the same way as any other ACTIONS form submission; see Form submission and ON_ACTION handlers.
Progress
A progress bar or circle for a single value against a maximum.Syntax
Progress properties
PROGRESS is a single value, not an array — there’s no collection binding here.
FEEDBACK surveys
AFEEDBACK block renders a rating/feedback card. Author it inside a RICH_CONTENT: (or FORMATS:) block.
Referencing a survey template
SetFEEDBACK_TEMPLATE to a project survey name. The prompt and type (the survey’s measure) then come from the template, so they become optional here; any copy fields you do set act as per-string overrides (useful for translation):
type, max, scoring, range, and comment_threshold are owned by the template, and setting them alongside a template is a compile error. Naming a template that doesn’t exist in the project is an error (UNKNOWN_SURVEY_TEMPLATE).
expires_after is the one exception to that rule — a numeric answer-window TTL (in seconds) that is overridable per-agent even when a template is referenced:
expires_after if set, else the template’s own TTL, else a 300-second platform default — clamped to 30–86400 seconds either way. A survey presented to the customer can only be answered within this window; a submission after it expires is rejected.
Inline FEEDBACK (deprecated)
Without a template,prompt and type are required:
Condition-based wording (<field>_rules)
To vary wording by condition — the second way to translate a survey, kept next to the source copy — add a <field>_rules list. Each rule has a when expression and the field’s replacement text (the field key, or the alias value). Rules are ordered, first match wins:
prompt, submit_label, pending_message, success_message, error_message, comment_prompt, comment_placeholder. There is no type_rules/max_rules — the measure is never rule-selectable.
Copy rules are evaluated before the customer answers, so a
when expression may reference max, language, channel, and session (as session.<name>) but not rating. An invalid condition is a compile error (INVALID_SURVEY_CONDITION).Post-transfer surveys (ESCALATE to a human agent)
When a human agent closes a conversation the bot escalated, the survey card shown on close is resolved from aFEEDBACK block — but that card renders with no bot turn running, so it can’t come from “whichever step the conversation happened to be on.” The platform instead picks one FEEDBACK block to stand for the whole agent:
- The agent-level block, if the agent declares one directly under its top-level
RICH_CONTENT:(outside anyFLOW:step) — this always wins when present. - Otherwise, a FLOW step’s own
FEEDBACKblock. Both placements compile to the same thing:FEEDBACKnested under a step’s ownRICH_CONTENT:, or under that step’sRESPOND:. The platform scans steps inFLOW:order:- Exactly one step has a
FEEDBACKblock → that one is used. - Several steps have a
FEEDBACKblock, naming the sameFEEDBACK_TEMPLATE→ treated as the same survey; the first step’s copy/wording wins. - Several steps have
FEEDBACKblocks naming different templates, or any of them is an inline (template-less) block → ambiguous. No post-transfer survey is shown at all, rather than guessing which one you meant.
- Exactly one step has a
A
FEEDBACK block’s when guard is not evaluated for this purpose — closing the conversation isn’t a turn, and when conditions over turn-scoped values (like input) would never hold outside one. A block nested deeper than the step itself — inside an ON_ACTION handler, a GATHER field, a digression, or a reasoning zone — is never a candidate; only the step’s own RICH_CONTENT:/RESPOND:-level block counts.expires_after) runs out — whichever happens first. The resulting rating is stored with the survey template, the escalating agent’s name, and its version, so it appears correctly scoped in that survey’s Feedback Insights.
Templates
Templates are named, reusable response definitions declared in theTEMPLATES: block. They support {{}} interpolation and multi-format variants.
Syntax
Template properties
Referencing templates
UseTEMPLATE(name) in any RESPOND value:
Template interpolation
Template bodies use the same{{variable_name}} interpolation and {{#if variable}}...{{/if}} conditional-section syntax as any other ABL string — see Template strings in Expressions and functions for the full syntax, including calling built-in functions inside {{}}.
Channel selection
The runtime selects the response format based on the delivery channel.
If the preferred format isn’t available, the runtime falls back through the priority chain until it finds a defined format. Plain text (
RESPOND) is always the final fallback.
Related articles:
- Expressions and functions — operators, built-in functions,
{{}}template-string syntax, and type coercion rules - Lifecycle and hooks — ON_START and hooks that support rich content
- Multi-Agent and supervisor — COMPLETE conditions with rich responses
- Data Types and utilities — types used in rich content and template fields