BASIC Execution Modes: RUNTIME vs WORKFLOW 🟑 BETA

General Bots BASIC scripts run in one of two execution modes. The mode is selected by a pragma at the top of the .bas file.

Quick Comparison

FeatureRUNTIME mode (default)WORKFLOW mode βš—οΈ
Pragma(none)#workflow
EngineRhai ASTStep engine + PostgreSQL
HEAR behaviorBlocks a threadSuspends to DB, zero threads
Server restartLoses positionResumes exact step
Side-effect re-run❌ possible on crashβœ… never
Multi-day flows❌ (1h timeout)βœ… unlimited
FOR EACH loopsβœ…βŒ
FUNCTION / SUBβœ…βŒ
USE WEBSITEβœ…βŒ
Startup time~1ms~2ms
RAM per session1 thread (~64KB)0 threads
ObservabilityLogs onlyDB rows (queryable)
Best forTools, short dialogsMulti-step dialogs, tickets, approvals

RUNTIME Mode (default)

Every .bas file without #workflow runs in RUNTIME mode. The script compiles to a Rhai AST and executes in a spawn_blocking thread. HEAR blocks the thread until the user replies (up to hear-timeout-secs, default 3600).

' ticket.bas β€” RUNTIME mode (no pragma)
TALK "Describe the issue"
HEAR description          ' blocks thread, waits
SET ticket = CREATE(description)
TALK "Ticket #{ticket} created"

When to use: Tool scripts called by LLM, short dialogs (< 10 minutes), scripts using FOR EACH, FUNCTION, or USE WEBSITE.


WORKFLOW Mode βš—οΈ

Status: Planned feature β€” see botserver/WORKFLOW_PLAN.md

Add #workflow as the first line. The compiler produces a Vec<Step> instead of a Rhai AST. Each step is persisted to workflow_executions in PostgreSQL before execution. On HEAR, the engine saves state and returns β€” no thread held. On the next user message, execution resumes from the exact step.

#workflow
' ticket.bas β€” WORKFLOW mode
TALK "Describe the issue"
HEAR description          ' saves state, returns, zero threads
SET ticket = CREATE(description)
TALK "Ticket #{ticket} created"

When to use: Multi-step dialogs, ticket creation, approval flows, anything that may span minutes or days.

Keyword compatibility in WORKFLOW mode

CategoryKeywordsWORKFLOW support
DialogTALK, HEAR, WAITβœ…
DataSET, GET, FIND, SAVE, INSERT, UPDATE, DELETEβœ…
CommunicationSEND MAIL, SEND TEMPLATE, SMSβœ…
AIUSE KB, USE TOOL, REMEMBER, THINK KBβœ…
HTTPGET (http), POST, PUT, PATCH, DELETE (http)βœ…
SchedulingSCHEDULE, BOOK, CREATE TASKβœ…
ExpressionsFORMAT, math, datetime, string functionsβœ… (via Rhai eval)
Control flowIF/ELSE/END IFβœ…
LoopsFOR EACH / NEXT❌ use RUNTIME
ProceduresFUNCTION, SUB, CALL❌ use RUNTIME
BrowserUSE WEBSITE❌ use RUNTIME
EventsON EMAIL, ON CHANGE, WEBHOOK❌ use RUNTIME

How WORKFLOW compiles

The compiler does not use Rhai for workflow mode. It is a line-by-line parser:

TALK "Hello ${name}"   β†’  Step::Talk { template: "Hello ${name}" }
HEAR description        β†’  Step::Hear { var: "description", type: "any" }
SET x = score + 1       β†’  Step::Set  { var: "x", expr: "score + 1" }
IF score > 10 THEN      β†’  Step::If   { cond: "score > 10", then_steps, else_steps }
SEND MAIL to, s, body   β†’  Step::SendMail { to, subject, body }

Expressions (score + 1, score > 10) are stored as strings and evaluated at runtime using Rhai as a pure expression calculator β€” no custom syntax, no side effects.

Observability

In WORKFLOW mode, every step is a DB row. You can query execution state directly:

SELECT script_path, current_step, state_json, status, updated_at
FROM workflow_executions
WHERE session_id = '<session-uuid>'
ORDER BY updated_at DESC;

Choosing a Mode

Does the script use FOR EACH, FUNCTION, or USE WEBSITE?
  YES β†’ RUNTIME (no pragma)

Does the script have HEAR and may run for > 1 hour?
  YES β†’ WORKFLOW (#workflow)

Is it a tool script called by LLM (short, no HEAR)?
  YES β†’ RUNTIME (no pragma)

Is it a multi-step dialog (ticket, approval, enrollment)?
  YES β†’ WORKFLOW (#workflow)  βš—οΈ when available