---
title: "Execution model"
description: "How Axiom runs a flow: immutable compiled artifacts, unary vs pipeline (SSE) invocation, the Executions list and detail pages, and durable resume after worker failure."
category: concept
surfaces: [canvas, console, http-api]
related: [concepts/nodes-packages-flows, concepts/type-system, getting-started/invoke-via-api, guides/use-interactive-api-docs, guides/api-keys, guides/flow-configs, guides/debug-a-flow, reference/http-api, reference/glossary]
last_reviewed: 2026-06-10
---

# Execution model

Every run of a flow is an **execution**: the platform compiles the flow into
an immutable compiled artifact, assigns an execution ID when the request is
accepted, and records durable checkpoints as each node completes. A flow runs
in one of two modes — **unary** (one input message in, one result out) or
**pipeline** (a stream of frames delivered over Server-Sent Events) — and
either way the run survives infrastructure failure: if a worker dies mid-run,
a replacement resumes from the last checkpoint instead of starting over.

## Flows run as compiled artifacts

Invoking a flow never executes an editable draft — it always executes a
[compiled artifact](/docs/reference/glossary#compiled-artifact). Compilation
validates that every edge connects type-compatible messages, resolves every
node placement, fixes the [entry node](/docs/reference/glossary#entry-node)
and [terminal node](/docs/reference/glossary#terminal-node), and freezes the
execution mode (unary or pipeline) into the artifact. An artifact is
immutable: editing the flow and compiling again produces a new artifact with
a new ID.

The artifact ID is the `graph_id` you pass when invoking the flow over HTTP.
In the editor, the flow inspector's **API** section shows the flow's invoke
endpoint, and its **Use via API (curl)** action compiles the current canvas
and produces a ready-to-paste curl command with the compiled `graph_id`
filled in. That `graph_id` is pinned to the version of the flow that was
compiled — after editing the flow, reopen the dialog to get an updated
command. The same section's **Open interactive docs** button opens a live
API reference generated for that exact artifact (see
[Use the interactive API docs](/docs/guides/use-interactive-api-docs)).

Workers cache compiled artifacts by ID, so repeat invocations skip graph
resolution entirely.

## Unary invocation

A unary flow handles one input message and produces one result per
invocation. This is the default mode for flows that contain no pipeline
nodes. Invoke it with `POST /invocations/v1/flows/invoke` on your deployment's
origin, authenticated with an API key created under **Console → API Keys**
(the key must belong to the same account that owns the flow):

```bash
# Replace the origin and graph_id with the values from "Use via API (curl)"
curl -X POST 'https://flows.example-axiom-host.com/invocations/v1/flows/invoke' \
  -H "Authorization: Bearer $AXIOM_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"graph_id": "01JX3Z9KQ4WV8RT2M5XB7CDEFG", "input": {"name": "Ada"}, "wait": true}'
```

The request body fields:

- `input` — a JSON object matching the entry node's input message. The
  platform converts it to protobuf for you; callers never deal with binary
  serialization.
- `wait` — when `true`, the call blocks until the flow completes and the
  response carries `result`, with the terminal node's output decoded back to
  JSON in `result.output`. The default wait timeout is 30 seconds; set
  `timeout_seconds` to override it. When `false`, the flow runs
  asynchronously and only `execution_id` is returned.
- `webhook` — optional `{"url": "...", "headers": {...}}`; for asynchronous
  runs the result is delivered by HTTP POST to that URL instead.
- `config_id` — optional [flow config](/docs/reference/glossary#flow-config)
  profile; when omitted the default hierarchy applies (tenant default → flow
  default). See [Flow configs](/docs/guides/flow-configs).

A successful `wait: true` response (HTTP 202):

```json
{
  "accepted": true,
  "execution_id": "0af7651916cd43dd8448eb211c80319c",
  "result": {
    "output": { "greeting": "Hello, Ada!" },
    "success": true,
    "completed_at": 1780000000
  }
}
```

The `execution_id` identifies this run everywhere — the Executions pages,
the events API, and distributed traces all reference it.

## Pipeline invocation streams frames over SSE

A pipeline node is a streaming generator: declared with `type: pipeline` in
`axiom.yaml`, it emits a sequence of output frames instead of a single
output message. A flow that contains any pipeline node always runs in
pipeline mode — frames flow continuously between nodes, and results stream
back to the caller progressively.

Pipeline flows compile to a pipeline artifact and must be invoked on the SSE
endpoint — `wait` applies only to unary invokes:

```bash
# -N disables curl's buffering so frames render as they arrive
curl -N -X POST 'https://flows.example-axiom-host.com/invocations/v1/flows/invoke/stream' \
  -H "Authorization: Bearer $AXIOM_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"graph_id": "01JX3ZAB2PIPE8RT2M5XB7CDEF", "input": {"name": "Ada"}}'
```

The response is a Server-Sent Events stream (`Content-Type:
text/event-stream`). Each event is one frame:

```text
data: {"execution_id":"0af7651916cd43dd8448eb211c80319c","frame_index":0,"payload":{"greeting":"Hello, Ada!"},"is_final":false,"success":true}

data: {"execution_id":"0af7651916cd43dd8448eb211c80319c","frame_index":1,"is_final":true,"success":true}
```

`payload` is the flow output for that frame, decoded to JSON using the
terminal node's output message. The stream ends when a frame arrives with
`is_final: true`; on failure the final frame carries an `error` string
instead of a payload. If no final frame arrives within the timeout (30
seconds by default, `timeout_seconds` to override), the stream closes with a
final error frame.

## Running a flow from the editor

The **Run** button sits at the bottom-center of the editor canvas and opens
the **Run Graph** dialog. The dialog shows the flow's type — "Unary — single
request / response" or "⚡ Pipeline — streams multiple frames via SSE" — with
a **Switch** action to toggle between them. Switching is disabled when the
flow contains a pipeline node, because a pipeline node's streaming frames
cannot be delivered through a unary run.

Fill in the entry node's input fields and press **▶ Run**. Unary runs also
offer a **Debug Mode** toggle to watch the execution in real time node by
node (see [Debug a flow](/docs/guides/debug-a-flow)). Every run started from
the editor is recorded as an execution, exactly like an API-triggered run.

## The executions list

Click **Executions** in the top header to open `/executions`: one row per
execution, newest first, with **Status**, **Started**, **Duration**,
**Flow**, and **Execution** columns. Filter by status, by flow, or by date
range. The same data is available over HTTP at `GET
/invocations/v1/executions`.

An execution's status is one of:

| Status | Meaning |
|---|---|
| Queued | Accepted, not yet picked up by a worker |
| Running | A worker is executing nodes |
| Paused (HITL) | Waiting on a human-in-the-loop approval |
| Waiting on gate | Waiting for parallel branches to converge at a gate |
| Debug paused | Stopped at a breakpoint in a debug session |
| Completed | Terminal node finished; result available |
| Failed | A node failed and the flow did not complete |
| Compensating | Running compensation steps after a failure |
| Cancelled | Stopped by an explicit cancel request |

A running execution can be cancelled with `DELETE
/invocations/v1/flows/{execution_id}`; cancellation takes effect immediately,
even if the flow is blocked inside a long-running node.

## Execution detail

Selecting an execution opens `/executions/<execution-id>`: a read-only
canvas that replays the run on the flow's topology, a timeline scrubber to
step through it, and a header showing the status plus Started, Updated,
Completed, and Duration timestamps. Six tabs break the run down:

- **Timeline** — the run's events in time order, driving the scrubber.
- **Events** — the persisted per-node event log, filterable by node. Also
  available at `GET /invocations/v1/executions/{id}/events`.
- **Checkpoints** — the durable checkpoint written after each completed
  node, including its recorded output. Also available at `GET
  /invocations/v1/executions/{id}/checkpoints`.
- **Pauses** — human-in-the-loop pauses and how each was resumed.
- **Branches** — gate activity for parallel branches.
- **Output** — the flow's final output (or failure), decoded against the
  terminal node's output message.

The detail page is fed entirely from persisted history, so it renders the
same run identically on every reload — including after the run finished.

## Durable resume

After every completed node, the platform durably records a checkpoint
containing that node's output before moving on. Checkpoints are what make an
execution survive infrastructure failure: if the worker executing a flow
dies mid-run, the platform redelivers the run to a replacement worker, which
loads the checkpoint history, skips every node that already completed, and
re-runs only the node that was in flight when the worker died. The execution
keeps its ID and completes normally — callers waiting on a result or an SSE
stream do not need to retry.

Checkpoints are retained for 30 days by default and are visible on the
**Checkpoints** tab of the execution detail page. Large node outputs are
stored out-of-band and rehydrated transparently when a resumed run (or the
detail page) needs them.

Pauses are durable the same way: an execution in **Paused (HITL)** or
**Waiting on gate** holds its state indefinitely — across worker restarts —
until it is resumed or times out per its configured policy.
