4.4 KiB
BASIC Execution Modes: RUNTIME vs WORKFLOW
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
| Feature | RUNTIME mode (default) | WORKFLOW mode ⚗️ |
|---|---|---|
| Pragma | (none) | #workflow |
| Engine | Rhai AST | Step engine + PostgreSQL |
| HEAR behavior | Blocks a thread | Suspends to DB, zero threads |
| Server restart | Loses position | Resumes 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 session | 1 thread (~64KB) | 0 threads |
| Observability | Logs only | DB rows (queryable) |
| Best for | Tools, short dialogs | Multi-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
| Category | Keywords | WORKFLOW support |
|---|---|---|
| Dialog | TALK, HEAR, WAIT |
✅ |
| Data | SET, GET, FIND, SAVE, INSERT, UPDATE, DELETE |
✅ |
| Communication | SEND MAIL, SEND TEMPLATE, SMS |
✅ |
| AI | USE KB, USE TOOL, REMEMBER, THINK KB |
✅ |
| HTTP | GET (http), POST, PUT, PATCH, DELETE (http) |
✅ |
| Scheduling | SCHEDULE, BOOK, CREATE TASK |
✅ |
| Expressions | FORMAT, math, datetime, string functions |
✅ (via Rhai eval) |
| Control flow | IF/ELSE/END IF |
✅ |
| Loops | FOR EACH / NEXT |
❌ use RUNTIME |
| Procedures | FUNCTION, SUB, CALL |
❌ use RUNTIME |
| Browser | USE WEBSITE |
❌ use RUNTIME |
| Events | ON 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