Skip to main content
The Run button in the builder executes the workflow currently on your canvas and streams every node back to the canvas live. This page covers what the Run button actually does, every event you receive over the live stream, how interrupts, errors, retries, and billing denials appear, and how to read a run after it finishes — both in the app and from code. For the underlying transport (frame format, reconnection, history replay), see SSE run streaming. For how individual node failures surface and how to configure retries, see Error handling & retries.

What “Run” does

Pressing Run executes an ad-hoc run of the live canvas schema — not a deployed version. The builder sends the current schema inline with ephemeral: true, so the run does not create a persistent chat record, and stamps attribution_workflow_id so the run still appears in this workflow’s run history. The canvas schema itself is what runs; attribution is metadata only. This is different from running a saved deployment by workflow_id, and different from running over the API. Each path resolves the schema differently:
Running by workflow_id requires an active deployment. If the workflow has no live deployment, the run returns 400 with the message Workflow has no active deployment. Deploy the workflow first.... The Run button avoids this entirely because it sends the canvas schema inline. To run a saved version by id, deploy first.

Run controls

The run controls live on the builder canvas. Before a run starts the builder validates the canvas and resets node state.
1

Provide run input

If the workflow’s state_schema declares input fields, the builder shows a run-input form. Each value is parsed to its declared field type — string, integer, float, boolean, object, or array — with a JSON fallback for object and array. Reference tokens like {{node_id.field}} are resolved from run state at execution time, not at submit time; see Variables & references.
2

Press Run

Every non-start node is set to pending, prior outputs are cleared, and the canvas opens a live stream. The builder keeps a rolling buffer of the most recent run events so it can show you context if the run fails.
3

Watch the stream

Nodes transition pending → running → completed (or error) as events arrive. Open the Detail Panel on any node to inspect its streamed output.
4

Stop or resume

Stop cancels a running or interrupted run gracefully — the current node finishes, then the run stops between nodes. If the run pauses at an interrupt node, the builder shows a form; submitting it resumes the same run.
The builder validates the canvas before it lets you run: there must be at least one non-start node, the start node must have an outgoing edge, no node may be unreachable from __start__, and every node that needs a credential must have one connected. Validation errors point you at the offending node (it opens the Detail Panel or focuses the node).

The run lifecycle

A run is asynchronous. The Run button issues a request that returns immediately with a run_id, then the run executes in the background while you watch it over a separate live stream. Under the hood the builder drives four operations:
Key identifiers you will see:
string
The per-execution identifier used for the live stream, cancellation, and the durable run record. The Run button starts one run with one run_id; resuming after an interrupt reuses the same run_id (one logical run, charged once). This differs from the Assistant and Composer, where a resume mints a new run_id. See Workflows & runs for the three distinct run-id identities.
string
The conversation/checkpoint thread. For a non-ephemeral run thread_id == chat_id; for the builder’s ad-hoc (ephemeral: true) run, chat_id is null and thread_id is a fresh identifier. resume is addressed by thread_id, not run_id.
string
The run record’s id in run history. This is not the same value as run_id — it is the id returned by GET /workflow-runs. Use it to fetch a single run detail.

Live SSE output

The live stream is delivered over Server-Sent Events on GET /workflows/listen/{run_id}. Every frame is a bare data-only line — data: {json} with a blank line after it — and there is no SSE event: field. You discriminate on the JSON type key. Multiple clients can listen to the same run_id at once, and a reconnect replays the run’s buffered history (1-hour retention) before tailing live frames, so reconnecting mid-run is safe.
The wire payloads below are the shapes the executor actually publishes. ModuleX also ships typed event models and generated TypeScript types, but those disagree with the wire on several fields — most notably node_update, whose wire key is node (the node id), not node_id. Always read the field names documented here, not the typed models.

Event types

event
First event of the run. Wrapped under data: {type, data: {run_id, thread_id, workflow_name, workflow_version, workflow_type, timestamp}}. workflow_type is workflow for canvas runs.
event
A node has begun. Flat shape: {type, node, name, timestamp, metadata}. The canvas uses this to flip the node to running.
event
A node produced output (the graph-loop result). Flat shape: {type, node, output}, where node is the node id and output is the serialized state delta or message list. The knowledge node publishes a richer output object (match count, provider, top score, sources, and timing).
event
A node attempt failed and will be retried. Carries the attempt counter and the computed backoff delay: {type, node, name, attempt, max_attempts, error_type, error_message, next_retry_in, timestamp}. See Retries.
event
A node exhausted its retries or hit a non-retryable error. The node then re-raises, which fails the run: {type, node, name, error_type, error_message, reason, attempt, max_attempts, recoverable, timestamp}.
event
The run reached an interrupt node and is asking a human a question. Wrapped under data: {type, data: {thread_id, message, data, resume_schema, examples}}. This event does not close the stream — the connection stays open so the canvas can render the form. Respond with resume. See Human-in-the-loop resume.
event
Confirms a resume took effect (published by the API, not the executor). Wrapped: {type, data: {run_id, thread_id, resume_value, timestamp}}. The same run_id continues.
event
A keepalive emitted after roughly 15 seconds of silence: {type: "heartbeat"} with no other fields. It is not a workflow event — ignore it. It keeps a long interrupt pause from idle-closing the connection.
event
Terminal success. Wrapped: {type, data: {message: "Workflow completed successfully"}}. The stream closes after this frame.
event
Terminal failure. Flat shape: {type, message}, for example {"type":"error","message":"Execution failed: ..."}. The stream closes after this frame.
event
Terminal cancellation, after Stop. Wrapped: {type, data: {run_id, reason, cancelled_at}}. The data may be null if the cancel record has already expired. The stream closes after this frame.
The terminal events that close the live stream are done, error, and cancelled. An interrupted marker exists only in the run’s replay history (it is never sent live), so that a later reconnect stops cleanly at the pause point.

