LiteBot

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

  1. On the Bots page choose Create Bot, then Visual Builder.
  2. Drag nodes from the palette onto the canvas and connect them. A flow needs one Start node and at least one End node.
  3. 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.
  4. 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:

  1. On the first turn the position is set to the node of type start, or the first node in the list.
  2. If the current node is userInput, the user's message is saved as variables[saveAsVariable] (default name userResponse) and the flow moves on.
  3. condition and tool nodes then run in code, one after another, without the model. At most FLOW_MAX_AUTO_ADVANCE steps run per turn (default REACT_MAX_ITERATIONS, then 10).
  4. At an end node, the node's content or message text is returned as the reply, with {{variable}} filled in. No model call is made.
  5. 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-userInput node, 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 userInput node reached straight after a start or message node 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. A userInput reached after another userInput, a tool or a condition does ask.
  • Message text belongs in content or message. The editor's messageContent field is not read by the executor.
  • Conditions read top-level variables only, so you cannot test order.success. The exists check 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, conditionNode and endNode, and message text in messageContent. The executor does not recognise those types: every node becomes a model step with the prompt Process 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 remove metadata.isVisualBuilder (send the whole metadata object in a PUT) 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 generated systemPrompt as a prompt bot with tools. A flow that previews well can behave differently once saved.
  • Editor-only fields. onError, retryCount, outputVariable, validationPattern, options, timeoutSeconds, endType and transferToBotId are stored but not read by the executor. A failed tool does not branch, and an End node of type transfer or escalate does 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_ADVANCE steps in one turn, and the model is then called at whatever node was reached.

Where to go next