Skip to main content

Documentation Index

Fetch the complete documentation index at: https://www.bolna.ai/docs/llms.txt

Use this file to discover all available pages before exploring further.

Every node has a list of edges. After each customer message, the framework picks the next node by checking deterministic edges first (instant, free), then handing off to the routing LLM if nothing deterministic matches.

Edge fields

FieldDescription
to_node_idTarget node id. Required.
conditionHuman-readable description of when to transition. Used as the routing LLM’s tool description.
condition_typeOne of "llm" (default if unset), "expression", "unconditional", "event".
expressionRequired when condition_type == "expression". See Expression edges.
event_nameRequired when condition_type == "event". The external event name to listen for. See Event injection.
function_nameOverride the auto-generated routing tool name (default: transition_to_<to_node_id>).
function_descriptionOverride the auto-generated routing tool description.
parametersMap of {name: type} to extract from the user’s input during the transition. See Inline data extraction.
priorityLower fires first. Defaults: expression / unconditional / event = 0, llm = 100.

Edge types

condition_typeHow it worksWhen to use
Not set or "llm"Routing LLM evaluates condition against the customer’s reply.When the decision requires understanding intent.
"expression"Rule-based check on context variables. Fires instantly if matched.Time of day, retry counts, language detection, any data-driven decision.
"unconditional"Always takes this edge. No check.When there is only one possible next step.
"event"Fires only when a matching external event arrives via REST. Ignored during speech routing.When an external system (payment, form, link click) drives the conversation. See Event injection.
Expression and unconditional edges are checked together (in priority order, ascending) before any LLM call is made.

Expression edges

{
  "to_node_id": "after_hours",
  "condition": "Outside working hours",
  "condition_type": "expression",
  "expression": {
    "logic": "or",
    "conditions": [
      { "variable": "recipient_data.current_hour", "operator": "lt",  "value": 10 },
      { "variable": "recipient_data.current_hour", "operator": "gte", "value": 18 }
    ]
  }
}
Use "logic": "and" when all conditions must be true. Use "logic": "or" when any one is enough.

Operators

Equality

eq, neq

Numbers

gt, gte, lt, lte

Text

contains

Lists

in, not_in

Existence

exists, not_exists

Priority

Lower priority fires first. Defaults if not set: deterministic edges (expression, unconditional, event) get priority 0; LLM edges get priority 100. Within the deterministic bucket, the first matching edge wins. For mutually-exclusive rules (e.g. working-hours vs after-hours), set distinct priorities to make ordering explicit.

Built-in variables

These variables are populated automatically and can be referenced in any expression.
VariableTypeNotes
recipient_data.current_hourint (0-23)Hour in the call’s timezone.
recipient_data.current_minuteint (0-59)
recipient_data.current_weekdaystringLowercase, e.g. "wednesday".
recipient_data.current_dayint (1-31)
recipient_data.current_monthint (1-12)
recipient_data.current_yearint
recipient_data.current_datestringDisplay string for prompts only, not for comparisons.
recipient_data.current_timestringDisplay string for prompts only.
recipient_data.timezonestringe.g. "Asia/Kolkata".
recipient_data.user_numberstringE.164 phone number.
detected_languagestringTop-level, not under recipient_data. e.g. "hindi", "en".
_node_turnsintUser messages on the current node. Resets on every transition.
_total_turnsintUser messages in the entire call.
_silence_repeatsintTimes the current node has replayed because the user stayed silent past repeat_after_silence_seconds. Resets on transition. See Static nodes.
Time variables (current_hour, current_weekday, etc.) are populated only when recipient_data.timezone is set on the call. Without a timezone, time-based expressions silently never match. Always set timezone when creating a call that uses time-based routing.

Common patterns

Working hours (10 AM to 6 PM)
{
  "to_node_id": "transfer_call",
  "condition": "Working hours",
  "condition_type": "expression",
  "priority": 0,
  "expression": {
    "logic": "and",
    "conditions": [
      { "variable": "recipient_data.current_hour", "operator": "gte", "value": 10 },
      { "variable": "recipient_data.current_hour", "operator": "lt",  "value": 18 }
    ]
  }
}
Auto-escalate after too many retries
{
  "to_node_id": "transfer_call",
  "condition": "Too many retries on this node",
  "condition_type": "expression",
  "expression": {
    "conditions": [
      { "variable": "_node_turns", "operator": "gte", "value": 2 }
    ]
  }
}

Inline data extraction

Edges can capture typed values from the user’s reply during routing. The routing LLM treats them as required parameters; on a successful transition the values are merged into context_data and become available everywhere.
{
  "to_node_id": "confirm_order",
  "condition": "Customer provided their order id",
  "parameters": {
    "order_id": "string"
  }
}
After the transition, context_data["order_id"] is set and you can:
  • Reference it in node prompts via {order_id}.
  • Use it in downstream expression edges: { "variable": "order_id", "operator": "exists" }.
  • Pass it to API tools as %(order_id)s.
Prefer parameters over a separate “extract” node. One LLM call routes and captures data.

Routing instructions

The routing_instructions field is prepended to every routing request. Keep it short and directive:
You are the Routing System for this conversation. Analyze the user's input
and the available edges. Select the edge whose condition best matches.
If no edge matches, stay on the current node.
You can include {variable} placeholders. They are substituted from context_data (and the flattened recipient_data) at runtime. Missing keys render as NULL.