Skip to content

Execution Model

BLOGE executes graphs with a completion-driven scheduler. Ready nodes start immediately, complete independently, and unlock downstream nodes by reducing dependency counts. This keeps the runtime simple, explicit, and well suited to Java 25 virtual threads.

Execution lifecycle

  1. Load or build the graph
    • Java code uses Graph.builder(...)
    • DSL code is compiled into the same runtime Graph model
  2. Initialize dependency counters
    • source nodes (no pending dependencies) enter the ready queue
  3. Bind request context
    • GraphContext is attached through ScopedValue so child virtual threads inherit it safely
  4. Start ready nodes
    • each ready node runs in its own virtual thread
  5. Record outputs and statuses
    • outputs flow into NodeResults
    • node status becomes COMPLETED, FAILED, SKIPPED, CANCELLED, or, in suspendable flows, SUSPENDED
  6. Evaluate downstream edges
    • direct edges decrement dependency counters
    • conditional edges activate only the selected target path
  7. Repeat until the graph finishes
    • the run ends when all terminal work is complete, the graph is cancelled, or failure propagation stops the remaining subtree

Why completion-driven scheduling

BLOGE does not require users to micromanage an executor graph. Instead, it derives concurrency from dependencies:

  • if two nodes do not depend on each other, they can run in parallel
  • if one node fans in from multiple upstream nodes, it runs only after all required outputs exist
  • if a branch chooses one path, the non-selected nodes are marked skipped instead of silently vanishing

This gives the graph a deterministic orchestration contract while still exploiting parallelism.

Virtual threads by default

The runtime starts ready nodes with Thread.startVirtualThread(). That matters because many orchestration nodes are I/O-bound: HTTP calls, database queries, message dispatch, or waits on remote systems.

Benefits:

  • blocking calls do not force teams into callback-heavy designs
  • one graph can fan out to many concurrent nodes without a large thread-pool footprint
  • timeout and retry policies can remain straightforward because blocking remains cheap

BLOGE currently uses direct virtual-thread scheduling and keeps structured concurrency as a future optimization target rather than a hidden runtime dependency.

Scoped request context with ScopedValue

GraphContext is bound through ScopedValue instead of ThreadLocal.

That gives BLOGE three practical advantages:

  • virtual threads inherit context automatically
  • execution context remains immutable in shape and easier to reason about
  • the runtime avoids the classic ThreadLocal cleanup mistakes that show up in request-scoped systems

OperatorContext then exposes the parts that operators should see:

  • current nodeId()
  • current graphName()
  • request-scoped graphContext()
  • retryAttempt()
  • executionId() for correlation and tracing

Failure propagation and control flow

A node failure is not just an exception. It changes downstream scheduling behavior.

Success path

  • node output is recorded
  • downstream dependencies are updated
  • branch edges evaluate and choose the next executable path

Failure path

  • resilience wrappers may retry, timeout, or produce a fallback first
  • if the exception escapes, the node is marked FAILED
  • the scheduler cancels or prevents the affected downstream subtree from executing
  • GraphResult.errors() records the node-level failure detail

Skipped path

If a conditional branch does not select a node, that node becomes SKIPPED. This is important for observability because the result model preserves what could have happened versus what actually ran.

Suspension, waits, and long-running flows

BLOGE can also model workflows that do not finish in one in-memory burst.

Suspendable execution is used for patterns such as:

  • timer-based waits
  • external event correlation
  • user tasks and human approval steps
  • session / phase / round conversational flows

In these cases a suspendable operator returns a structured result instead of throwing. The runtime records suspend keys, partial output, and timeout metadata so execution can resume later through the normal signal or timer pipeline.

Durable runtime integration

When bloge-durable is present, the same execution model can persist:

  • execution identity and status transitions
  • node checkpoints and partial outputs
  • waits and work items
  • routing and shard bindings
  • graph definitions for cold recovery

This allows long-running graphs to survive process restarts without inventing a second programming model for “durable mode.”

Observability surfaces

The execution model is intentionally visible to tooling and telemetry:

  • graph duration metrics
  • node duration metrics
  • retry, timeout, fallback, and error counters
  • structured lifecycle logs
  • graph-level and node-level tracing spans

Those signals line up with the actual scheduling units, so operators can debug orchestration issues from graph structure instead of digging through unrelated infrastructure logs.

Practical implications for graph authors

  • Model parallel work as separate nodes with no dependency edge between them.
  • Put timeouts around any slow external call.
  • Use fallback only when degraded output is still meaningful.
  • Prefer session or durable constructs for long-running waits instead of homemade polling loops in business code.

Next steps