OpenWOP openwop.dev

Scenario walkthroughs ~15 minutes total

Three runs, end to end.

Three end-to-end examples of OpenWOP runs, showing the workflow definition, the wire-level events the host emits, and what the client sees. Each scenario links back to the normative spec sections that govern its surface.

A 77-second narrated film of Scenario C, with English captions. An agent is halfway through a long job when the machine it runs on goes down. Because every step is written down as it happens, the machine reads its own notes when it comes back, finds the unfinished job and finishes it, with nobody stepping in. Someone who was watching and lost their connection picks up exactly where they left off. Then a new job branches from an earlier point while the original stays as it was. The ids and event names are the ones on this page. A scripted illustration.
  • A

    Single agent with human approval

    a supervisor agent makes a decision, asks for human confirmation, resumes when the human answers, and terminates.

    run lifecycle · interrupts · stream modes
  • B

    Multi-agent fan-out with envelope retries

    a supervisor dispatches to two worker agents in sequence, each producing a typed envelope. The second agent's first envelope fails schema validation and the host retries automatically before the run continues.

    multi-agent execution · AI envelope · RFC 0032
  • C

    Suspend, restart, replay-fork from checkpoint

    a long-running workflow is interrupted by a host restart, comes back up from its event log, and the operator forks a new run from a historical checkpoint to test a policy change.

    replay · storage adapters · version negotiation

What these skip

Three things deliberately left out, all linked here for the curious:

  1. Auth handshakeEvery request carries Authorization: Bearer …. See Auth profiles for OAuth2 / OIDC / mTLS variants.
  2. Webhook deliveryThe SSE stream above is the synchronous view; production hosts also deliver every event over HMAC-signed webhooks per webhooks.md.
  3. BYOK secret resolutionThe summarize node in Scenario B used a host-managed Anthropic key. For BYOK, see the aiProviders byok facet and the ai.credentialRef run option in runs.md §"Run options."

The walkthroughs

Each with its workflow definition, the wire trace, and what just happened, normatively.

Reading time: ~15 minutes total. Each scenario is independent — skim the one that matches what you're building.

Scenario A — Single agent with human approval

What it shows: the most common pattern — a supervisor agent makes a decision, asks for human confirmation, resumes when the human answers, and terminates.

Spec surfaces touched: run lifecycle, interrupts, stream modes.

Workflow definition

{
  "id": "wf-approve-and-act",
  "version": 1,
  "nodes": [
    { "id": "decide",  "typeId": "core.orchestrator.supervisor" },
    { "id": "ask",     "typeId": "core.hitl.clarify" },
    { "id": "act",     "typeId": "core.http.request" }
  ],
  "edges": [
    { "from": "decide", "to": "ask" },
    { "from": "ask",    "to": "act" }
  ],
  "channels": [
    { "name": "decision", "type": "string" },
    { "name": "approval", "type": "boolean" }
  ]
}

Wire trace

Client creates the run:

POST /runs
OpenWOP-Version: 2
Authorization: Bearer …
Content-Type: application/json
Idempotency-Key: ck-2026-05-21-001

{ "workflowId": "wf-approve-and-act", "inputs": { "url": "https://api.example.com/charge" } }

Host responds:

HTTP/1.1 201 Created
{ "runId": "run-abc", "status": "pending", "eventsUrl": "/runs/run-abc/events" }

Client opens an SSE stream:

GET /runs/run-abc/events?streamMode=updates
OpenWOP-Version: 2
Accept: text/event-stream

Events flow:

event: run.started        { "runId": "run-abc", "ts": "2026-05-21T18:00:00Z" }
event: runOrchestrator.decided { "agentId": "supervisor",
                               "decision": { "kind": "ask-user", "prompt": "Charge $42 to example.com?" } }
event: interrupt.requested    { "kind": "clarification", "key": "ask-approval",
                               "data": { "questions": [{ "id": "approval", "question": "Charge $42 to example.com?" }] } }

The run is now waiting-input. The host serializes its state and waits.

Human answers, run resumes

POST /runs/run-abc/interrupts/ask
{ "resumeValue": { "approval": true } }

Events continue:

