-`workflow_executions` and `workflow_events` tables exist in DB
-`WorkflowExecution` / `WorkflowEvent` models exist in `core/shared/models/workflow_models.rs`
-`ORCHESTRATE WORKFLOW` keyword exists in `basic/keywords/orchestration.rs` (stub)
-`STEP` keyword registered but not durable
- Compiler (`basic/mod.rs`) produces Rhai AST and runs it in one shot via `engine.eval_ast_with_scope`
-`HEAR` currently blocks a thread (new) — works but not crash-safe
---
## Goal
BASIC scripts run as **durable step sequences**. Each keyword is a step. On crash/restart, execution resumes from the last completed step. No re-run. No Rhai for control flow.
```basic
' ticket.bas
TALK "Describe the issue" ← Step 1
HEAR description ← Step 2 (suspends, waits)
SET ticket = CREATE(description) ← Step 3
TALK "Ticket #{ticket} created" ← Step 4
```
---
## Two Execution Modes
The compiler serves both modes via a pragma at the top of the `.bas` file:
```basic
' Default: Rhai mode (current behavior, fast, no durability)
TALK "Hello"
' Workflow mode (durable, crash-safe)
#workflow
TALK "Hello"
HEAR name
```
`ScriptService::compile()` detects `#workflow` and returns either:
-`ExecutionPlan::Rhai(AST)` — current path, unchanged
Expressions inside steps (`condition`, `expression`, `template`) are still evaluated by Rhai — but only as **pure expression evaluator**, no custom syntax, no side effects. This keeps Rhai as a math/string engine only.
**Why custom over Restate:** Restate requires its own server as a proxy between HTTP requests and handlers — adds a network hop and an extra process. The custom plan uses PostgreSQL already running in the stack, zero extra infrastructure.
**Escape hatch:** The `Step` enum in this plan maps 1:1 to Restate workflow steps. If the custom engine proves too complex to maintain, migration to Restate is mechanical — swap `WorkflowEngine::execute_step` internals, keep the compiler and Step enum unchanged.