From d54cae35c58e7823e05bba485ead7cc8ccd3c767 Mon Sep 17 00:00:00 2001 From: os-zhuang Date: Wed, 10 Jun 2026 21:45:25 +0500 Subject: [PATCH] docs(flow): document durable pause & resume (ADR-0019) in the flow guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The flow guide covered ADR-0031 structured control flow but had nothing on the suspend/resume runtime — approval/screen/wait pausing semantics, the resume endpoint, wait-timer auto-resume + cold-boot re-arm, nested pause through subflows (linked runs, #1693), and the runs observability API were all undocumented (ROADMAP P1 "Document Flow-first automation"). Adds a "Durable pause & resume" section: per-node pause/resume table (with the decide-through-approvals-API rule and why direct resume strands the request), the nested-pause chain model with its showcase example pair, the runs endpoints, and the in-memory-history caveat. Node-type table rows for screen/wait/subflow now flag their pause behavior. Co-Authored-By: Claude Opus 4.8 --- content/docs/guides/metadata/flow.mdx | 60 +++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/content/docs/guides/metadata/flow.mdx b/content/docs/guides/metadata/flow.mdx index 045ad1ed39..7638754ed3 100644 --- a/content/docs/guides/metadata/flow.mdx +++ b/content/docs/guides/metadata/flow.mdx @@ -110,9 +110,9 @@ Each node performs a specific action in the flow. | `get_record` | Query records | | `http_request` | Make an HTTP API call | | `script` | Execute custom JavaScript | -| `screen` | Display a user form/screen | -| `wait` | Pause and wait for an event or time | -| `subflow` | Invoke another flow | +| `screen` | Display a user form/screen (durable pause) | +| `wait` | Pause for a timer or named signal (durable pause; timers auto-resume) | +| `subflow` | Invoke another flow — a pause inside the child suspends both runs as a linked chain | | `connector_action` | Execute an external connector action | ### Node Structure @@ -296,6 +296,60 @@ events). > protocol as the **interop** representation and map onto these constructs on > import/export — they are not the native authoring model. +## Durable pause & resume (ADR-0019) + +Some nodes don't finish in one pass — they **suspend** the run and wait for +the outside world. The engine snapshots the run (variables, step log, position) +to the `sys_automation_run` table and returns +`{ status: 'paused', runId }`; the pause **survives a process restart** and is +continued later through a single entry point: + +``` +POST /api/v1/automation/{flow}/runs/{runId}/resume +{ "inputs": { … }, "branchLabel": "approve", "output": { … } } +``` + +| Pausing node | Suspends until… | Resumed by | +| :--- | :--- | :--- | +| `approval` | a human decision | the approvals service (`POST /api/v1/approvals/requests/:id/approve\|reject`) — resumes down the matching `approve` / `reject` edge. **Always decide through the approvals API**, never by calling `resume` directly: a direct resume strands the pending `sys_approval_request`, so the record stays locked and later approvals on the same record hit `DUPLICATE_REQUEST`. | +| `screen` | a user submits the form | the UI runner posting the collected `inputs`; a `paused` response carrying the next `screen` chains multi-step wizards under one stable `runId` | +| `wait` (timer) | an ISO-8601 duration elapses | **automatically** — a one-shot job resumes the run; after a cold boot the engine re-arms pending timers from the durable store (overdue timers resume immediately) | +| `wait` (signal) | a named external event | any caller invoking `resume(runId)` | + +### Nested pause — pausing inside a subflow + +A pausing node inside a `subflow` suspends the **whole chain as linked runs**: +the child run pauses at its node, and the parent pauses at the `subflow` node +(`correlation: 'subflow:'`). Both continuations are durable, and +the chain resolves from either end: + +- deciding the child's approval (or its timer firing) completes the child and + **bubbles** its output back into the parent, which continues with the same + `${nodeId}.output` / `outputVariable` mapping as a synchronous subflow; +- resuming the **parent** run id (what a UI holds from the original launch) + **delegates** down to the suspended child — multi-screen child wizards keep + the parent run id stable. + +A child that fails terminally after the pause fails every waiting ancestor, so +no run is stranded as resumable-forever. See the worked example pair in the +showcase app: `showcase_project_closure` invokes the reusable +`showcase_closure_signoff` approval subflow and notifies the owner with the +bubbled decision. + +### Observing runs + +``` +GET /api/v1/automation/{flow}/runs # run history (status, steps, error) +GET /api/v1/automation/{flow}/runs/{runId} # one run +GET /api/v1/automation/{flow}/runs/{runId}/screen # re-fetch a paused screen +``` + +Each run's `steps[]` records every executed node — including loop iterations, +parallel branch bodies, and try/catch region steps — and the Studio flow +designer surfaces the same data in its **Runs** side panel. Run history is an +in-memory ring buffer (the durable rows in `sys_automation_run` hold only +*live pauses*), so history starts fresh on each boot. + ## Edges Edges connect nodes and define the execution path: