Execution Model: VM, JIT, and Host Calls
JSON rules are not executable by themselves — they are input data. What actually runs is native code compiled into the server binary, plus bytecode VM or JIT code for expression evaluation.
Ordo's execution chain has four layers:
- Rule data — JSON generated by the platform
- Step scheduler — native code that walks the step graph
- Expression layer — interpreter, bytecode VM, or JIT
- Capability layer — providers for HTTP, metrics, audit, and other side effects
Capabilities are not a replacement for the VM. They are host-call boundaries in the execution chain.
From JSON to the CPU
At runtime:
- The server reads rule JSON
- It deserializes it into
RuleSet,Step, andActionKind - A native executor schedules and runs steps
- Expressions inside those steps are evaluated by the expression layer
- Outbound behavior crosses into capability providers
Step scheduling is native Rust. VM and JIT handle expression evaluation. Capabilities handle interaction with the outside world.
What the step scheduler does
The step scheduler decides:
- which step is active
- which branch a decision step should take
- which actions an action step should execute
- when execution reaches a terminal step
This is a native state machine loop — not the VM.
What the expression VM does
The bytecode VM handles expression evaluation:
- branch conditions
- the right-hand side of
set_variable - metric values
- terminal outputs
- parameter expressions inside
ExternalCall
BytecodeVM is a classic dispatch loop: load an instruction, branch on the opcode, read or write register slots. The CPU is executing native Rust machine code that happens to interpret expression bytecode.
Relevant source:
What the JIT does
Both the VM and JIT execute native machine code. The difference is that the VM dispatches instruction by instruction, while the JIT emits machine code for hot expressions up front and lets the CPU run that code directly.
The JIT accelerates pure compute paths:
- numeric comparisons
- boolean logic
- field access
- expression composition
It does not perform network IO, audit logging, or metrics emission.
What a host call is
An outbound capability is not just another opcode — it is a host function call.
The flow:
- The executor computes request parameters locally
- It calls
capability_invoker.invoke(...) - The provider performs the real runtime behavior
- The result is returned into rule context
This is the same boundary you see in WASM host imports, Lua calling C functions, JVM calling JNI, or a database execution plan invoking an external function.
How ExternalCall participates in execution
In the current execution model, ExternalCall works like this:
- Read
service,method, andparamsfrom the action - Evaluate each parameter expression
- Build a
CapabilityRequest - Invoke the capability boundary
- If
result_variableis set, write the response back into context
Implementation:
Example: network.http
When a rule calls network.http:
- The executor evaluates
url,json_body, and other parameters intoValue - It sends a capability request
- The
network.httpprovider makes the actual HTTP request throughreqwest - The provider wraps the response into
CapabilityResponse - Later rule steps read
$result.payload
Real network IO happens in the host layer. The VM or JIT only computes the URL, body, headers, and any expressions over the returned result. Sockets, timeouts, Tokio scheduling, and syscalls belong to the host runtime.
What's supported today
ExternalCall now works in both the interpreter path and the compiled executor. Built-in capabilities include network.http, metrics.prometheus, and audit.logger.
That means a rule can stay on the compiled execution path, keep using the VM or JIT for parameter evaluation, and only cross into the host runtime at the action boundary.
Where this should go
The foundation is now in place. The next step is not "make the VM do HTTP", but to deepen the host-call model across the rest of the platform:
- Move more side effects behind capability boundaries
- Expand compiled executor coverage for host-call actions
- Strengthen timeout, retry, breaker, and observability policies at the capability layer
- Keep platform-generated rule models aligned with runtime capability support
VM and JIT handle fast computation. Capabilities handle crossing the engine boundary. They are complementary, not competing.