v0.2.0 · Rust · Apache-2.0

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/steps
use 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.

Current Status

Stable

Core execution semantics

Evolving

APIs may evolve

Research

Production use cases

The engine is suitable for: Research, Prototyping, Internal Systems, Correctness-focused experimentation