Getting Started with Obelisk

This guide walks you through running a durable application with Obelisk — a workflow engine that automatically recovers your program from crashes. You'll write a step activity, serial and parallel workflows, and a webhook endpoint in JavaScript, then see crash recovery in action.

No build step or toolchain required — just install Obelisk and run.

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. Clone the Tutorial

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

The repository contains four JavaScript files and a deployment.toml — nothing else to install or compile.

3. The Components

Activity

activity/step.js — the unit of side-effectful work. Activities are retried automatically on timeout or failure.

// tutorial:demo/activity.step: func(idx: u64, sleep-millis: u64) -> result<string>
export default async function step(idx, sleep_millis) {
  console.log(`Step ${idx} started`); // logs are persisted
  await new Promise((r) => setTimeout(r, Number(sleep_millis)));
  console.log(`Step ${idx} completed`);
  return String(idx);
}

Workflows

Workflows orchestrate activities. They must be deterministic — given the same inputs and event history they always produce the same sequence of calls. This lets Obelisk recover them from crashes by replaying the execution log.

workflow/serial.js — runs 10 steps in sequence with a persistent 1-second sleep between each:

// tutorial:demo/workflow.serial: func() -> result<string, string>
export default function serial() {
  let acc = 0;
  for (let i = 0; i < 10; i++) {
    obelisk.sleep({ seconds: 1 });
    const result = obelisk.call("tutorial:demo/activity.step", [i, i * 200]);
    acc += Number(result);
    console.log(`step(${i})=${result}`);
  }
  console.log(`serial completed: ${acc}`);
  return String(acc);
}

Key concepts:

workflow/parallel.js — submits all 10 steps concurrently using join sets, then awaits results:

// tutorial:demo/workflow.parallel: func() -> result<string, string>
export default function parallel() {
  const joinSets = [];
  for (let i = 0; i < 10; i++) {
    const js = obelisk.createJoinSet();
    js.submit("tutorial:demo/activity.step", [i, i * 200]);
    joinSets.push({ i, js });
  }
  console.log("parallel: submitted all child executions");
  let acc = 0;
  for (const { i, js } of joinSets) {
    const response = js.joinNext();
    if (!response.ok) throw `step ${i} failed`;
    const result = obelisk.getResult(response.id);
    acc = 10 * acc + Number(result.ok);
    console.log(`step(${i})=${result.ok}, acc=${acc}`);
    obelisk.sleep({ milliseconds: 300 });
  }
  console.log(`parallel completed: ${acc}`);
  return String(acc);
}

Webhook Endpoint

webhook/tutorial.js — serves HTTP and triggers workflows via obelisk.call:

export default function handle(request) {
  const url = new URL(request.url);
  const path = url.pathname;
  console.log(`Handling request: ${path}`);

  if (path === "/serial") {
    const result = obelisk.call("tutorial:demo/workflow.serial", []);
    return new Response(`serial workflow completed: ${result}`, { status: 200 });
  } else if (path === "/parallel") {
    const result = obelisk.call("tutorial:demo/workflow.parallel", []);
    return new Response(`parallel workflow completed: ${result}`, { status: 200 });
  } else {
    return new Response("not found\ntry /serial or /parallel", { status: 404 });
  }
}

Configuration

deployment.toml wires the components together. Each entry assigns an FFQN (package:namespace/interface.function) and WIT types to the JS function — Obelisk synthesizes the interface at startup, so no WIT files are needed. The FFQN is the address used in obelisk.call; the JS function name maps to the function part of it. See JS components for links to the per-type pages (activities, workflows, webhooks) and JSON encoding.

[[activity_js]]
name = "step"
location = "${DEPLOYMENT_DIR}/activity/step.js"
ffqn = "tutorial:demo/activity.step"
params = [
  { name = "idx", type = "u64" },
  { name = "sleep-millis", type = "u64" },
]
return_type = "result<string>"
exec.lock_expiry.seconds = 10

[[workflow_js]]
name = "serial"
location = "${DEPLOYMENT_DIR}/workflow/serial.js"
ffqn = "tutorial:demo/workflow.serial"
params = []
return_type = "result<string, string>"

[[workflow_js]]
name = "parallel"
location = "${DEPLOYMENT_DIR}/workflow/parallel.js"
ffqn = "tutorial:demo/workflow.parallel"
params = []
return_type = "result<string, string>"

[[webhook_endpoint_js]]
name = "webhook"
location = "${DEPLOYMENT_DIR}/webhook/tutorial.js"
routes = ["/*"]

${DEPLOYMENT_DIR} resolves to the directory containing the deployment file. See Configuration for all options.

4. Run

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

5. Inspect Executions

List top-level executions — one per webhook call:

curl -s "http://localhost:5005/v1/executions"
E_01KN209P2PC...  Finished(ok)  wasi:http/incoming-handler.handle  2026-03-31 13:11:01 UTC

Fetch logs for a webhook execution (includes its console.log output):

EXECUTION_ID=E_01KN209P2PC...
curl -s "http://localhost:5005/v1/executions/${EXECUTION_ID}/logs"

To see the child workflow and activities spawned by a webhook, use show_derived and filter by FFQN prefix:

curl -s "http://localhost:5005/v1/executions?show_derived=true&ffqn_prefix=tutorial:demo"
E_01KN209P2PC....o:1_1              Finished(ok)  tutorial:demo/workflow.serial    2026-03-31 13:11:01 UTC
E_01KN209P2PC....o:1_1.o:2-step_1  Finished(ok)  tutorial:demo/activity.step      2026-03-31 13:11:02 UTC
...

Then fetch logs for the workflow execution by its ID — console.log calls appear as structured log entries with timestamps.

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

6. 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: Obelisk persists the full deployment configuration — including the paths to JS source files — in the database on first run, so the server can reload all components on restart without re-specifying them. Obelisk 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