Raw frame trace

A run that reaches an interrupt, is resumed, and then completes looks like this on the wire (blank line between frames omitted for brevity except where shown):
A failed run ends with data: {"type":"error","message":"Execution failed: <msg>"} instead of done. A cancelled run ends with a cancelled frame.

Errors

A run can fail before it starts, while it executes, or because the caller is denied by billing. Each surfaces differently.

Start-time errors

These are returned by POST /workflows/run before any stream opens — the Run button shows them as a toast or dialog. They use the standard {detail} envelope unless noted.
status
Bad request. Common causes: a malformed state_schema input, or running by workflow_id with no live deployment (Workflow has no active deployment. Deploy the workflow first...).
status
The workflow or run could not be found in your organization. Cross-organization ids return 404, never another org’s data — {"detail":"Workflow not found"} / {"detail":"Run not found"}.
status
Gone. Returned if you send only an llm body (the removed LLM-only mode). Use the Assistant instead.
status
A billing denial — see Billing denials. These are real and live on the run surface.
status
An internal error, including a malformed workflow_schema (which surfaces as 500, not 422): {"detail":"Failed to start workflow: ..."}.

Run-time errors

Once the run is streaming, a node failure arrives as a node_error event followed by a run-level error event that closes the stream. The durable run record is then marked failed with the error message. For per-node detail and how to debug a failed node, see Error handling & retries.
The live stream reports terminal success as a done event, but the durable run record stores the status as succeeded (not completed). Read done/error/cancelled from the stream, and succeeded/failed/cancelled from run history.

Billing denials (402 / 403 / 429)

Every charged run passes through a billing admission gate that runs before any run record or background task is created (reject-before-write). When it denies the run, the response is a flat DenialEnvelope{code, layer, key, current, limit, reason} with no detail key — and no run is created and no stream opens. The builder catches this and routes you to the relevant upgrade or wallet action instead of starting a run. The HTTP status depends on which layer denied the run: Example 402 body:
This flat DenialEnvelope is live on the run, Composer, Assistant, and managed-knowledge surfaces only. Plain CRUD and org-settings routes use the {detail} envelope instead. For the full error-shape taxonomy see Errors & status codes; for what consumes credits and how the gate works see Usage gating & limits.

Retries and node errors

Most node types are wrapped in a retry layer. When an attempt fails with a retryable error type, the executor waits with exponential backoff and tries again, publishing a node_retry event for each retry. When retries are exhausted (or the error is not retryable) it publishes node_error, the node re-raises, and the run fails. Retries are configured per node with retry_config. When retry_config is omitted, a node defaults to 2 retries (3 total attempts).
Two node types are intentionally not retry-wrapped: the interrupt node (it pauses for a human and must never auto-retry) and the function node (it typically returns a soft-failure value rather than raising). See Error handling & retries for the full retry contract and debugging guidance.

Credit impact

A successful or failed run is charged exactly one run credit, recorded once per run_id. Resuming an interrupted run does not charge again — the gate’s reservation is keyed on the run_id, so the resume is idempotent. Language-model token usage is metered separately inside the executor regardless of whether the run succeeds, fails, or is cancelled. See Credits & metering.

Run history

Every run is persisted to a durable run history, independent of the live stream. In the builder, the Runs panel lists this organization’s runs for the current workflow; each row links to a full run detail. Run history survives even if the workflow is later deleted. Two read endpoints back the panel:
endpoint
Lists runs, newest first, scoped to your organization. Optional filters: workflow_id, status, trigger_type, plus limit (default 50, max 100) and offset. The response sets has_more from a limit + 1 fetch — there is no total count. Each row is a light summary: id (the run_pk), run_id, workflow_id, trigger_type, is_ad_hoc, status, started_at, completed_at, duration_seconds, error_message, created_at, has_output.
endpoint
Returns the full run detail for a single run, including input_snapshot and output_summary. The path parameter is the run record’s id (run_pk), not the executor run_id.
A run’s status in history is one of pending, running, succeeded, failed, cancelled, interrupted, or skipped. Its trigger_type is one of manual (a builder/JWT run), api_key, scheduled, or composer — a Run-button run is manual.

Run the same workflow from code

The Run button is one of three run surfaces. To run a workflow programmatically and stream its events, call POST /workflows/run and then listen on the returned run_id. Authenticate every request with Authorization: Bearer mx_live_… and X-Organization-ID.
Running by workflow_id over the API loads the workflow’s live deployment snapshot, not the canvas — deploy first, or send an inline workflow schema to run ad-hoc. The same 402 / 403 / 429 billing denials apply. Note that idempotency_key makes the start call safe to retry on the client, but it does not deduplicate runs server-side. See Run via API for the full request contract.

Next steps

SSE run streaming

The transport in depth: frame format, reconnection, and history replay.

Error handling & retries

Per-node retry configuration and how to debug a failed run.

Deploy & versions

Save a deployment so you can run a fixed version by id.

Run via API

Trigger and stream a run from your own backend.