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
POST /runswithIdempotency-Keyaccepted peridempotency.md. The same key on retry returns the existing run.- The orchestrator emitted
runOrchestrator.decided { decision: { kind: "ask-user" } }per RFC 0006 §B–§C. interrupt.requestedwithkind: "clarification"perinterrupt.md§Payload. The run is inwaiting-inputstatus.POST /runs/{runId}/interrupts/{nodeId}accepts the answer perinterrupt.md§"Resolve surfaces." The host MUST validate theresumeValueagainst the interrupt'sresumeSchema, when one is declared, before resuming.- SSE event ordering preserved across the suspension —
sequenceis strictly increasing per run, and a client that reconnects withLast-Event-IDreceives every later event in log order perevents.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
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.core.dispatchiteratesnextWorkerIdssequentially per RFC 0022 §D. The parent variable bag is the cross-worker handoff channel per §D.- 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 canonicalreason. - 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 withenvelope_invalid(see Error codes, HTTP 422). Here the second attempt validated. agent.toolCalled+agent.toolReturnedpair via sharedcallIdper RFC 0002. On a host advertisingtoolHooks.prePostEvents(RFC 0064), the call carriesargsHash— SHA-256 over the JCS-canonicalized args with secrets already redacted (SR-1), the content-free alternative to rawinputs— and the return carriesstatus.
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
sequenceis 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, perpersistence.md§"Durable acceptance and recovery."- Recovery is not a pause:
run.resumedis emitted only when a run leavespaused(runs.md§"Pause and resume"), and it carries no restart cursor — the client's cursor isLast-Event-ID. POST /runs/{runId}:forkis normative for replay-fork perruns.md§"Fork" andreplay.md. Events withsequence < fromSeqare fixed history; events fromfromSeqon are re-executed. Inreplaymode that prefix is byte-equivalent to the parent (§"Byte-equivalence of the prefix"); abranchlike this one starts from the projected state atfromSeqwith the caller'srunOptionsOverlayand is not deterministic by design.- The fork carries a new
runIdso client-side deep links to the parent remain stable. - 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
summarizenode in Scenario B used a host-managed Anthropic key. For BYOK, see theaiProvidersbyokfacet and theai.credentialRefrun option inruns.md§"Run options."