Documents
Workflow Context Management
Workflow Context Management
Type
Topic
Status
Published
Created
May 6, 2026
Updated
May 6, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

Workflow Context Management#

DBOS uses a single module-level ContextVar to store per-execution workflow state in a thread-safe and async-safe way. All context reads and writes go through accessor functions wrapping this variable; no code accesses the ContextVar directly. Context managers at every execution boundary (workflow, step, transaction, handler) install a fresh DBOSContext object on entry and restore the previous one on exit, creating an implicit stack of execution scopes.

_dbos_context_var — the root ContextVar#

_dbos_context_var is the single ContextVar[Optional[DBOSContext]] that holds the context for the currently executing operation. Three thin wrappers are the only intended interface:

assert_current_dbos_context() is a convenience assertion used inside decorators where a context is always expected.

DBOSContext — the state object#

DBOSContext is a plain dataclass-like object (not itself a ContextVar). Each instance captures everything needed to execute one DBOS operation:

FieldPurpose
workflow_idIdentifies the running workflow (empty = outside a workflow)
function_idMonotonically-incremented step counter for deterministic replay
parent_workflow_id / parent_workflow_fidLink to enclosing workflow for child workflows
sql_sessionActive SQLAlchemy session (non-None only inside a transaction)
curr_step_function_idStep in progress (-1 = none)
workflow_deadline_epoch_ms / workflow_timeout_msPropagated deadlines
authenticated_user / authenticated_roles / assumed_roleAuth context propagated into child operations
deduplication_id, priority, queue_partition_key, etc.Queue enqueue options

State predicates — is_workflow(), is_transaction(), is_step() — determine which type of operation is active.

Context propagation: explicit copy, not copy_context()#

Python's contextvars.copy_context() is not used. Instead, DBOS propagates context by constructing DBOSContext child objects explicitly:

  • DBOSContext.create_child() creates a new context inheriting parent_workflow_id, auth fields, and the is_within_set_workflow_id_block flag from the parent.
  • DBOSContext.create_start_workflow_child() is the entry point for spawning child workflows; it increments the parent's function_id and assigns a deterministic child ID ({parent_id}-{function_id}).
  • snapshot_step_ctx() creates an immutable snapshot of the current workflow context for a step call, atomically incrementing function_id on the parent so two concurrent steps never share an ID.
  • The top-level helper snapshot_step_context() returns this snapshot when called from inside a workflow, or the existing context otherwise.

The resulting DBOSContext object is passed as an explicit parameter to the worker function (new_wf_ctx at lines 930–938), where EnterDBOSWorkflow installs it into _dbos_context_var via _set_local_dbos_context().

Context manager hierarchy#

Each execution boundary is wrapped by a context manager that saves/restores _dbos_context_var:

Context managerUsed forKey behavior
EnterDBOSWorkflowWorkflow entry (sync & async)Saves previous context, installs new one, calls ctx.start_workflow() / ctx.end_workflow(), restores on exit
EnterDBOSStepCtxStep executionSwaps in the step snapshot context, calls start_step() / end_step()
EnterDBOSTransactionTransaction executionIncrements function_id, calls start_transaction() / end_transaction() — does not swap context objects
EnterDBOSHandlerHTTP handler entryCreates a minimal context if none exists, tears it down on exit
DBOSContextEnsureGuarantees a context is presentCreates a bare DBOSContext() if none installed

In practice, the sync workflow wrapper chains these via the Outcome.also() / Outcome.then() combinator API . The thread-pool worker _execute_workflow_wthread and the async counterpart _execute_workflow_async both enter EnterDBOSWorkflow as their outermost with block, so each thread/coroutine gets its own isolated _dbos_context_var binding.

For steps called within a workflow, run_step() calls ctx.snapshot_step_ctx() on the live workflow context and passes the snapshot to invoke_step(), which wraps it in EnterDBOSStepCtx .

User-facing context manipulation#

Several public context managers let callers inject configuration before invoking a workflow or queue operation; each saves and restores the relevant fields:

All of these call get_local_dbos_context() at __enter__ time and will create a bare context if one doesn't exist yet.

Key files#

  • dbos/_context.py — entire context system: DBOSContext, the ContextVar, all context managers
  • dbos/_core.py — workflow/step/transaction decorators; calls all of the above context managers
Workflow Context Management | Dosu