Execution Model

WOML separates authoring from execution so the language can remain readable without making the runtime depend on markup, templates, or editor behavior. Understanding this boundary explains why validation is strict, why context is read-only, and why a restarted process can recover durable work.

From file to run

  1. The Bun and TypeScript frontend parses the XML-like source while preserving raw JavaScript inside <script>.
  2. Frontend validation checks structure, attributes, IDs, references, modules, document profiles, and supported behavior.
  3. Compilation lowers the source into a versioned, language-neutral directed acyclic graph.
  4. Rust independently validates the compiled model before accepting it.
  5. The engine admits trigger occurrences, schedules eligible nodes, supervises attempts, and appends durable events.
  6. When a script node becomes ready, Rust sends a bounded invocation to the Bun script host.
  7. An isolated worker executes the asynchronous JavaScript body and returns JSON-compatible data or a typed failure.
  8. Rust records the outcome before scheduling dependent work.

Running woml check exercises the authoring and compiled-model boundary without creating a run. Running woml run additionally activates triggers and the durable runtime.

One execution authority

The compiled model is the interface between every frontend and the core engine. Rust does not parse .woml, resolve {{...}}, interpret embedded JavaScript source, or understand VS Code highlighting.

This means language concerns cannot silently leak into runtime behavior. A fundamental <step>, a reusable step, a lifecycle action, and a managed capability operation all reach the engine through explicit versioned contracts.

The compiled graph is a DAG from the beginning. The current language often creates edges from document order, while primitives such as parallel, fork, choice, and for-each introduce reviewed graph structures. Rust schedules the graph; it does not simply walk markup nodes.

Durable truth

The append-only run event log is authoritative. There is no mutable context object that serves as a second source of truth.

The runtime folds events to derive:

  • The current run status.
  • Completed step outputs.
  • Available context for the next script.
  • Retry and approval state.
  • Terminal presentation and inspection data.
  • Recovery work after restart.

In-memory projections may make this faster, but they can be discarded and rebuilt. A run remains bound to the exact compiled definition admitted at its start, even if the source file is edited while the run is waiting.

What happens when a step succeeds

Consider:

WOML
<step id="prepare">
  <script>
    return { orderId: "order-42" };
  </script>
</step>

The worker's return is not immediately exposed to later work. Rust first validates that it is bounded JSON, records the successful attempt and step outcome, and then makes context.steps.prepare visible to dependent nodes.

This ordering prevents downstream work from observing a result that exists only in process memory.

Crash behavior

Rebuilding context from completed events is safe because it is pure derivation. Replaying an external effect is not automatically safe.

A process can crash after an API accepted a payment but before WOML recorded the response. A recorded step_started event proves that an attempt began; it does not prove whether the external effect happened.

When WOML cannot prove a safe outcome or reattach to a reviewed managed operation, it fails the ambiguous work closed instead of blindly repeating it. Event sourcing provides durable truth, but it does not magically make arbitrary side effects exactly once.

Why this model matters

The architecture gives WOML two useful properties at the same time: authors work with readable markup and normal JavaScript, while the engine retains strict ownership of scheduling, persistence, recovery, and operational state.

See Durability and Recovery for operational details.