
## duroxide
[](https://crates.io/crates/duroxide)
[](https://docs.rs/duroxide)
> **[Latest Release: v0.1.30](https://crates.io/crates/duroxide/0.1.30)** — Sub-orchestration parent-link and collision fixes, safer IDs, and UUID generation.
> See [CHANGELOG.md](CHANGELOG.md#0130---2026-07-29) for release notes.
> **Preview:** This project is currently in preview.
**Duroxide is a lightweight, embeddable durable execution runtime for Rust.**
Write ordinary `async` Rust. Duroxide makes it *durable*: your code keeps
running across process crashes, restarts, and deployments. A workflow that
waits 30 days looks exactly like one that waits 30 milliseconds — and if the
process dies in the middle, it resumes right where it left off, without
re-running the work it already finished.
Inspired by the [Durable Task Framework](https://aka.ms/durabletask)
and [Temporal](https://temporal.io/).
### What you get
- **Durable by default** — every step is recorded; crashes resume from the last completed step.
- **Plain async Rust** — orchestrate with `.await`, control flow, and error handling you already know.
- **Embeddable** — runs in-process on Tokio. No separate server to operate.
- **Storage-agnostic** — a `Provider` trait backs persistence; a SQLite provider (in-memory or file) is built in.
### What you can build
- **Function chaining** — sequential steps where each depends on the last.
- **Fan-out / fan-in** — run many activities in parallel, then aggregate deterministically.
- **Human-in-the-loop** — wait for approvals, callbacks, or webhooks, then resume.
- **Durable timers** — sleep for minutes, hours, or days without holding a thread.
- **Saga compensation** — roll back prior steps on failure.
- **Built-in retries** — configurable backoff and per-attempt timeouts.
- **Cancellation** — in-flight activities receive cooperative cancellation signals.
- **Worker specialization** — route activities to dedicated pools with tags (e.g. `gpu`).
- **Durable KV** — per-instance key/value state that survives replay.
### Install
```toml
[dependencies]
duroxide = { version = "0.1", features = ["sqlite"] } # With the bundled SQLite provider
# OR
duroxide = "0.1" # Core only — bring your own Provider
```
## Examples
### Hello world
```rust
use std::sync::Arc;
use duroxide::{ActivityContext, Client, OrchestrationContext, OrchestrationRegistry, OrchestrationStatus};
use duroxide::runtime::{self};
use duroxide::runtime::registry::ActivityRegistry;
use duroxide::providers::sqlite::SqliteProvider;
# #[tokio::main]
# async fn main() -> Result<(), Box<dyn std::error::Error>> {
let store = Arc::new(SqliteProvider::new("sqlite:./data.db", None).await?);
// An activity does the real work (I/O, HTTP, DB — anything).
let activities = ActivityRegistry::builder()
.register("Hello", |_ctx: ActivityContext, name: String| async move {
Ok(format!("Hello, {name}!"))
})
.build();
// An orchestration coordinates activities deterministically.
let orchestrations = OrchestrationRegistry::builder()
.register("HelloWorld", |ctx: OrchestrationContext, name: String| async move {
let result = ctx.schedule_activity("Hello", name).await?;
Ok::<_, String>(result)
})
.build();
let rt = runtime::Runtime::start_with_store(store.clone(), activities, orchestrations).await;
let client = Client::new(store);
client.start_orchestration("inst-1", "HelloWorld", "Rust").await?;
match client.wait_for_orchestration("inst-1", std::time::Duration::from_secs(5)).await? {
OrchestrationStatus::Completed { output } => assert_eq!(output, "Hello, Rust!"),
other => panic!("unexpected: {other:?}"),
}
rt.shutdown(None).await;
# Ok(())
# }
```
### Surviving crashes
Each completed step is durably recorded, so a restart replays history and
resumes from exactly where it stopped.
```rust
use duroxide::OrchestrationContext;
async fn fulfill_order(ctx: OrchestrationContext, order_id: String) -> Result<String, String> {
// Step 1: charge the card. When this completes, the result is written to history.
let receipt = ctx.schedule_activity("ChargeCard", order_id.clone()).await?;
// 💥 If the process crashes HERE, Duroxide replays on restart:
// `ChargeCard` is NOT charged again — its recorded result is returned
// instantly and execution continues from this exact line.
// Step 2: wait for the warehouse. This can take hours or days; no thread is held.
let _ = ctx.schedule_wait("WarehouseShipped").await;
// 💥 Crash anywhere in this wait? On restart we resume still waiting —
// the timer/event state is durable.
// Step 3: notify the customer.
ctx.schedule_activity("SendShippingEmail", order_id).await?;
Ok(receipt)
}
```
### Fan-out / fan-in
```rust
use duroxide::OrchestrationContext;
async fn fanout(ctx: OrchestrationContext) -> Vec<String> {
let f1 = ctx.schedule_activity("Greet", "Gabbar");
let f2 = ctx.schedule_activity("Greet", "Samba");
// join resolves deterministically by history order, not polling order.
ctx.join(vec![f1, f2])
.await
.into_iter()
.map(|o| o.unwrap_or_else(|e| panic!("activity failed: {e}")))
.collect()
}
```
### Timers and external events
```rust
use duroxide::{Either2, OrchestrationContext};
async fn wait_with_timeout(ctx: OrchestrationContext) -> String {
let timer = ctx.schedule_timer(std::time::Duration::from_secs(60));
let event = ctx.schedule_wait("Approval");
// Use ctx.select2 — NOT tokio::select! — so the outcome is replay-safe.
match ctx.select2(timer, event).await {
Either2::First(()) => "timed out".to_string(),
Either2::Second(data) => data,
}
}
```
### Error handling and compensation
```rust
use duroxide::OrchestrationContext;
async fn with_recovery(ctx: OrchestrationContext) -> String {
match ctx.schedule_activity("Fragile", "input").await {
Ok(v) => v,
Err(e) => {
ctx.trace_warn(format!("fragile failed: {e}"));
ctx.schedule_activity("Compensate", "").await.unwrap()
}
}
}
```
## How it works
Duroxide runs each orchestration **turn by turn**. Every operation gets a
correlation id; scheduling is recorded as a history event (e.g.
`ActivityScheduled`) and completions are matched back by id (e.g.
`ActivityCompleted`). On restart, the runtime **replays** that history to
rebuild in-memory state: completed steps return their recorded results without
re-executing, and the orchestration continues from the first unfinished step.
This is why orchestrations must be **deterministic** — they coordinate, they
don't do I/O. Activities are where side effects happen, and they run at most
once per logical step. A few consequences worth knowing:
- Use `ctx.join` / `ctx.select2` (not `tokio::join!` / `tokio::select!`) so
concurrency resolves by history order, not wall-clock polling.
- Use `ctx.schedule_timer()`, `ctx.new_guid()`, `ctx.utcnow()` instead of
`std::time`, `rand`, or `Uuid::new_v4()` directly.
📖 **For the full story** — how futures are made durable, the replay algorithm
step by step, and nondeterminism detection — read
[Durable Futures Internals](docs/durable-futures-internals.md).
## The Duroxide family
Several related projects share Duroxide's durable-execution model. Pick the one
that fits how you want to author and host your workflows:
- **[pg_durable](https://github.com/microsoft/pg_durable)** — PostgreSQL
extension. Use this when you want durable pipelines and functions
**directly in PostgreSQL**, with no other moving parts.
- **[duroxide](https://github.com/microsoft/duroxide)** _(this repo)_ —
Rust durable-execution runtime. Use this when you want to author workflows in
**Rust** and embed the runtime in your service. Multiple storage providers
are available (SQLite built-in, PostgreSQL via
[duroxide-pg](https://github.com/microsoft/duroxide-pg), or bring your own).
- **[duroxide-python](https://github.com/microsoft/duroxide-python)** — Python
SDK over the duroxide runtime. Use this when you want to author workflows in
**Python**.
- **[duroxide-node](https://github.com/microsoft/duroxide-node)** — Node.js /
TypeScript SDK over the duroxide runtime. Use this when you want to author
workflows in **JavaScript / TypeScript**.
- **[duroxide-pg](https://github.com/microsoft/duroxide-pg)** — PostgreSQL
provider for the duroxide runtime. Plug this into duroxide / duroxide-python /
duroxide-node when you want **PostgreSQL** as the durable store.
## Learn more
- **[Orchestration Guide](docs/ORCHESTRATION-GUIDE.md)** — the complete guide to writing workflows.
- **[Durable Futures Internals](docs/durable-futures-internals.md)** — how replay and durability work under the hood.
- **[Provider Implementation](docs/provider-implementation-guide.md)** / **[Provider Testing](docs/provider-testing-guide.md)** — build and test a custom storage backend.
- **[Observability Guide](docs/observability-guide.md)** — structured logging and metrics.
- **[AI Skills](docs/skills/)** — context files for AI assistants (Copilot, Cursor, etc.).
- **Examples** — `cargo run --example hello_world`, plus more in [`examples/`](examples/) and [`tests/e2e_samples.rs`](tests/e2e_samples.rs).
## Development
```bash
cargo build --all-features # Build
cargo nt # Run regular tests with nextest
cargo test --doc --all-features # Run doctests
./run-tests.sh # Full two-pass CI test suite
./run-stress-tests.sh # Stress tests (see STRESS_TEST_MONITORING.md)
```
See [CHANGELOG.md](CHANGELOG.md) for release notes and
[CONTRIBUTING.md](CONTRIBUTING.md) to get involved.
## Support
Use GitHub Issues for bug reports and feature requests. Do not report security vulnerabilities through public GitHub issues; follow the instructions in [SECURITY.md](SECURITY.md) instead.
## Code of Conduct
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information, see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or comments.
## Security
Microsoft takes the security of our software products and services seriously. Please do not report security vulnerabilities through public GitHub issues. See [SECURITY.md](SECURITY.md) for security reporting instructions.
## Privacy and Telemetry
Duroxide does not send telemetry to Microsoft. Applications may configure their own logging or metrics exporters; those signals are controlled by the application owner.
## Trademarks
This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos is subject to those third-party policies.
## License
MIT License - see [LICENSE](LICENSE) for details.