event: interrupt.resolved { "nodeId": "ask", "interruptId": "…", "kind": "clarification" }
event: node.started       { "nodeId": "act", "typeId": "core.http.request" }
event: node.completed     { "nodeId": "act", "outputs": { "status": 200 } }
event: run.completed      { "outputs": { "status": 200 } }

The runId is stable across the pause; deep linking, replay, and fork all work against the same id.

What just happened, normatively

  1. POST /runs with Idempotency-Key accepted per idempotency.md. The same key on retry returns the existing run.
  2. The orchestrator emitted runOrchestrator.decided { decision: { kind: "ask-user" } } per RFC 0006 §B–§C.
  3. interrupt.requested with kind: "clarification" per interrupt.md §Payload. The run is in waiting-input status.
  4. POST /runs/{runId}/interrupts/{nodeId} accepts the answer per interrupt.md §"Resolve surfaces." The host MUST validate the resumeValue against the interrupt's resumeSchema, when one is declared, before resuming.
  5. SSE event ordering preserved across the suspension — sequence is strictly increasing per run, and a client that reconnects with Last-Event-ID receives every later event in log order per events.md.

Scenario B — Multi-agent fan-out with envelope retries

What it shows: a supervisor dispatches to two worker agents in sequence, each producing a typed envelope. The second agent's first envelope fails schema validation and the host retries automatically before the run continues.

Spec surfaces touched: multi-agent execution, AI envelope, RFC 0032 reliability events.

Workflow definition

{
  "id": "wf-research-and-summarize",
  "version": 1,
  "nodes": [
    { "id": "supervise", "typeId": "core.orchestrator.supervisor" },
    { "id": "research",  "typeId": "core.openwop.agents.deep-research",
      "config": { "envelope": "research.findings" } },
    { "id": "summarize", "typeId": "core.llm.chat",
      "config": { "envelope": "summary.brief", "model": "claude-sonnet-4-6" } }
  ],
  "edges": [
    { "from": "supervise", "to": "research"  },
    { "from": "supervise", "to": "summarize" }
  ]
}

fanOutPolicy: "sequential" is the default per RFC 0022; see that RFC for the parallel variant.

Wire trace

Run starts. Supervisor dispatches to research:

event: run.started
event: runOrchestrator.decided { "agentId": "supervisor",
                               "decision": { "kind": "next-worker", "nextWorkerIds": ["research", "summarize"] } }
event: node.started       { "nodeId": "research", "typeId": "core.openwop.agents.deep-research" }
event: agent.toolCalled   { "callId": "tc-001", "agentId": "research", "toolName": "web.search", "argsHash": "sha256:…" }
event: agent.toolReturned { "callId": "tc-001", "agentId": "research", "toolName": "web.search", "status": "ok" }
event: node.completed     { "nodeId": "research" }

Supervisor dispatches to summarize. First attempt at the summary envelope fails schema validation:

event: node.started       { "nodeId": "summarize", "typeId": "core.llm.chat" }
                          ← attempt 1 emits a summary.brief envelope that fails schema validation
event: envelope.retry.attempted { "nodeId": "summarize", "attempt": 2, "reason": "schema-violation" }
event: node.completed     { "nodeId": "summarize" }
event: run.completed      { "outputs": { … } }

What just happened, normatively

  1. runOrchestrator.decided { decision: { kind: "next-worker", nextWorkerIds: [...] } } is the supervisor decision per RFC 0006 §B–§C; the planner→worker handoff it drives is RFC 0037 §B.
  2. core.dispatch iterates nextWorkerIds sequentially per RFC 0022 §D. The parent variable bag is the cross-worker handoff channel per §D.
  3. The invalid first attempt triggers a host-side retry per RFC 0032 §B.1 — envelope.retry.attempted (SHOULD-tier; the first attempt emits nothing) carries the attempt count and the canonical reason.
  4. If the retry budget were exhausted, the host would emit envelope.retry.exhausted (a MUST under RFC 0032 §B.2) and the run would fail with envelope_invalid (see Error codes, HTTP 422). Here the second attempt validated.
  5. agent.toolCalled + agent.toolReturned pair via shared callId per RFC 0002. On a host advertising toolHooks.prePostEvents (RFC 0064), the call carries argsHash — SHA-256 over the JCS-canonicalized args with secrets already redacted (SR-1), the content-free alternative to raw inputs — and the return carries status.

