Getting Started with Obelisk (Rust / WASM)

This guide walks you through building a durable application with Obelisk using Rust compiled to WebAssembly. You'll implement an activity, serial and parallel workflows, and a webhook endpoint in Rust, then see crash recovery in action.

If you prefer to skip the build step, see Getting Started (JavaScript) — it uses the same demo-tutorial but with JS files that run directly without compilation.

1. Install Obelisk

curl -L --tlsv1.2 -sSf https://raw.githubusercontent.com/obeli-sk/obelisk/main/download.sh | bash

See the Installation page for Docker, Nix, cargo-binstall, and other options.

2. Install Rust

Obelisk WASM components are compiled with Rust. Install Rust via rustup:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Add the required WASM targets:

rustup target add wasm32-wasip2 wasm32-unknown-unknown

3. Clone the Tutorial

git clone https://github.com/obeli-sk/demo-tutorial.git
cd demo-tutorial/rust

The rust/ directory contains three Cargo crates and a deployment.toml.

4. The Components

Activity

activity/activity-sleepy/src/lib.rs — the unit of side-effectful work. Activities are retried automatically on failure or timeout, and must be idempotent.

use exports::tutorial::activity::activity_sleepy::Guest;
use wit_bindgen::generate;

generate!({ generate_all });
struct Component;
export!(Component);

impl Guest for Component {
    fn step(idx: u64, sleep_millis: u64) -> Result<u64, ()> {
        println!("Step {idx} started");
        std::thread::sleep(std::time::Duration::from_millis(sleep_millis));
        println!("Step {idx} completed");
        Ok(idx)
    }
}

The WIT interface (wit/tutorial_activity/tutorial_activity.wit) declares the function signature:

package tutorial:activity;

interface activity-sleepy {
    step: func(idx: u64, sleep-millis: u64) -> result<u64>;
}

Return types must be fallible (result) — Obelisk uses this to drive retries and timeouts. See Rust Components for the WIT world and bindgen details.

Workflows

workflow/workflow-tutorial/src/lib.rs defines two functions.

Serial — runs 10 steps in sequence with a persistent 1-second sleep between each:

fn serial() -> Result<u64, ()> {
    let mut acc = 0;
    for i in 0..10 {
        workflow_support::sleep(ScheduleAt::In(Duration::Seconds(1)), None)?;
        let result = step(i, i * 200)?;
        acc += result;
        log::info(&format!("step({i})={result}"));
    }
    Ok(acc)
}

Key concepts:

Parallel — submits all 10 steps concurrently using join sets, then awaits results:

fn parallel() -> Result<u64, ()> {
    let mut handles = Vec::new();
    for i in 0..10u64 {
        let join_set = workflow_support::join_set_create();
        step_submit(&join_set, i, i * 200);  // non-blocking submit
        handles.push((i, join_set));
    }
    let mut acc = 0;
    for (i, join_set) in handles {
        let result = step_await_next(&join_set).unwrap();  // returns the child result directly
        acc = 10 * acc + result?;
        workflow_support::sleep(ScheduleAt::In(Duration::Milliseconds(300)), None)?;
    }
    Ok(acc)
}

step_submit and step_await_next are extension functions automatically generated by Obelisk from the activity's WIT interface.

Webhook Endpoint

webhook/webhook-tutorial/src/lib.rs — serves HTTP and triggers workflows:

#[wstd::http_server]
async fn main(request: Request<Body>) -> Result<Response<Body>, Error> {
    let path = request.uri().path_and_query().unwrap().as_str();
    let response = match path {
        "/serial" => {
            let acc = workflow::serial().unwrap();
            Response::builder().body(Body::from(format!("serial workflow completed: {acc}")))
        }
        "/parallel" => {
            let acc = workflow::parallel().unwrap();
            Response::builder().body(Body::from(format!("parallel workflow completed: {acc}")))
        }
        _ => Response::builder()
            .status(StatusCode::NOT_FOUND)
            .body(Body::from("not found")),
    }?;
    Ok(response)
}

Configuration

deployment.toml wires the compiled WASM files together:

[[activity_wasm]]
name = "activity_sleepy"
location = "target/wasm32-wasip2/release/activity_sleepy.wasm"
exec.lock_expiry.seconds = 10

[[workflow_wasm]]
name = "workflow_tutorial"
location = "target/wasm32-unknown-unknown/workflow/workflow_tutorial.wasm"

[[webhook_endpoint_wasm]]
name = "webhook_tutorial"
location = "target/wasm32-wasip2/webhook/webhook_tutorial.wasm"
routes = ["/*"]

See Configuration for all options.

5. Build and Run

Build all three components:

(cd activity/activity-sleepy && cargo build --release --target wasm32-wasip2)
(cd workflow/workflow-tutorial && cargo build --profile workflow --target wasm32-unknown-unknown)
(cd webhook/webhook-tutorial && cargo build --profile webhook --target wasm32-wasip2)

Start the server:

obelisk server run --deployment deployment.toml

Three endpoints start automatically:

Trigger the serial workflow:

curl http://localhost:9090/serial

After ~20 seconds (10 steps × 1 s sleep each):

serial workflow completed: 45

Trigger the parallel workflow — all steps run concurrently, finishes in a few seconds:

curl http://localhost:9090/parallel
parallel workflow completed: 123456789

6. Inspect Executions

The Web UI at http://localhost:8080 provides a visual trace. Click an execution and enable Autoload children to see the full hierarchy of webhook → workflow → activities.

Serial workflow — steps run one at a time:

Trace view of the serial workflow showing sequential activity executions

Parallel workflow — all steps run concurrently:

Trace view of the parallel workflow showing concurrent activity executions

Via the API. The API port (5005) requires a bearer token, printed to the console on startup; export it first (or start the server with --allow-unauthenticated-api for a throwaway tutorial). See Authentication.

export OBELISK_API_TOKEN=<token printed on server startup>

# List top-level executions
curl -s "http://localhost:5005/v1/executions" -H 'Accept: text/plain' \
  -H "Authorization: Bearer $OBELISK_API_TOKEN"

# Include child executions (workflows + activities)
curl -s "http://localhost:5005/v1/executions?show_derived=true&ffqn_prefix=tutorial" \
  -H 'Accept: text/plain' -H "Authorization: Bearer $OBELISK_API_TOKEN"

Fetch logs for a specific execution:

EXECUTION_ID=E_01...
curl -s "http://localhost:5005/v1/executions/${EXECUTION_ID}/logs" -H 'Accept: text/plain' \
  -H "Authorization: Bearer $OBELISK_API_TOKEN"

7. Crash Recovery

Start the serial workflow, then kill the server while it is running:

# terminal 1
curl http://localhost:9090/serial

# terminal 2 — kill mid-execution
kill $(pgrep obelisk)

The curl request fails — but the workflow state is safely persisted. Restart the server:

obelisk server run

No --deployment flag needed on restart: Obelisk persists the full deployment configuration in the database on first run. It resumes the workflow from its last completed step. Already-completed activities are not re-executed.

This is durable execution: your workflows survive server crashes, restarts, and deployments without losing progress or duplicating work.

Next Steps