Graphrun
Graphrun is an embedded 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.
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.
From this repository, the same idea as a crate example:
That prints payment_id: pay-1 (inventory.reserve then payment.charge).
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.