Flow nodes
Deep dive into FlowNodeModel — the building blocks of every conversation flow.
Overview
Each node in a template’s flow represents a distinct phase of the conversation. The agent enters the initial_node at the start and transitions between nodes via function calls. Nodes define what the LLM should do (task messages), what happens on entry/exit (pre/post actions), and what tools the LLM can invoke (functions).
FlowNodeModel fields
| Field | Type | Description |
|---|---|---|
node_name | string | Unique identifier for this node within the flow |
task_messages | List[TaskMessage] | System/user messages that instruct the LLM |
role_messages | List[TaskMessage] | Additional role-specific messages (persona, tone) |
pre_actions | List[FlowAction] | Actions executed before the LLM processes task messages |
post_actions | List[FlowAction] | Actions executed after leaving the node |
functions | List[FlowFunction] | Node-scoped functions the LLM can call |
vad_config | Optional[VadConfig] | Node-level VAD override |
interruption | Optional[InterruptionConfig] | Node-level interruption override |
input_collection | Optional[InputCollectionConfig] | DTMF/speech input collection config |
Task messages
Task messages are the LLM instructions for a node. They follow the standard chat-completion message format and support {'{variable}'} placeholder syntax.
| Field | Type | Description |
|---|---|---|
role | string | Message role — typically "system" or "user" |
content | string | Message content — supports {'{variable}'} placeholders |
{
"task_messages": [
{
"role": "system",
"content": "You are calling {customer_name} about their appointment on {appointment_date} with {doctor_name}. Be polite and professional."
}
],
"role_messages": [
{
"role": "system",
"content": "Speak in a warm, conversational Indian English tone. Keep responses under 2 sentences."
}
]
}Variable Placeholders
Any {placeholder} in message content is resolved from the lead payload, template secrets, or credential secrets at call time. See Variable Substitution for details.
Flow actions (pre & post)
Actions execute at specific lifecycle points. pre_actions run before the LLM; post_actions run after exiting the node.
| Field | Type | Description |
|---|---|---|
type | "tts_say" \| "end_conversation" \| "function" | The action type |
text | Optional[string] | Text to speak (for tts_say) |
handler | Optional[string] | Handler function name (for function type) |
args | Optional[object] | Arguments to pass to the handler |
Action Types
tts_say — Speaks text immediately via TTS. Supports {'{variable}'} substitution.
{
"type": "tts_say",
"text": "Hello {customer_name}! Please hold for just a moment."
}end_conversation — Ends the conversation immediately.
{ "type": "end_conversation" }function — Calls an internal handler function with optional arguments.
{
"type": "function",
"handler": "mute_stt",
"args": { "duration": 3 }
}Node transition patterns
Nodes transition through functions. When the LLM calls a function with transition_to, the conversation moves to that node.
Common patterns:
- Linear flow: greeting → collect → confirm → farewell
- Branching: greeting → (confirm OR reschedule) → farewell
- Loopback: collect_details → (insufficient data? stay) → confirm
- Hub-and-spoke: main_menu → (billing | support | orders) → main_menu
Stay on Current Node
Set transition_to: null on a function to stay on the current node after execution. Useful for data collection without changing phases.
Per-Node configuration overrides
Nodes can override template-level VAD, interruption, and input collection settings. These overrides follow the reset-then-apply pattern.
Reset-then-Apply Pattern
On every node transition, VAD, interruption, and input collection configs are reset to template defaults first, then node-level overrides apply. This prevents configuration bleed between nodes.
| Override | Reset Behavior | Use Case |
|---|---|---|
vad_config | Reset to template VAD | Increase silence threshold for thinking time |
interruption | Reset to template interruption | Disable interruption during critical info |
input_collection | Reset to null (disabled) | Enable multi-segment capture for specific nodes |
is_active | — | Set false to silently exclude the node at load time (no validation error; just removed). |
{
"vad_config": { "stop_secs": 2.0 },
"interruption": { "mode": "disabled_discard" },
"input_collection": {
"enabled": true,
"user_speech_timeout": 3.0
}
}interruption.mode accepts "enabled" or "disabled_discard". input_collection has only enabled and user_speech_timeout — there is no DTMF support today. See Interruption control and Input collection for full semantics.
Complete flowNode example
{
"node_name": "collect_details",
"task_messages": [
{
"role": "system",
"content": "You are collecting delivery details from {customer_name}. Ask for their preferred delivery date and time slot."
}
],
"role_messages": [
{
"role": "system",
"content": "Speak in a friendly, conversational tone. Keep responses concise."
}
],
"pre_actions": [
{ "type": "tts_say", "text": "Great, let me help you schedule your delivery." }
],
"post_actions": [],
"functions": [
{
"name": "delivery_scheduled",
"description": "Customer has confirmed a delivery date and time slot",
"properties": {
"delivery_date": { "type": "string", "description": "YYYY-MM-DD format" },
"time_slot": { "type": "string", "enum": ["morning", "afternoon", "evening"] }
},
"required": ["delivery_date", "time_slot"],
"transition_to": "farewell",
"hooks": [
{
"name": "update_outcome_in_database",
"expected_fields": {
"outcome": { "source": "static", "value": "scheduled" },
"delivery_date": { "source": "llm" },
"time_slot": { "source": "llm" }
}
}
]
}
],
"vad_config": { "stop_secs": 1.5 },
"interruption": { "mode": "disabled_discard" },
"is_active": true
}Design Tip
Keep nodes focused on a single task. A node that tries to do too much leads to confused LLM behavior. Break complex workflows into multiple small, well-defined nodes.