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
- Load or build the graph
- Java code uses
Graph.builder(...) - DSL code is compiled into the same runtime
Graphmodel
- Java code uses
- Initialize dependency counters
- source nodes (no pending dependencies) enter the ready queue
- Bind request context
GraphContextis attached throughScopedValueso child virtual threads inherit it safely
- Start ready nodes
- each ready node runs in its own virtual thread
- Record outputs and statuses
- outputs flow into
NodeResults - node status becomes
COMPLETED,FAILED,SKIPPED,CANCELLED, or, in suspendable flows,SUSPENDED
- outputs flow into
- Evaluate downstream edges
- direct edges decrement dependency counters
- conditional edges activate only the selected target path
- 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
- Configure the failure-handling envelope in Resilience Policies
- Add persistence in Durable Flows
- Instrument production runs in Observability