bpm-engine
A correctness-first, token-driven workflow execution kernel in Rust.
Designed for deterministic replay and crash-safe long-running processes.
Correctness Over Features
Most BPM engines optimize for features and modeling UX. This engine optimizes for correctness. Every execution step is driven by database state. Every state transition is recorded as history.
Built for Reliability
Six fundamental guarantees that make bpm-engine suitable for mission-critical workflows.
Exactly-Once Completion
Tokens reach final states exactly once. No duplicate executions, no lost state. The engine guarantees atomic completion.
Crash-Safe by Design
Engine recovers deterministically by replaying events. Kill the process, restart, and continue exactly where you left off.
Lease-Based External Tasks
External workers fetch tasks by topic. Leases guarantee exactly one owner at a time with automatic timeout handling.
Persistent Timers
No in-memory timers. All timers are persisted and survive restarts. Naturally scalable across multiple instances.
Full Audit History
Every state change emits an event. History is append-only and globally ordered. Perfect for compliance and debugging.
Formal Invariants
Join nodes wait for all branches. Retries are monotonic. Core behavior is mathematically proven through invariants.
Up and Running in 5 Minutes
Get started with bpm-engine. No Docker required for the default in-memory backend.
# Clone the repository
git clone https://github.com/fanjia1024/bpm-engine.git
cd bpm-engine
# Build the project
cargo build# Start the REST server (http://127.0.0.1:3000)
cargo run -p bpm-engine-server-rest
Built-in process definitions: minimal (Start → End), payment-flow (Start → ExternalTask → End)
# Run a minimal process (Start → End)
cargo run --example simple_process
# Or with curl:
curl -X POST http://127.0.0.1:3000/api/v1/process-instances \
-H "Content-Type: application/json" \
-d '{"process_def_id":"minimal"}'
# Check instance status
curl http://127.0.0.1:3000/api/v1/process-instances/:id
# Get execution history
curl http://127.0.0.1:3000/api/v1/process-instances/:id/history
# Get aggregated trace
curl http://127.0.0.1:3000/api/v1/process-instances/:id/trace
# Get unified step timeline (E6-1)
curl http://127.0.0.1:3000/api/v1/process-instances/:id/stepsuse bpm_worker_sdk::{
EngineClient, ExternalTask, TaskContext,
TaskHandler, TaskResult, Worker, WorkerConfig
};
struct PaymentHandler;
#[async_trait::async_trait]
impl TaskHandler for PaymentHandler {
fn task_type(&self) -> &str { "payment" }
async fn handle(&self, task: ExternalTask, _ctx: TaskContext) -> TaskResult {
// Business logic here
let mut variables = std::collections::HashMap::new();
variables.insert("status".to_string(), "PAID".to_string());
TaskResult::Complete { variables }
}
}
let worker = Worker::builder()
.client(EngineClient::new("http://127.0.0.1:3000"))
.handler(PaymentHandler)
.config(WorkerConfig::new("worker-1")
.poll_interval(std::time::Duration::from_secs(1)))
.build();
worker.start().await;Simple Yet Powerful
A clean, layered architecture that separates concerns and enables flexibility.
┌─────────────────────────────────────────────────────────────────┐
│ bpm-engine │
├─────────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ Scheduler │ │ Token │ │ Invariants │ │
│ │ │ │ Executor │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Persistence Layer │ │
│ │ (in-memory / PostgreSQL) │ │
│ │ Runtime Tables │ History │ Timers │ External Tasks │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ External Workers (fetch / lock / complete via REST API) │
└─────────────────────────────────────────────────────────────────┘
Workspace Crates
A well-organized workspace with clear responsibilities for each crate.
| Crate | Responsibility |
|---|---|
bpm-engine-core |
ProcessDefinition, NodeType, Token, EngineEvent, Saga. Pure logic, no I/O. |
bpm-engine-storage |
Async persistence traits (ProcessInstanceStore, TokenStore, ExternalTaskStore, TimerStore) |
bpm-engine-runtime |
BpmEngine event loop, EngineContext, event handlers, gateway evaluation |
bpm-engine-adapter-memory |
In-memory implementations. Default for development/testing |
bpm-engine-adapter-postgres |
PostgreSQL implementation. Production-ready persistence |
bpm-engine-bpmn |
BPMN 2.0 XML parser → ProcessDefinition compiler |
bpm-engine-server-rest |
HTTP API server (axum). Wires EngineContext with storage adapter |
bpm-engine-worker-sdk |
EngineClient, Worker, TaskHandler. Workers are stateless and horizontally scalable |
REST API
Clean, RESTful API for all engine operations. Base path: /api/v1
| Method | Path | Description |
|---|---|---|
| POST | /process-instances |
Start instance. Body: {"process_def_id", "variables"?: {}} |
| GET | /process-instances/:id |
Get instance status and current nodes |
| GET | /process-instances/:id/history |
Get execution history (events with sequence and category) |
| GET | /process-instances/:id/trace |
Get aggregated trace (token timelines and external-task history) |
| GET | /process-instances/:id/steps |
Get unified step timeline (E6-1: token steps + worker attempts + AI envelope metadata; no variable values) |
| POST | /process-definitions/deploy |
Deploy process from BPMN 2.0 XML (body: raw XML) |
| GET | /tasks?type=user|external |
List waiting tasks |
| POST | /tasks/:task_id/complete |
Complete user task |
| POST | /external-tasks/fetch-and-lock |
Worker: fetch and lock tasks |
| POST | /external-tasks/:task_id/complete |
Worker: complete task |
| POST | /external-tasks/:task_id/fail |
Worker: fail task |
Optional header: x-tenant-id for tenant isolation
Learn More
Comprehensive documentation to help you understand and use bpm-engine effectively.
Reference
Current Status
Core execution semantics
APIs may evolve
Production use cases
The engine is suitable for: Research, Prototyping, Internal Systems, Correctness-focused experimentation