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 withephemeral: 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 run lifecycle
A run is asynchronous. The Run button issues a request that returns immediately with arun_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:
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 onGET /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.
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.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):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 byPOST /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 anode_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 flatDenialEnvelope — {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 anode_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 perrun_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.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, callPOST /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.