Architecture

From one workflow to a system of workflows

How WOML projects use modules, synchronous calls, background starts, and durable events to split complex work into components that remain understandable.

6 min read

The first useful workflow is often one file. A larger system should not become one enormous file. WOML provides several forms of composition so each component can keep a focused contract while the runtime coordinates the complete process.

Start with the smallest useful boundary

A step is the smallest durable unit of authored work. A local JavaScript or TypeScript module is appropriate when several steps need the same calculation or API wrapper. A separate workflow becomes useful when work needs its own trigger, policy, history, deployment boundary, or independent lifecycle.

These boundaries can evolve. Extracting a module or child workflow does not require replacing WOML with another orchestration model.

Reuse local code with modules

The imports section exposes named exports from a local JavaScript or TypeScript module under services. The workflow still shows where the module participates, while implementation details stay in a normal source file that can be tested and reused.

Modules support static relative imports. They do not install packages at runtime or hide initialization side effects, which keeps the packaged workflow deterministic.

customer-onboarding.woml · WOML
<imports>
  <module name="customers" from="./modules/customers.ts" />
</imports>

<steps>
  <step id="normalize" name="Normalize customer">
    <script>
      return services.customers.normalize(context.payload);
    </script>
  </step>
</steps>

Choose how workflows communicate

Use services.workflows.call() when the parent needs the child workflow's final JSON result before it can continue. The call is durable, rejects cycles, and applies the child workflow's own runtime policies. A synchronous call cannot target a workflow containing Human Approval because the JavaScript continuation cannot be serialized across a long approval wait.

Use services.workflows.start() when the parent only needs durable admission. The parent receives the child run ID and continues while the child runs independently. Its later failure does not rewrite the already completed parent step.

Use services.events.emit() when a fact should fan out to every active subscriber. Events decouple the publisher from the number and identity of consumers while preserving durable publication and delivery results.

customer-onboarding.woml · assessRisk · JavaScript
const risk = await services.workflows.call(
  "calculate-risk",
  { customerId: context.steps.customer.id },
  { name: "calculate-customer-risk", timeout: "30s" }
);

return { customerId: context.steps.customer.id, risk };

A large WOML system is not one larger canvas. It is a set of readable workflows with explicit ways to communicate.

Operate the system as one project

A workflow.json project manifest joins workflow discovery with operator-owned configuration. It can declare the workflows, policies, reviewers, and execution targets that belong together while secret values remain outside source control.

That project can run locally and later use durable artifacts, waits, approvals, infrastructure policies, streamed output, OpenTelemetry, and authenticated remote targets. Composition changes the size of the system without changing how its source is reviewed.

Topics

  • workflow composition
  • modularity
  • events
  • system design

Continue reading

View all articles →

Next · Runtime

Durable execution is more than retrying a function

Retries matter, but durable workflows also need stable history, immutable definitions, honest recovery, and a clear answer when an external effect may have happened.

Read the article →