workflow.parklab.work
Declarative · Versioned · Agent-native

The workflow engine
agents author as data.

Define state and process declaratively in JSON (WDL), instantiate them, and evolve them by publishing immutable versions — on a shared, auditable substrate. Durable execution is delegated to DBOS on a single Postgres. 사람을 위한 시각화가 아니라, 에이전트가 프로세스를 스스로 구상·발전시키는 기판.

status probing… version instances MCP /mcp
order-lifecycle · WDL entity machinereplay-safe
PAY SHIP DELIVER CANCEL pending paid shipped delivered ✓ succeed cancelled ✗ fail
01

Overview

A managed engine where agents conceive and evolve processes, and where several agents cooperate over one shared, auditable, durable substrate. Part of the parklab family (store / mindmap).

◆ ONE ENGINE

State = Process

Entity state machines and process orchestration are the same model (WDL) — two profiles, one grammar.

◆ IMMUTABLE

Evolve by version

A workflow has a stable slug; each publish appends an immutable version. Instances pin to their start version.

◆ DURABLE

Delegated to DBOS

One generic interpreter reads the pinned WDL; steps are journaled, waits are durable. Instances survive crashes.

The key idea — WDL is data, a DBOS workflow is code. A single generic interpreter run_instance(id) bridges them: it reads the pinned definition and evaluates states one at a time. Because the version can't change mid-flight, deterministic replay holds — pure interpretation re-runs in memory while every side effect is a journaled, exactly-once step.

02

Core concepts

Immutable, versioned definitions

“Updating” a workflow means publishing a new version under the same slug. Running instances are pinned to the version they started on and run to completion on it — there is no live migration. This isn't a convenience; it's a correctness requirement: DBOS deterministic replay needs the logic fixed for an instance's lifetime.

Two profiles, one grammar

The same WDL expresses an entity state machine (event-centric, like order-lifecycle) and a process orchestration (activity-centric, like doc-triage). The engine and definition language are one; the difference is only which state types dominate.

Expressions

  • Guards & computed values — JSONLogic. Data access via {"var":"context.path"}. Event transitions also see {"var":"event.field"}.
  • String templating{{ $.context.path }} (JSONPath). A whole-token string resolves to the raw value (dict/number preserved); embedded tokens interpolate to text.
  • assign — a flat mapping {"context.k": <rule>} writes computed values back into context.
03

WDL reference

A WDL 1.0 document has wdl_version, name, initial, and a states map. Every state declares a type.

typemeaningkey fields
eventWait for an external event (entity-state primitive)on
taskPerform an activity (external call)activity, on_success, on_error, retry, timeout_s
choiceGuard-based branchingchoices[], default
parallelFan out into branches, then joinbranches[], next
mapIterate over a collectionitems, iterator, next
waitDurable timer and/or external eventseconds or on, next
passTransform context, then move onassign, next
succeedTerminal successresult
failTerminal failureerror, cause

Activities (what a task runs)

kindshape
http{ method, url, headers?, body? } — templated; non-2xx → on_error
agent{ prompt, system?, model?, output_schema? } — Claude; output_schema forces structured JSON
mcp{ server, tool, args } — calls an external MCP tool over Streamable HTTP
subworkflow{ workflow, version?, input } — starts a child instance and awaits it (durable)
nooppass-through / testing
04

Examples

Two definitions, one grammar — an entity machine and a process orchestration.

A · entity state machine

order-lifecycle.wdl.jsoncopy
{
  "wdl_version": "1.0", "name": "order-lifecycle", "initial": "pending",
  "states": {
    "pending":   { "type": "event", "on": { "PAY": {"target":"paid"}, "CANCEL": {"target":"cancelled"} } },
    "paid":      { "type": "event", "on": { "SHIP": {"target":"shipped"} } },
    "shipped":   { "type": "event", "on": { "DELIVER": {"target":"delivered"} } },
    "delivered": { "type": "succeed" },
    "cancelled": { "type": "fail", "error": "ORDER_CANCELLED" }
  }
}

B · process orchestration

