Skip to content

Commit d54cae3

Browse files
os-zhuangclaude
andcommitted
docs(flow): document durable pause & resume (ADR-0019) in the flow guide
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 <noreply@anthropic.com>
1 parent d299a5f commit d54cae3

1 file changed

Lines changed: 57 additions & 3 deletions

File tree

content/docs/guides/metadata/flow.mdx

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,9 @@ Each node performs a specific action in the flow.
110110
| `get_record` | Query records |
111111
| `http_request` | Make an HTTP API call |
112112
| `script` | Execute custom JavaScript |
113-
| `screen` | Display a user form/screen |
114-
| `wait` | Pause and wait for an event or time |
115-
| `subflow` | Invoke another flow |
113+
| `screen` | Display a user form/screen (durable pause) |
114+
| `wait` | Pause for a timer or named signal (durable pause; timers auto-resume) |
115+
| `subflow` | Invoke another flow — a pause inside the child suspends both runs as a linked chain |
116116
| `connector_action` | Execute an external connector action |
117117

118118
### Node Structure
@@ -296,6 +296,60 @@ events).
296296
> protocol as the **interop** representation and map onto these constructs on
297297
> import/export — they are not the native authoring model.
298298
299+
## Durable pause & resume (ADR-0019)
300+
301+
Some nodes don't finish in one pass — they **suspend** the run and wait for
302+
the outside world. The engine snapshots the run (variables, step log, position)
303+
to the `sys_automation_run` table and returns
304+
`{ status: 'paused', runId }`; the pause **survives a process restart** and is
305+
continued later through a single entry point:
306+
307+
```
308+
POST /api/v1/automation/{flow}/runs/{runId}/resume
309+
{ "inputs": { … }, "branchLabel": "approve", "output": { … } }
310+
```
311+
312+
| Pausing node | Suspends until… | Resumed by |
313+
| :--- | :--- | :--- |
314+
| `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`. |
315+
| `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` |
316+
| `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) |
317+
| `wait` (signal) | a named external event | any caller invoking `resume(runId)` |
318+
319+
### Nested pause — pausing inside a subflow
320+
321+
A pausing node inside a `subflow` suspends the **whole chain as linked runs**:
322+
the child run pauses at its node, and the parent pauses at the `subflow` node
323+
(`correlation: 'subflow:<childRunId>'`). Both continuations are durable, and
324+
the chain resolves from either end:
325+
326+
- deciding the child's approval (or its timer firing) completes the child and
327+
**bubbles** its output back into the parent, which continues with the same
328+
`${nodeId}.output` / `outputVariable` mapping as a synchronous subflow;
329+
- resuming the **parent** run id (what a UI holds from the original launch)
330+
**delegates** down to the suspended child — multi-screen child wizards keep
331+
the parent run id stable.
332+
333+
A child that fails terminally after the pause fails every waiting ancestor, so
334+
no run is stranded as resumable-forever. See the worked example pair in the
335+
showcase app: `showcase_project_closure` invokes the reusable
336+
`showcase_closure_signoff` approval subflow and notifies the owner with the
337+
bubbled decision.
338+
339+
### Observing runs
340+
341+
```
342+
GET /api/v1/automation/{flow}/runs # run history (status, steps, error)
343+
GET /api/v1/automation/{flow}/runs/{runId} # one run
344+
GET /api/v1/automation/{flow}/runs/{runId}/screen # re-fetch a paused screen
345+
```
346+
347+
Each run's `steps[]` records every executed node — including loop iterations,
348+
parallel branch bodies, and try/catch region steps — and the Studio flow
349+
designer surfaces the same data in its **Runs** side panel. Run history is an
350+
in-memory ring buffer (the durable rows in `sys_automation_run` hold only
351+
*live pauses*), so history starts fresh on each boot.
352+
299353
## Edges
300354

301355
Edges connect nodes and define the execution path:

0 commit comments

Comments
 (0)