Workflows
Stem workflows are the durable orchestration layer on top of the normal Stem task runtime. They let you model multi-step business processes, suspend on time or external events, resume on another worker, and inspect the full run state from the store, CLI, or dashboard.
Use this section as the main workflow manual. The older
Core Concepts > Workflows page now just
orients you and links back here.
Pick a workflow style
- Flow: a declared sequence of durable steps. Use this when you want the workflow structure to be the source of truth.
- WorkflowScript: a durable async function. Use this when normal Dart control flow should define the execution plan.
- Annotated workflows with
stem_builder: use annotations and generated workflow refs when you want plain method signatures and less string-based wiring.
Read this section in order
- Getting Started shows the runtime bootstrap and the basic start/wait lifecycle.
- Flows and Scripts explains the execution model difference between declared steps and script checkpoints.
- Starting and Waiting covers named starts, generated workflow refs, results, and cancellation policies.
- Suspensions and Events covers
sleep,awaitEvent, due runs, and external resume flows. - Annotated Workflows covers
stem_builder, context injection, and generated workflow refs. - Context and Serialization documents where context objects are injected and what parameter shapes are supported.
- Errors, Retries, and Idempotency explains replay expectations and how to keep durable code safe.
- How It Works explains the runtime, store, leases, and manifests.
- Observability covers logs, dashboard views, and store inspection.
- Troubleshooting collects the workflow-specific failure modes you are likely to hit first.
Runtime bootstrap
final client = await StemClient.fromUrl(
'redis://127.0.0.1:56379',
adapters: const [StemRedisAdapter(), StemPostgresAdapter()],
overrides: const StemStoreOverrides(
backend: 'redis://127.0.0.1:56379/1',
workflow: 'postgresql://<user>:<password>@127.0.0.1:65432/stem',
),
);
final workflowApp = await client.createWorkflowApp(
flows: [ApprovalsFlow.flow],
scripts: [retryScript],
eventBusFactory: WorkflowEventBusFactory.inMemory(),
workerConfig: const StemWorkerConfig(queue: 'workflow'),
);
await workflowApp.start();
StemWorkflowApp is the recommended entrypoint because it wires:
- the underlying
StemApp - the workflow runtime
- the workflow store
- the workflow event bus
- the managed worker that executes the internal
stem.workflow.runtask
If you already own a StemClient, you can attach workflow support through that
shared client instead of constructing a second app boundary:
final client = await StemClient.fromUrl('memory://');
final workflowApp = await client.createWorkflowApp(
flows: [ApprovalsFlow.flow],
scripts: [retryScript],
);
If your service already owns a StemApp, layer workflows on top of it with
stemApp.createWorkflowApp(...). That path reuses the existing worker, so the
app must already subscribe to the workflow queue plus any task queues the
workflows need.