Visual flows
This chapter explains flow bots: bots drawn as a graph of steps in the dashboard's visual builder instead of written as one prompt. It covers turning the builder on, the node types, templates and validation, how a flow is stored on the bot, and how a flow turn runs. Read it before you build a flow bot, and read Known gaps before you put one in front of callers.
What a flow bot is
A flow bot is an ordinary bot with two extra things: a _visualFlow object holding the nodes and edges, and metadata.isVisualBuilder: true. When both are present and the flow has at least one node, LiteBot runs the bot in flow mode. Each turn it moves through the graph, runs conditions and tools in code, and calls the model only for the current step, with a short prompt for that step. Without metadata.isVisualBuilder, the same bot runs as a normal prompt bot and the graph is only stored for re-editing.
Flow mode applies to text chat (POST /api/chat and the /chat WebSocket). Voice sessions on /voice always use the bot's systemPrompt.
Turning the visual builder on
| Setting | Where | Effect |
|---|---|---|
ENABLE_VISUAL_EDITOR=true |
.env beside the binary |
Registers /api/visual-builder/* (status, templates, validate, convert). Default false |
VITE_ENABLE_VISUAL_EDITOR=true |
Dashboard build | Shows the Visual Builder button. This is fixed when the dashboard is built into the binary |
The start-up log shows [visual-builder] Routes registered at /api/visual-builder, or Module disabled (ENABLE_VISUAL_EDITOR !== 'true'). If the server flag is on but the button is missing, your binary's dashboard was built without the second flag.
Check the server side:
curl -s http://127.0.0.1:3345/api/visual-builder/status
{ "success": true, "data": { "enabled": true, "version": "1.0.0", "features": { "templates": true, "validation": true, "export": true } } }
The /api/visual-builder routes do not require a login.
Building a flow in the dashboard
- On the Bots page choose Create Bot, then Visual Builder.
- Drag nodes from the palette onto the canvas and connect them. A flow needs one Start node and at least one End node.
- Select a node to edit its fields in the properties panel. The Start node holds the bot's name, description, provider, model and other bot settings.
- Validate shows errors and warnings. Preview opens a test chat. Save creates the bot (
POST /api/bots), or updates it (PUT /api/bots/:botId) when you are editing an existing flow bot.
You can also export the flow as a JSON file and import it again, and load a template as a starting point.
Node types
The editor stores each node with a React Flow type and a data.nodeType.
| Palette | type |
data.nodeType |
Fields you set |
|---|---|---|---|
| Start | startNode |
start |
botName, botDescription, systemContext, provider, model, temperature, maxTokens, language, department, keywords, escalationKeywords, maxEscalationAttempts |
| Message | messageNode |
message |
messageContent |
| User Input | userInputNode |
userInput |
expectedInputType (text, number, date, selection, confirmation), saveAsVariable, validationPattern, options |
| Tool | toolNode |
tool |
toolName, toolDescription, parameters, outputVariable, onError |
| Condition | conditionNode |
condition |
conditions (each with operator, leftOperand, rightOperand), defaultBranch |
| End | endNode |
end |
endType (complete, escalate, transfer, hangup), transferToBotId, handoffMessage |
Templates
Five templates ship with the server. List them with GET /api/visual-builder/templates and fetch one with GET /api/visual-builder/templates/<id>.
| Id | Purpose |
|---|---|
appointment-booking |
Inbound booking: pick department, doctor and slot, then book |
customer-faq |
Answer questions from the knowledge base |
lead-qualification |
Qualify sales leads and schedule demos |
payment-collection |
Outbound: collect pending payments |
survey-bot |
Structured survey with conditional questions |
Templates call tools such as getDepartments or bookAppointment. Those tools must exist in your registry (Tools); the template does not create them.
Validation
The dashboard checks the flow before it saves or previews. Errors block the save; warnings do not.
| Message | Level |
|---|---|
Flow must have a Start node |
Error |
Flow can only have one Start node |
Error |
Flow must have at least one End node |
Error |
Start node must have a bot name |
Error |
Tool node "<label>" must specify a tool |
Error |
Start node should specify a provider (defaulting to groq) |
Warning |
<n> node(s) are not connected to the flow |
Warning |
Condition node "<label>" has no conditions defined |
Warning |
User Input node "<label>" should specify a variable name |
Warning |
No path found from Start to any End node |
Warning |
POST /api/visual-builder/validate with {"flow": {...}} runs the server's copy of these checks. It differs in two places: a condition node without conditions is an error, and there is no path check. POST /api/visual-builder/convert validates and returns a bot config without saving it. It refuses flows over 1 MB, and fails with Cycle detected when any node can be reached by two paths.
How the flow is stored on the bot
Saving converts the flow into a normal bot. The Joi schema checks only that _visualFlow has nodes and edges arrays (Visual flow keys).
| Bot key | Filled from |
|---|---|
id |
botName, lower-cased with non-alphanumerics turned into -, up to 50 characters. Kept when you edit an existing bot |
name, description |
botName, botDescription |
provider, model |
Start node, default gemini and models/gemini-2.5-flash |
temperature, maxTokens |
Start node, default 0.7 and 800 |
tools |
Every toolName on a Tool node |
systemPrompt |
A generated state-machine prompt: role, systemContext, rules, tools and one STATE per node |
escalationRules |
keywords from escalationKeywords, maxAttempts from maxEscalationAttempts (default 3) |
handoffMessages |
From End nodes with endType: transfer, a target bot and a handoff message |
metadata |
department, keywords, priority: 1, isVisualBuilder: true, flowVersion |
intentThreshold, switchingThreshold |
0.3 and 0.4 |
_visualFlow |
The whole flow: nodes, edges, metadata |
metadata.keywords makes the bot a target for the intent router and for in-session bot switching (Routing and bot switching).
How a flow turn runs
The flow executor (src/core/flow-executor.js) keeps its position in the session context as _flowState: currentNodeId, visitedNodes, variables and iterationCount. A turn runs like this:
- On the first turn the position is set to the node of type
start, or the first node in the list. - If the current node is
userInput, the user's message is saved asvariables[saveAsVariable](default nameuserResponse) and the flow moves on. conditionandtoolnodes then run in code, one after another, without the model. At mostFLOW_MAX_AUTO_ADVANCEsteps run per turn (defaultREACT_MAX_ITERATIONS, then 10).- At an
endnode, the node'scontentormessagetext is returned as the reply, with{{variable}}filled in. No model call is made. - At any other node, the model is called once. Its system prompt is replaced by a step prompt:
You are <name>. <description>.plus the step, and the conversation so far follows. After a non-userInputnode, the flow moves to the next node.
What the model is told at each node:
Node type |
Step prompt |
|---|---|
start |
content, or Greet the user and begin the conversation. |
message |
Say this exact message to the user: "<content>" |
userInput |
Ask the user for their <label> (expected: <inputType>). Wait for their response. No tools are offered |
| any other | content, or Process the "<type>" step. |
Condition nodes compare variables[variable] with value using equals, notEquals, contains, notContains, greaterThan, lessThan, greaterThanOrEqual, lessThanOrEqual, matches (regex), exists or notExists. Several conditions combine with logic AND (default) or OR. The flow follows the edge whose sourceHandle is true or yes when the result is true, false or no when false, otherwise an edge with no handle, otherwise the first edge.
Tool nodes run toolName (or tool) with parameters (or params). A parameter may be a string with {{variable}}, or {"source": "variable", "value": "<name>"}, {"source": "static", "value": ...} or {"source": "userInput", "variable": "<name>"}. The tool's result is saved as variables[saveResultAs].
Compared with a prompt bot, a flow turn does not add the date message or the session-context message, does not run tool calls the model asks for, and always returns shouldEscalate, shouldHangup and botSwitched as false.
Writing a flow the runtime executes
The executor reads the plain node types (start, message, userInput, tool, condition, end) and the fields above. You can store such a flow through the API:
curl -s -X POST http://127.0.0.1:3345/api/bots \
-H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
-d @pizza-orders-flow.json
{
"id": "pizza-orders-flow",
"name": "Pizza Orders",
"description": "Takes pizza orders for Example Pizza",
"provider": "gemini",
"systemPrompt": "You take pizza orders for Example Pizza.",
"tools": ["createOrder"],
"metadata": { "isVisualBuilder": true },
"_visualFlow": {
"nodes": [
{ "id": "s", "type": "start", "data": { "content": "Greet the customer and ask which pizza they want." } },
{ "id": "p", "type": "userInput", "data": { "label": "pizza", "saveAsVariable": "pizza" } },
{ "id": "a", "type": "userInput", "data": { "label": "delivery address", "saveAsVariable": "address" } },
{ "id": "t", "type": "tool", "data": { "toolName": "createOrder", "saveResultAs": "order",
"parameters": { "item": "{{pizza}}", "address": { "source": "variable", "value": "address" } } } },
{ "id": "c", "type": "condition", "data": { "variable": "order", "operator": "exists" } },
{ "id": "ok", "type": "end", "data": { "content": "Thank you. Your {{pizza}} is on its way." } },
{ "id": "fail", "type": "end", "data": { "content": "Sorry, I could not place the order. Please call the shop." } }
],
"edges": [
{ "id": "e1", "source": "s", "target": "p" },
{ "id": "e2", "source": "p", "target": "a" },
{ "id": "e3", "source": "a", "target": "t" },
{ "id": "e4", "source": "t", "target": "c" },
{ "id": "e5", "source": "c", "target": "ok", "sourceHandle": "true" },
{ "id": "e6", "source": "c", "target": "fail", "sourceHandle": "false" }
]
}
}
Two rules shape a working flow:
- A
userInputnode reached straight after astartormessagenode does not ask its own question. The model speaks the earlier node, the flow moves on, and the next user message is saved straight away. Put the question in the earlier node, as the start node does above. AuserInputreached after anotheruserInput, a tool or a condition does ask. - Message text belongs in
contentormessage. The editor'smessageContentfield is not read by the executor. - Conditions read top-level variables only, so you cannot test
order.success. Theexistscheck above catches a tool that threw, timed out or is missing; a tool that returns{"success": false, ...}still counts as existing. A tool name that is not in the registry fails the whole turn.
Known gaps
- Editor flows do not run as drawn. The editor saves node types
startNode,messageNode,userInputNode,toolNode,conditionNodeandendNode, and message text inmessageContent. The executor does not recognise those types: every node becomes a model step with the promptProcess the "<type>" step., tool nodes do not run (tool calls the model asks for are ignored too), input is not saved, and End nodes do not end the flow. To use a flow drawn in the editor, either removemetadata.isVisualBuilder(send the wholemetadataobject in aPUT) so the bot runs in prompt mode on its generated state-machine prompt, or rewrite the nodes in the runtime form above. - Preview is not the runtime. Preview calls
POST /api/chat/preview, which runs the generatedsystemPromptas a prompt bot with tools. A flow that previews well can behave differently once saved. - Editor-only fields.
onError,retryCount,outputVariable,validationPattern,options,timeoutSeconds,endTypeandtransferToBotIdare stored but not read by the executor. A failed tool does not branch, and an End node of typetransferorescalatedoes not transfer or escalate. - After an End node every further message returns the same end text. Start a new session to start again.
- Bot switching still runs before the flow on each turn, so a flow bot can be switched away from by another bot's keywords.
- Loops of conditions and tools stop after
FLOW_MAX_AUTO_ADVANCEsteps in one turn, and the model is then called at whatever node was reached.
Where to go next
- Bot keys and the
_visualFlowschema: Bot configuration reference - Tools that flow nodes call: Tools
- Testing a flow bot over the API: Chat API
- Following a flow turn by turn in the request trace: Conversations and analytics