Background jobs and durable workflows for Dart
Define work in Dart. Run it locally, in Flutter, or in a server worker.
Use Stem when work should run outside a request, retry after failure, or wait between steps without keeping a Dart function alive.
- Background tasks — Typed inputs and results, retries, queues, and schedules.
- Workflows — Named checkpoints, durable waits, and retry/compensation policies.
- Storage choices — Start in memory; add SQLite, Redis, or PostgreSQL when you need persistence. Each adapter has its own platform and deployment requirements.
- Operations — Inspect work through the CLI, signals, and OpenTelemetry.
Stem is experimental and pre-1.0. Test your application's failure and recovery paths before production use. Native adapters and isolate workers are not available on every platform; see the portable runtime guide.
| I want to… | Start here |
|---|---|
| Try a workflow without a database or code generation | First workflow below |
| Send typed background tasks to workers | Generated tasks |
| Resume work after a process restart | Persistent workflow hosts |
| Show workflow progress in Flutter | Flutter host bindings |
| Store Flutter work locally | Flutter SQLite setup |
| Choose storage and deployment options | Backend guide |
| Understand the runtime one concept at a time | Documentation |
This checkout targets Dart 3.13+. Repository docs describe the source on this branch; published versions can lag behind it. When using pub.dev, check the documentation and SDK requirements for the version you resolve.
Create a console application and add Stem:
dart create -t console stem_demo
cd stem_demo
dart pub add stemWith a Stem version that includes WorkflowHost, replace bin/stem_demo.dart
with this complete example:
import 'package:stem/stem.dart';
final greeting = HostedWorkflow<String, String>(
name: 'greeting',
run: (context, name) async {
final cleaned = await context.step('normalize', () => name.trim());
return 'Hello, $cleaned!';
},
);
Future<void> main() async {
final host = await WorkflowHost.inMemory(workflows: [greeting]);
try {
final run = await host.submit(greeting, ' Ada ');
print(await run.result);
} finally {
await host.close();
}
}Run dart run. Expected output: Hello, Ada!.
The host starts and owns its runtime and worker. context.step saves a named
checkpoint, and run.result waits for the typed result. The step body runs
locally in the workflow execution; it is not a separately queued remote task.
This example is process-local. In-memory checkpoints disappear when the process exits. To make runs survive a restart, configure durable storage, re-register the workflow definitions, and retain run IDs. Follow the workflow host guide for that next step.
For independent background jobs rather than multi-step orchestration, start with generated typed tasks. For a typed task without code generation, see the core package example.
Define work in Dart. Stem queues it, runs it in workers, and makes task results and workflow state available to your application.
flowchart TB
accTitle: How Stem runs background work
accDescr: Your application submits tasks, workflows, or scheduled jobs. A work queue delivers them to Stem workers, which execute the work and record results and workflow state.
app["Your Dart or Flutter app"]
work["Tasks, workflows, and schedules"]
queue["Work queue"]
workers["Stem workers"]
results[("Results and workflow state")]
app --> work --> queue --> workers --> results
Choose in-memory, SQLite, Redis, or PostgreSQL adapters for the queue and stores. Capabilities vary by adapter; in-memory state is process-local. See the workflow host guide for checkpointing, retries, compensation, and recovery.
| Package | Description | pub.dev |
|---|---|---|
stem |
Core runtime: contracts, worker, scheduler, in-memory adapters, signals, Canvas, workflows | |
stem_cli |
Command-line tooling (stem executable) and CLI utilities |
|
stem_memory |
Compatibility package for the explicit package:stem/memory.dart in-memory library |
|
stem_sqlite |
SQLite queue, results, and workflow storage for local persistence | |
stem_redis |
Redis Streams broker, result backend, and watchdog helpers | |
stem_postgres |
Postgres broker, result backend, and scheduler stores | |
stem_flutter |
Flutter bootstrap, workflow host lifecycle, and progress widgets | |
stem_flutter_sqlite |
Managed SQLite storage and Ormed setup for Flutter Stem apps | |
stem_builder |
Build-time code generator for annotated tasks and workflows | |
stem_adapter_tests |
Shared contract test suites for adapter implementations | |
stem_dashboard |
Hotwire-based operations dashboard (experimental) | — |
| Topic | Guide |
|---|---|
| Task definitions, arguments, and results | Typed tasks and code generation |
| Parallel and sequential task composition | Canvas |
| Delayed and recurring jobs | Scheduling |
| Workflow sleeps, events, retries, and compensation | Workflow hosts |
| Inspecting workers, schedules, and failed deliveries | CLI setup and commands |
| Deployment and failure handling | Production checklist |
- Execution can repeat. Use idempotency keys for external side effects. Checkpoints do not make a payment, email, or HTTP request exactly-once.
- Persistence requires persistent adapters. Keep the queue and relevant stores durable, and keep workflow names, step names, and codecs compatible across deployments.
- Closing a host is not cancelling a run. It stops local observation and owned resources. Cancellation is explicit and does not undo external effects.
- Mobile execution follows OS lifecycle limits. Flutter bindings do not keep an application running after the OS suspends or terminates it.
Stem's package skills teach agents the supported task, workflow, code generation,
Flutter lifecycle, and SQLite patterns. They are shipped under each package's
skills/ directory, not as a separate runtime dependency.
For a dependency version that includes skills:
dart run skills@ get -p stemThe command lets you select the skills to install into your project's agent directory. Skills added in this repository are not available from older pub.dev releases. See AI assistant setup for package selection, local development, and the distinction between shipped package skills and repository contributor instructions.
- Dart 3.13.0+
- Flutter 3.47.0+ (for the local Flutter package gate)
- Docker (for adapter integration tests)
- Nix and devenv 2.2+ (recommended workspace environment)
# Clone the repository
git clone https://github.com/kingwill101/stem.git
cd stem
# Enter the pinned workspace environment
devenv shell
# Discover the workspace and run the centralized quality/test gate
stem-workspace
stem-quality
stem-standalone
stem-testThe root repodoc package owns workspace discovery,
dependency resolution, tests, coverage, examples, standalone package
resolution, and job profiling. The devenv.nix scripts expose those commands
with a cached compiled binary so the same entrypoints are available locally
and in CI. The Taskfile is only a compatibility wrapper; repodoc does not
invoke it.
Integration tests require the Docker test stack:
# Run all package tests with Docker-backed integration env
devenv shell -- stem-test
# Run coverage workflow for core adapters/runtime packages
devenv shell -- stem-coverage
# Run targeted adapter suites (auto-bootstraps integration env)
devenv shell -- repodoc test:contract
devenv shell -- repodoc test:redis
devenv shell -- repodoc test:postgresUse the profiling tasks when investigating runtime performance rather than relying on a single test-suite timing:
# Five fresh AOT trials with medians and p95 summaries.
stem-profile --mode isolate --workload cpu --work-units 250
# Pause before execution and attach Dart DevTools through the VM service.
devenv shell -- repodoc profile:job:vm --mode isolate --workload cpu --work-units 250The AOT profile writes a machine- and commit-stamped JSON artifact under
build/stem-profile/. The VM-service profile is intended for CPU, timeline,
isolate, and allocation inspection; see benchmark/README.md
for the full workflow and scenario options.
Targeted adapter tasks now bootstrap integration environment automatically. If bootstrap still fails (for example Docker unavailable), run:
source ./packages/stem_cli/_init_test_envCapability flags and skip behavior for adapter contract suites are documented in
packages/stem_adapter_tests/README.md.
Contributions are welcome! Please read the contribution guidelines before submitting a PR.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
See CONTRIBUTING.md for the repository's development workflow.
MIT License — see LICENSE for details.
