← Back to work

Workflow Automation Engine

Core contributor · backend + frontend
Execution engine over a directed step graph: 21 step types (conditions, loops, sub-workflows)
Isolated sandbox (128MB per isolate) with a custom DSL transpiled via Babel
Temporal-style replay to verify determinism — critical for a financial system

Workflow automation · Execution engine · B2B fintech (US)

A visual workflow automation platform — Zapier-like, purpose-built for a regulated financial domain. Every run executes inside an isolated VM with a hard memory cap, user logic is compiled through a custom DSL instead of running as raw JavaScript, and any past run can be replayed step by step to verify it produces an identical result. It was the highest-load service in the stack. I worked across both the execution engine and the visual editor.

20+step types
128MBmemory cap per isolate

What problem does the Workflow Automation Engine solve?

Business operations in lending involve long, branching processes: pull data from external providers, apply decision rules, transform payloads between formats, call partner APIs, wait on asynchronous responses, escalate, retry. These flows changed constantly and varied per client, so hard-coding them was untenable. They needed to be configurable by non-engineers — but they also handle real money, which means “configurable” couldn’t mean “unsafe” or “unpredictable.”

How does the execution engine work?

The visual editor is a node-based canvas where users compose flows from typed steps and connect them into branching paths, with inline code editing for the logic inside each step.

The execution engine is the substantial part. It runs a directed step graph with real control flow — over 20 step types, including conditions, decision tables, loops, jumps, parallel path splits, nested sub-workflows, external API calls, database queries, data mapping and transformation, formula evaluation, file operations, and asynchronous pause/resume. Because it supports loops and jumps, the graph is genuinely cyclic — not a DAG — so the engine resolves the next step at runtime on each iteration rather than pre-computing a topological order.

What technical decisions underpin the Workflow Automation Engine?

Running user code safely

An isolated VM per run, with a hard memory cap

Users write logic that executes in a financial production system. Raw eval or a soft sandbox wasn't acceptable. Each execution runs inside an isolated VM instance with a hard 128 MB memory cap and a deliberately minimal runtime surface.

  • Hard 128 MB cap per isolate, minimal and controlled runtime surface
  • No shared state between executions — one workflow can't read another run's data
  • Concurrency bound by memory, not CPU → scales horizontally, not vertically
A language of its own, not raw JavaScript

A DSL compiled through a transpiler

User logic is written in a small domain-specific language, parsed and compiled through a transpiler with a custom compiler plugin — visitors for declarations, expressions, identifiers, member access — into safe executable code.

  • Full control over exactly what users can express, without exposing raw JavaScript
  • Domain-specific error messages instead of raw JS stack traces
  • The language can evolve and recompile without exposing the underlying runtime
Determinism, proven

Replay: from an assumption to something continuously verified

The property that mattered most: the same input must always produce the same output. A workflow that silently behaves differently on re-run is a correctness and audit problem in a financial system.

  • Replays any past execution instruction by instruction against the original result
  • Explicit failure state: "the replay diverged, so this execution was non-deterministic"
  • Shadow-execution mode to compare runs side by side

Asynchronous execution, in production

Long-running flows are dispatched to a message queue and processed by the same service running as a consumer — the same image serves HTTP requests synchronously and consumes the queue asynchronously, with the role determined by how it starts rather than by two separate codebases. Failed messages retry with backoff and fall through to a dead-letter queue, with execution status persisted throughout.

Closing

This was the highest-load service in the platform, running business-critical financial workflows in production, scaled horizontally to handle peak client volume. What matters most to me here isn’t the list of step types — it’s the discipline of treating determinism as something you prove, not something you assume, in a system where a run that can’t be reproduced is, plainly, an audit problem.