Scenario C — Suspend, restart, replay-fork from checkpoint

What it shows: a long-running workflow is interrupted by a host restart, comes back up from its event log, and the operator forks a new run from a historical checkpoint to test a policy change.

Spec surfaces touched: replay, storage adapters, version negotiation.

Setup

A run started yesterday with the SQLite reference host. Mid-run, the host process restarts.

event: run.started        { "runId": "run-xyz" }                  sequence 0
event: node.started       { "nodeId": "fetch-data" }              sequence 1
event: node.completed     { "nodeId": "fetch-data" }              sequence 2
event: node.started       { "nodeId": "transform" }               sequence 3
event: node.completed     { "nodeId": "transform" }               sequence 4
                          ← host process exits here

Host comes back up

On startup, the host scans its event log for runs in non-terminal status. For run-xyz it finds the last persisted event and continues, with no client action:

event: node.started       { "nodeId": "publish", "typeId": "core.http.request" }    sequence 5
event: node.completed     { "nodeId": "publish" }                                   sequence 6
event: run.completed      { "outputs": { … } }                                      sequence 7

No run.resumed is emitted: that event marks the exit from paused, and a restart is not a pause. Every event carries its sequence, so a client whose stream dropped reconnects with Last-Event-ID set to the last sequence it saw and receives exactly the events after it — which is how it deduplicates if its downstream had partial state (events.md §"SSE frames").

Operator forks from a historical checkpoint

A policy change in the transform node means the operator wants to re-run the second half of yesterday's pipeline with the new logic, without re-fetching data.

POST /runs/run-xyz:fork
{ "mode": "branch",
  "fromSeq": 4,
  "runOptionsOverlay": { "configurable": { "transformMode": "v2" } } }

Host responds:

HTTP/1.1 201 Created
{ "runId": "run-xyz-fork-1",
  "sourceRunId": "run-xyz",
  "mode": "branch",
  "fromSeq": 4,
  "status": "pending",
  "eventsUrl": "https://api.example.com/runs/run-xyz-fork-1/events" }

The new run inherits events 0..3 from the parent as fixed history (all of fetch-data, and the start of transform), then begins fresh execution from event 4 — transform's result onward — with the new transformMode: "v2". The original run-xyz is unchanged.

What just happened, normatively

  1. sequence is strictly increasing per run and persisted logs are never renumbered (events.md §"The envelope"). A host that accepted work MUST resume it after the accepting process dies, without client action, per persistence.md §"Durable acceptance and recovery."
  2. Recovery is not a pause: run.resumed is emitted only when a run leaves paused (runs.md §"Pause and resume"), and it carries no restart cursor — the client's cursor is Last-Event-ID.
  3. POST /runs/{runId}:fork is normative for replay-fork per runs.md §"Fork" and replay.md. Events with sequence < fromSeq are fixed history; events from fromSeq on are re-executed. In replay mode that prefix is byte-equivalent to the parent (§"Byte-equivalence of the prefix"); a branch like this one starts from the projected state at fromSeq with the caller's runOptionsOverlay and is not deterministic by design.
  4. The fork carries a new runId so client-side deep links to the parent remain stable.
  5. Per-region clock fields MAY differ on the fork (the fork executes "now", not at the parent's event times) per replay.md §"Byte-equivalence of the prefix" (RFC 0036 §E).

What these scenarios skip

Three things deliberately left out, all linked here for the curious:

  • Auth handshake — every request carries Authorization: Bearer …. See Auth profiles for OAuth2 / OIDC / mTLS variants.
  • Webhook delivery — the SSE stream above is the synchronous view; production hosts also deliver every event over HMAC-signed webhooks per webhooks.md.
  • BYOK secret resolution — the summarize node in Scenario B used a host-managed Anthropic key. For BYOK, see the aiProviders byok facet and the ai.credentialRef run option in runs.md §"Run options."

Run scenario A yourself.