Following my posts on determinism and durable function trees, this installment advances this blog post series “The Theory of Durable Execution”.
Durable execution engines (DEEs) talk about “workflows”, “activities”, “virtual objects”, “handlers”, and “functions”, but they’re often describing the same underlying execution patterns. This post proposes a model that extends the generic durable function into three forms: stateless functions, sessions, and actors.
I’ll cover this in three parts:
The behavior-state continuum
The three durable function forms and associated properties
Mapping the DE frameworks to these forms
The Behavior-State Continuum
We can think about the mix of computation and data in ordinary programming along a simple behavior–state continuum:
Fig 1. (behavior only) -> (behavior with state) -> (state with behavior) -> (state-only)
These points correspond to familiar constructs:
Pure behavior: Like a stateless function in Functional Programming. A piece of logic that runs to completion with no retained state.
Behavior with private state: An object that represents an ongoing computation, such as a worker loop, coroutine, or background task. It maintains private mutable fields (counters, flags, intermediate data) across steps, and other threads or components may interact with it while it runs. Its state exists only for the lifetime of the process it represents.
State with mutation behavior: An entity-like class whose primary purpose is to hold long-lived state, with methods that mutate that state, such as a domain object, state machine or actor.
State-only: Passive data structures like DTOs and events, which carry state but define no behavior.
This continuum provides a conceptual logic space: from pure computation, to computations that unfold over time, to long-lived stateful objects, to pure data. In the next section, we’ll map this continuum onto the three forms of durable function.
The Three Durable Function Forms
Durable execution occupies the first three points of the behavior–state continuum. Pure state-only objects (DTOs, events) don’t execute anything, so they fall outside the model. What matters are the computational forms that do run and whose behavior must survive crashes, restarts, and replay.
This gives us three forms of durable function:
Stateless function: Durable one-shot behavior.
Session: Interactive, long-running process with an execution identity and execution-scoped mutable state. One function to run the workflow, with one or more functions for interactivity.
Actor: Durable object with a persistent identity and persistent state which exposes one or more functions for state mutation or other reactive logic. It exists independently of any execution.
Fig 2. The three function forms.
I think session could be replaced with workflow or process, but I like session as it implies it is a longer-lived function that can be interacted with during its lifetime. Workflow/process are so overloaded that it becomes less useful in this conceptual model.
The different function forms can also be defined by some properties:
Publicly addressable: no | yes
Identity type: execution-id | entity-id
Lifetime: bounded | unbounded
Communication: one-shot | ongoing
Note: “Publicly addressable” refers to an already executing function instance.
Fig 3. How the forms relate to different properties.
Let’s explore the three forms further, referring back to these properties.
Stateless Function
A Stateless Function is a one-shot process with a start and an end, with no further interactivity.
Fig 4. A stateless function is invoked and runs until completion.
It has no public addressable identity as it doesn’t need one, only the DEE needs the id. Each execution is a one-shot process. The invoker does not come back to interact with the running function (only to await its result if it chooses to).
Any state it has is incidental, enough for the durable execution engine to run it to completion. Thus any state is generally the result of side effects for memoization. The state does not need to be retained beyond the life of the execution (though in practice it might be for observability and auditing).
Example: A payment processing function deployed as a web service or lambda function. It receives order details, checks that the discount is valid, calls a payment gateway API, and returns success or failure. If it crashes mid-execution, the durable execution engine reruns it from the start using memoized results from any completed steps. Either it's a one-way invocation or the caller asynchronously awaits the durable promise (see The Durable Function Tree post). There is no way to query its state or interact with it while running.
Session
A session (or workflow if you prefer) is an interactive process which has a start and an end, which can maintain mutable state. Its public identity is in the form of an execution ID. A session is kicked off by invoking its main function, and can be interacted with via message passing or by invoking secondary functions.
Fig 5. The session is kicked off by a main function and can be interacted with via secondary functions or message passing. It runs until completion.
External parties perform these interactions via its public identity. The identity and state is temporary in nature as it represents a process, it only needs to be maintained for as long as the process is running or any dependencies that might await its completion (with the same caveat of keeping state around for observability and auditing). The process self-terminates when it reaches the end of its main function.
Example: A loan application workflow orchestrates multiple steps: credit check, document verification, risk assessment and a manual approval. While running, via secondary functions, a customer service agent can query its current progress, cancel it, or an external approval can update its approval status. It has an execution ID (like loan-app-12345) that need only exist while the loan application is being processed. Once complete, the execution terminates and the ID becomes historical.
Actor
An actor is a long-lived stateful object with a persistent identity that identifies it as a “thing”. It offers one or more functions and executes function invocations one at a time, updating its state and/or executing side effects. Due to its unbounded lifetime, it must be explicitly deleted. As long as the actor exists, it can be addressed, interacted with via its functions, and its state can evolve.
Fig 6. An actor offers multiple functions for state mutations and reactive logic.
Example: A shopping cart exists as a durable entity with a persistent key (like cart-user-789). Customers can add items, remove items, or view contents through function calls, even across multiple web sessions spanning days, weeks or months. The cart lives on independently of any particular execution. It continues to exist until explicitly deleted or garbage collected, maintaining its state between interactions.
Concurrency
Instances of each can be triggered (created) concurrently, but the concurrency model of each running instance is different:
Stateless Function: Although many stateless functions may run in parallel, each individual execution is driven by the DEE as a single-threaded control flow, replayed and advanced as if it were invoked only once. The function may use internal concurrency, but its durable control flow is never executed concurrently with itself.
Session: The session’s main function is driven forward exactly like a stateless function: single-threaded durable control flow with no concurrent re-entry. However, a session can receive concurrent external interactions (via secondary functions), which are processed at defined suspension points (see the durable function tree post).
Actor: The functions for a given actor (identified by its key) are executed serially (across the functions). This corresponds directly to the actor mailbox model, ensuring single-threaded mutation of the actor’s state. As such, each function execution should be short-lived.
Durable function trees with mixed forms
In The Durable Function Tree I explained how durable execution tends to form trees where functions call functions. Now imagine a durable function tree where any function in the tree could be any of these three forms.
Session: CarRentalWorkflow
├─ Stateless Function: ValidateCustomer
├─ Actor: Car (key: car-789)
│ └─ function: hold(startDate, endDate, holdPeriod)
├─ Stateless Function: ProcessPayment
└─ Actor: Car (key: car-789)
└─ function: confirmReservation()
In this example, the car actor can use a durable timer to expire the hold on the car. It does not only require external parties to drive its behavior.
Seeing the tree as a mix of forms helps clarify the semantics:
Stateless functions perform one-shot logic
Sessions provide interactive orchestration
Actors represent long-lived logical participants that hold persistent state and process messages (or method calls) over an unbounded lifetime
Mapping Durable Execution Engines to the Model
This conceptual model is a guide. In reality there can be some ambiguity, especially between stateless functions and sessions. Whether a function is more stateless or interactive session-based one can depend on what features of the engine you use.
Temporal
Temporal workflows are full-featured sessions with execution-scoped identity, with signals and updates for externally driven mutation and queries for state inspection. A workflow, like a loan application, can run for hours, days to months, with customer service agents sending cancellation signals or monitoring systems querying its progress.
Activities, by contrast, are similar to stateless functions that execute side effects (API calls, database operations) and return results, with no mechanism for external interaction during execution. However, an activity is not directly invocable from the outside, so it is not a clean mapping to the stateless function concept.
Fig 7. A Temporal workflow as a session with the main function annotated with @workflow and secondary functions for interaction annotated with @signal.
Restate
Restate thinks in terms of services and handlers. There are three service types, which in turn have one or more handlers (functions/methods).
A Basic Service has one or more independent handlers, where each handler maps closest to the stateless function. Once invoked, a handler’s execution cannot be interacted with. The Service is a way of grouping related handlers together into one application.
Fig 8. A basic service with four independent handlers.
A Workflow service has a single run handler (to kick off the workflow), and maps to the session. The service may have multiple handlers and they all exist to interact with the workflow. Handlers are either exclusive or shared, which determines the concurrency of the logic they execute. The run handler executes the workflow as a series of actions (side effects), such as the credit check, document check, etc, from the earlier example. Shared handlers are read-only (used for queries) and can execute concurrently. Exclusive handlers (for mutating its state) cannot run concurrently, they are invoked serially by the Restate Service. When the run handler is suspended, it allows for other exclusive handlers to be invoked.
Fig 9. Restate workflow with its run handler and secondary handlers for interactivity.
A Virtual Object service maps to a collection of actors, such as a collection of shopping carts, with persistent key-based identity per actor instance ("cart-user-123"). Each virtual object has exclusive write handlers for mutating the state and shared read handlers for querying the state.
Fig 10. A Restate virtual object with handlers for mutating its state and possibly executing side effects.
DBOS
DBOS centers its model on workflows as sessions. Workflows execute a series of transactions and/or steps within the session context (local-context side effects). Workflows are the unit of durable execution, with support for send/recv messaging and event publishing that enables external interaction. A DBOS workflow can block (and suspend) to await a message via a DBOS queue (like manager approval) before continuing, fitting the session pattern.
Fig 11. A DBOS workflow
Resonate
Resonate takes a more minimalist approach built on durable promises as the foundational abstraction (enabling durable async/await). Functions are primarily stateless as they execute, return promises, and compose into larger computations. However, Resonate supports session semantics through blocking promises: a function can create a promise, block on it, and have external parties resolve it later with data. This enables human-in-the-loop workflows and approval processes.
Final Thoughts
While we typically just talk about durable functions or workflows as externally triggerable units of durable execution, we can break things down into three function forms by thinking in terms of the behavior-state continuum:
Stateless functions for one-shot deterministic logic.
Sessions for long-running, interactive orchestration.
Actors for persistent, message-driven state.
In all cases, no matter the form:
The execution logic is deterministic control flow + non-deterministic side effects
The logic is made reliable through re-execution + memoization
Function forms can be composed into mixed form trees.
The three forms sit on the behavior-state continuum, but they also are points in a multidimensional space where properties like identity, lifetime, communication, and concurrency are the dimensions. The DE engines come with mechanics for each of these properties to a greater or lesser extent. We can combine these properties to make many hybrids that don't neatly fit this categorization. For example, we could make sessions unbounded. This is something that a Temporal workflow can simulate via continue-as-new to create a new session (workflow) based on the current one when its history has grown too large. However, I find it helpful to outline these forms to make it simpler to reason about the types of executable distributed programs that can be built.
This concludes, for now, my series “The Theory of Durable Execution” where I have attempted to reduce the diverse frameworks down to a simpler logical model.
Durable functions as deterministic control flow with one or more idempotent/duplication-tolerant side effects
Function reliability from deterministic replay + memoization
The Durable Function Tree - Part 1, The Durable Function Tree - Part 2
Function composition via the durable function-tree (with suspension points on remote-context side effects)
The Three Durable Function Forms (this post)
Extending durable functions (via the behavior-state continuum) to include three forms (stateless functions, sessions, actors)