Conversation node

Conversation node is the most frequently used node type. Its purpose is to engage in dialogue with the user. It doesn’t trigger any backend functions or perform external actions. Rather, it focuses solely on user-agent interaction.

Key points:

Operation mode

Conversation node may operate in one of the following modes:

Instructions

You may use variables in conversation node instructions by enclosing their names in curly brackets, for example:

Hello! This is {name} from the customer care team at {company_name}.

Variables may come from global flow configuration, conversation data, dynamic variables (such as user_utterance_count), or variables extracted / generated earlier in the flow. Note that variables extracted by this node's own Extract variables section are not available here, as that extraction runs only when the node exits.

You may also use prompt conditions ({{#if}} / {{#elseif}} / {{/if}} branching) in conversation node instructions, exactly as in an agent prompt.

Including another node's prompt

To reuse a block of instructions across several nodes, embed another conversation node's prompt with the following syntax, placed on its own line:

{{< node_name}}

node_name is the Name of the conversation node whose prompt you want to include. At runtime the line is replaced with that node's prompt text, and variable expansion and {{#if}} branching are then applied to the combined result. If no node with that name exists, the line is left unchanged.

Transitions

Transitions determine when the specific conversation node has finished its job and what next node the conversation should transition to. Transitions are typically defined using free text and evaluated after each user utterance.

Transitions are normally evaluated after user provides response, and not for the first phrase generated by the conversation node. This may be changed by enabling the Transition immediately parameter in the conversation node settings.

Text and logical transitions

When you add a transition by clicking the + button in the Transition section, you are asked whether it is a Text or a Logical transition:

If a node defines both text and logical transitions, the logical transitions take precedence over the text transitions, regardless of their visual order on the node card.

You can also embed a logical condition inside a Text transition by enclosing it in brackets – for example (age < 18). However, it is recommended to use a dedicated Logical transition instead, for clarity.

Logical statements can be used the same way in global node conditions – see Global nodes.

Empty and non-empty transitions

An empty Text transition shows the User replies placeholder. It matches any user utterance, so the conversation transitions to the next node after the user says anything:

An empty Logical transition shows the Else placeholder and has the same effect – it matches regardless of what the user says.

If instead you want the conversation to proceed only after the user provides the required information, use a non-empty transition that describes the expected condition – for example User provided his age:

Order of evaluation

When a node uses logical transitions, the order in which things happen matters:

  1. The node converses with the user until some transition is matched. The Extract variables section is not evaluated during these turns.
  2. Once the node "exits" – i.e. a transition is matched – the Extract variables section is evaluated.
  3. The logical transitions are then re-evaluated (and, as noted above, take precedence over text transitions). If one of them now matches, it is used instead of the transition that was matched initially.

This order is what lets you route the conversation based on variables that only become available after extraction. Note that because extraction runs only after the conversation stage ends, a node whose logical transitions depend on extracted variables needs an empty Else (or User replies) transition to end the conversation stage on each turn – otherwise the node would keep conversing and never reach the extraction step.

For example, consider the following City node:

Its Extract variables section defines a city variable whose description explicitly instructs the LLM to assign the value N/A when the user doesn't provide enough information:

On each user reply, the empty Else transition ends the conversation stage, so the Extract variables section runs and city is set either to the city name or to N/A. The logical transitions are then re-evaluated: if city == 'N/A' the node keeps asking, otherwise the Else transition routes the conversation to the next node.

The next example, the Age node, combines logical transitions with the per-node user_utterance_count variable (see Dynamic variables) to limit the number of attempts:

The transitions are re-evaluated, after extraction, in order:

Extract variables

Conversation node may be optionally configured to extract variables from the conversation transcript that happened in the specific node. To enable this functionality, turn on Extract variables in node settings and configure the corresponding section that will be added to the conversation node card.

For each variable you need to define name, type and description. Make sure to provide meaningful description, as it is used by LLM to populate the variable’s data.

Alternatively you may populate a variable using a jq or JMESPath statement in its Description, as described in Using jq or JMESPath statements. The Description also supports dynamic variable expansion and {{#if}} branching, as well as an ENUM: prefix to restrict the value to a fixed set.

You may also perform the extraction as a separate step, using a dedicated Extract variables node. Unlike the inline extraction described here – which only sees the transcript of this node – a dedicated node can extract variables from the complete conversation history.

Note: The Extract variables section runs only when the node exits – i.e. when a transition is matched – and not after every user turn. If you need the variables to be refreshed (and logical transitions re-evaluated) on each turn, add an empty Else (or User replies) transition, as described in Order of evaluation.

Documents

You may provide conversation node with documents for grounding the responses. Conversation node uses “agentic RAG” mode for accessing the documents data – similar to the Semantic search (agentic, via doc_search tool) mode, as described in Using documents.

A structured lookup document may be attached the same way. It grounds the node's responses on the record it retrieves – or, with the #lookup directive, the node calls the lookup as a step of the flow instead; see Structured lookup below.

Conversation node settings

Playing pre-recorded audio

A node in Say mode can play pre-recorded audio files instead of – or in addition to – speaking text. The files must first be uploaded as an audio document. To play them, add one or more #play_<cmd> directives, each on its own line, to the node's text:

Additional behavior:

By default, audio files are assumed to be in wav/lpcm16 format. To use a different format, set the play_format flow variable; it accepts the same formats as the play_url pre-defined tool. For example:

play_format = wav/mulaw

The example below greets the caller with a recorded prompt and then reads back a reference number digit by digit:

#play_file greetings welcome.wav
Your reference number is:
#play_digits digits {reference_number}

Pausing before speaking

A node in Say mode can stay silent for a while before speaking its text. Make #pause <seconds> the first non-empty line of the node's text; the node then waits the given number of seconds and only then sends the rest of the text:

#pause 1.5
I found the following records matching your request.
#pause 2
#play_file greetings welcome.wav

Structured lookup

A conversation node can use a structured lookup document in one of two modes.

For grounding the node's responses – add the document to the node's Documents, like any other document. The node gains the document's lookup tool, and the agent decides when to call it and uses the matching record to word its own reply to the caller. This suits a node that is conversing: the agent can ask the caller to spell a name, try again, or read a couple of candidates back and ask which one was meant.

As a tool call – make #lookup the first non-empty line of the node's text:

#lookup find_customer {caller}

The node then stops being conversational and becomes the equivalent of a Call tool node whose tool is the document lookup: it performs exactly one lookup and moves on, never speaking and never waiting for the caller. The flow decides what happens next by branching on the outcome, rather than the agent wording a reply from the rows. When the value to look up comes from a variable, no LLM is involved at all.

So: use #lookup when you want the lookup called as a step whose result drives the flow ("we have the caller's account number – fetch the record and route accordingly"), and plain Documents when the record is there to inform what the agent says next.

The rest of this section describes the #lookup tool-call mode.

Syntax

The directive names the lookup to call — its tool_name, as configured on one of the node's attached documents — followed by what to look up: one value per search column, in the order the columns are configured.

Form Meaning
#lookup find_customer {caller} look up the value held by the caller variable
#lookup find_customer "{caller}" the same; for a single value the quotes are optional
#lookup find_customer John D look up a literal value
#lookup find_customer "John D" the same, quoted
#lookup find_customer "{caller}" "{city}" quotes required — one quoted value per search column, for a lookup matching on several columns
#lookup find_customer no values at all, so the model works them out from the conversation (Prompt mode only)

Quotes are what separate one value from the next, so a lookup matching on several columns must quote every value. #lookup find_customer "{caller}" "{city}" passes two values; #lookup find_customer {caller} {city} passes one — everything after the tool name on an unquoted line is a single value, which is what a one-column lookup wants.

Values are always trimmed of surrounding whitespace.

Any further lines of the node's text become additional instructions for the model, used only when it has to work a value out from the conversation (see the next section).

A misconfigured directive — one that names no lookup, names one that none of the node's documents define, or sits on a node with no lookup document attached — reports lookup_status = error, so the node routes down its error branch. The problem is written to the conversation log.

Operation mode decides whether the LLM is involved

A flow used as a tool must have its values on the directive line, because it has no conversation of its own to work them out from.

What the node produces

While the node runs, three variables are available:

Variable Type Value
lookup_status str match, ambiguous, not_found or error
lookup_result str the matching rows as a JSON array, best first; [] when nothing matched
lookup_score int match score of the best row, 0 when there is none

error means the lookup never ran — an unresolved value in Say mode, no LLM available, the model declining to call the tool, or a document whose lookup index hasn't been built.

Note: ambiguous means the match wasn't confident enough to act on. That is usually several similar-sounding records, but it can also be a single weak one — so branch on lookup_status, never on how many rows came back. See Tuning the thresholds.

The three variables are scoped to the node: they are removed as soon as the flow moves on, and are not visible to later nodes. lookup_status, lookup_result and lookup_score are therefore reserved names inside a #lookup node — a flow variable of the same name is shadowed for the duration of the node and restored afterwards.

Transitions

Use Logical transitions to branch on the outcome:

lookup_status == "match"      --> Confirm the account
lookup_status == "ambiguous"  --> Ask which record was meant
Else                          --> Offer to transfer

Free-text transition conditions are not evaluated on a #lookup node — there is no caller utterance to judge them against. Consequently:

A #lookup node cannot transition back to itself.

Leave Skip response off on a lookup node — the node never waits for the caller anyway. If it is on, the node behaves like any other node with Skip response: it follows the single Skip response connection, and its own transitions — the ones the canvas hides — are not evaluated at all, so the node cannot branch on the outcome.

Using the matched record

The variables above route the flow, but they disappear with the node. There are two ways to use the record itself further on.

Consuming it in the next node

Exactly as with a Call tool node, the matched rows are added to the conversation context, so the next node's prompt can consume them. This is what makes the ambiguous branch above workable: that node already has the candidate records, so its instructions can simply be

The lookup returned several possible records. Ask the caller which of them they meant.

The record is not spoken by the lookup node itself, and it is never shown to the lookup node's own LLM turn — only to the node the flow transitions to.

To suppress it, add a #discard_response directive on its own line, so the record stays out of the conversation context:

#lookup find_customer {caller}
#discard_response

Variables extraction still runs over the record, so you can copy out the fields a later node needs without exposing the whole record — worth doing when the document holds anything sensitive.

Extracting variables from it

Alternatively — or in addition — copy values out with the node's own Extract variables section, which runs against lookup_result. For example, a variable with:

captures the best-matching record's address, which then behaves like any other flow variable and can be referenced as {customer_address} anywhere later in the flow. All the usual extraction forms work — jq / JMESPath statements, the {} whole-response capture, and LLM-driven extraction.

Extraction runs on every outcome, including not_found and error, where lookup_result is the empty array []. A jq / JMESPath statement that matches nothing then yields an empty value — "" for a string variable, 0 for int and float, false for bool — without consulting the LLM. So in the example above, customer_address is "" when no record was found.

Prefer extraction when a later node needs one specific field, and the conversation context when the agent has to reason over the whole record — as the ambiguous branch does.

Special features for the AC DNN STT provider

When your agent uses the AC DNN speech-to-text provider, the following additional capabilities are available.

STT context

A node in Say mode can apply a speech-to-text context to the user's next utterance, biasing how the STT engine recognizes it.

Add a #stt_context <name> directive (alongside the #play_<cmd> directives) to the node's text. It attaches an sttContextId to the last play / message activity emitted by the node, so the named context applies to the user's next utterance. For example:

#play_file prompts confirm.wav
#stt_context D_Confirm_Context

Additional behavior:

user_utterance_alt variable

user_utterance_alt holds the STT interpretation of the current user utterance – the engine's normalized reading of what was said. It is available in both Agent and Flow prompts / dynamic variables.

For example, an utterance whose recognition yields "interpretation": "7 0 1 2 1 2 1 3" makes user_utterance_alt equal to 7 0 1 2 1 2 1 3.

The variable is cleared to an empty string on turns that carry no STT interpretation – for example chat messages, DTMF input, or no-user-input events.

user_utterance_ext variable

user_utterance_ext is a convenience variable that resolves to user_utterance_alt when the current turn carries an STT interpretation, and falls back to user_utterance otherwise. It is available in both Agent and Flow prompts / dynamic variables.

Use it when a prompt should prefer the STT interpretation "if present" without having to test user_utterance_alt for emptiness.

Unsuccessful speech recognition

When the STT engine reports that it could not recognize the user's speech, the agent receives a “fake” user utterance (similar to no user input handling):

Add explicit instructions to your prompt on how to react to STT-NO-MATCH – for example, asking the user to repeat.