Graphrun
Graphrun is an embedded durable workflow engine for Rust. Depend on graphrun, open Engine::local on a directory in your process, and drive runs through that API. It uses your Tokio runtime. It does not start a server or need a database daemon.
This is from the author of Workflow Core. Same job (long-running graphs, waits, sagas) as an embeddable library: the graph is data, local mode is one Raft voter on a redb file, no separate workflow server.
The graph is data: YAML or a typed builder, both the same IR. Commands go through Raft onto a redb file in that directory. Restart the process on the same path and the run is still there.
This release does not let you plug in your own activity handlers. A catalog names contracts (counter.increment, inventory.reserve, …). Local mode runs built-in fixtures for those names. An unknown name echoes its input and succeeds. Use Graphrun to persist and orchestrate those graphs; do not treat a custom catalog name as user code that ran.
Install
[]
= "0.1"
= { = "1", = ["derive"] }
= { = "1", = ["rt-multi-thread", "macros"] }
Rust 1.90 or newer. Linux and macOS. 0.1 resolves to the latest unyanked 0.1.x on crates.io.
First run
Paste this into src/main.rs. Input {value: 0} finishes at {value: 2}.
use ;
use BTreeMap;
use Duration;
const CATALOG: & = br#"{
"format": "graphrun.catalog/v1",
"schemas": {
"counter/v1": {
"type": "object",
"required": ["value"],
"additionalProperties": false,
"properties": {"value": {"type": "integer"}}
}
},
"activities": [
{
"name": "counter.increment",
"version": 1,
"input_schema": "counter/v1",
"output_schema": "counter/v1",
"execution": "async",
"effects": "pure",
"recovery": "RetrySafe",
"error_codes": []
}
]
}"#;
const YAML: &str = r#"
dsl: graphrun/v1
id: hello_world
version: 1
input_schema: counter/v1
output_schema: counter/v1
start: hello
nodes:
hello:
kind: activity
activity: {name: counter.increment, version: 1}
input: {from: workflow.input}
next: goodbye
goodbye:
kind: activity
activity: {name: counter.increment, version: 1}
input: {from: nodes.hello.output}
next: finish
finish:
kind: complete
output: {from: nodes.goodbye.output}
"#;
async
A catalog is the registry of payload schemas and activity contracts the graph may name. It is not the workflow and not your handler code.
Crate examples (also on docs.rs source):
hello_world prints {value: 2}. order prints payment_id: pay-1.
What to read next
Do these in order. Each sample folder is self-contained (workflow.yaml, catalog.json, payload types, builder).
- 01 Hello World — the graph above, plus the typed builder
- 02 Passing Data — order → reserve → charge
- 03 Events —
wait_signalandEngine::signal
Then pick from 04–10: while, foreach, choose, parallel, saga, repeat, timeout recovery.
How-tos:
- Build a workflow in Rust
- Run YAML from your process
- Wait for an external event
- Inspect a stalled run
- Compensate or abandon
- Back up and restore
- Run a three-member cluster
Typed builder
payload!(Order, "order") binds a struct to catalog schema order/v1. First .activity uses the run input; the next uses the previous output. Same IR as the YAML.
The full listing is samples/02-passing-data.
let reserve = catalog.?;
let charge = catalog.?;
let definition =
.activity?
.activity?
.finish?;
let run = engine.start.await?;
Optional CLI
graphrun-cli inspects and signals an engine you already opened. Your application does not depend on it.
Cluster
After a local run works: Engine::member is a storage replica, Engine::run_worker claims activities over gRPC/mTLS. Same write path as local. Cluster how-to.
Limits
A definition may have 512 nodes. YAML, payloads, and IR are each 256 KiB. docs/limits.md.