doc-triage.wdl.jsoncopy
{
  "wdl_version": "1.0", "name": "doc-triage", "initial": "classify",
  "states": {
    "classify": {
      "type": "task",
      "activity": { "kind": "agent",
        "prompt": "Classify this document: {{ $.context.doc }}",
        "output_schema": { "type":"object", "properties":{"category":{"type":"string"}} } },
      "on_success": { "target":"route", "assign":{"context.category":{"var":"result.category"}} },
      "on_error":   { "target":"needs_human" }
    },
    "route": { "type":"choice",
      "choices":[ {"guard":{"==":[{"var":"context.category"},"urgent"]}, "target":"escalate"} ],
      "default":"archive" },
    "escalate": { "type":"succeed" },
    "needs_human": { "type":"wait", "on":{ "RESOLVE":{"target":"archive"} } },
    "archive": { "type":"succeed" }
  }
}
05

REST API

JSON over HTTPS. Everything under /workflows and /instances requires Authorization: Bearer <ADMIN_TOKEN>. Health & docs are public.

Definitions & versions

POST/workflows/validateValidate WDL without persisting (agents call this before publishing)
POST/workflowsCreate a workflow (optionally with its first version)
GET/workflowsList workflows
GET/workflows/{slug}Metadata + version summaries
PATCH/workflows/{slug}Update meta / default_version
POST/workflows/{slug}/versionsPublish a new immutable version (= evolve)
GET/workflows/{slug}/versions/{v}The full WDL of a version
GET/workflows/{slug}/versions/{v}/mermaidRender the version as a Mermaid diagram

Instances

POST/workflows/{slug}/instancesStart an instance (input context, optional pinned version)
GET/instances/{id}Current state + context + status
POST/instances/{id}/eventsSend {type, payload} → drives a transition
GET/instances/{id}/historyAppend-only audit log
POST/instances/{id}/cancelTerminate
GET/statsWorkflow / version / instance counts (public)

Interactive: Swagger UI →  ·  ReDoc →  ·  openapi.json →

06

MCP server

Streamable HTTP at /mcp (bearer-authed). An agent runs the whole closed loop — validate → publish → instantiate → drive by events → inspect — as MCP tools.

workflow.validate workflow.list workflow.get workflow.publish workflow.start workflow.send_event workflow.get_state workflow.get_history workflow.list_instances workflow.cancel workflow.render_mermaid

connect (MCP client config)copy
{
  "mcpServers": {
    "workflow": {
      "url": "https://workflow.parklab.work/mcp",
      "headers": { "Authorization": "Bearer <ADMIN_TOKEN>" }
    }
  }
}
07

Quickstart

Publish a workflow, start an instance, drive it through events — end to end.

bashcopy
# publish (validation runs automatically)
curl -X POST https://workflow.parklab.work/workflows \
  -H "Authorization: Bearer $TOKEN" -H "content-type: application/json" \
  -d '{"slug":"order","name":"Order","definition":{ ...WDL A... }}'

# start an instance → returns {id, current_state:"pending", status:"waiting"}
curl -X POST https://workflow.parklab.work/workflows/order/instances \
  -H "Authorization: Bearer $TOKEN" -H "content-type: application/json" -d '{"input":{}}'

# drive it (each event is durable; queued if the instance isn't parked yet)
curl -X POST https://workflow.parklab.work/instances/$ID/events \
  -H "Authorization: Bearer $TOKEN" -H "content-type: application/json" -d '{"type":"PAY"}'

# inspect: GET /instances/$ID  →  completed @ delivered
Note — the bearer token is the deployment's ADMIN_TOKEN (it is not shown here). Retrieve it on the host with grep ^ADMIN_TOKEN= /opt/workflow/infra/prod/.env.
08

Architecture

Agents speak JSON (REST or MCP). The control plane validates & stores immutable versions and starts instances; the execution plane (DBOS) gives each instance durable, exactly-once execution. One Postgres backs both.

Agents ──(HTTP / MCP)──▶ CONTROL PLANE (FastAPI) ├─ WDL validator (JSON Schema + static analyzer) ├─ version registry (immutable, versioned) ├─ instance API (start · event · state · history) └─ MCP server (Streamable HTTP, /mcp) │ start / send-to ▼ EXECUTION PLANE (DBOS Transact) └─ run_instance(): one generic interpreter · @DBOS.step → activities + projections · DBOS.recv → external events (durable) · DBOS.sleep → durable timers ▼ PostgreSQL (single backbone) ├─ dbos.* execution journal └─ domain workflows · versions · instances · history

Stack — Python 3.12 · FastAPI · DBOS Transact · PostgreSQL 16 · JSONLogic · MCP (Streamable HTTP) · UUIDv7. Deployed as a Docker container behind the shared Caddy on the parklab Vultr box, alongside store and mindmap.