> Source: https://builder.ema.ai/v2/core-concepts/workflows
> Title: Workflows

# Workflows

A **workflow** is the executable definition of an AI Employee. It is a **directed acyclic graph (DAG)**: a set of **nodes** connected by **edges**. Each node does one unit of work — reasoning with an agent, reshaping data, declaring an output. Each edge carries data and an optional [condition](/builder/v2/core-concepts/conditions-and-expressions) that decides whether the downstream node runs. Because the graph is acyclic, the platform can sort it into an execution order where every node runs only after the nodes it depends on have finished.

You edit a workflow visually in the AI Employee builder, save drafts as you go, and publish when it is ready. Every run executes against the live published version and records a full step-by-step trace.

## The DAG model

A workflow's definition is a single JSON object with two arrays:

```json
{
  "nodes": [ /* DAGNode objects */ ],
  "edges": [ /* DAGEdge objects */ ]
}
```

-   **Nodes** are the units of work. Each has a unique `id`, a `type`, a human-readable `label`, and type-specific configuration.
-   **Edges** connect a `from` node to a `to` node. An edge declares a data dependency and may carry a `condition`. An edge with `"type": "ordering"` enforces order only, with no data dependency.

The platform builds the DAG, computes each node's in-degree (how many edges point at it), and runs a topological sort. Nodes whose dependencies are all satisfied become eligible at the same time and run in parallel, up to the configured parallelism limit.

### Node types

A node's `type` field is one of the following. The agent node is where reasoning happens; the others are structural or deterministic.

`type`

Purpose

`start`

The entry point. Maps the run's input into the workflow. Exactly one per workflow. Its `input_schema` declares the fields the workflow accepts.

`agent`

A reasoning step. Runs a configured agent type (set in `agent_config.agent_type`), with optional knowledge bases, Tools, and a human-in-the-loop block. This is where LLM work, RAG retrieval, and Tool calls happen. See [Agents](/builder/v2/core-concepts/agents).

`transform`

A deterministic, non-LLM reshape. Extracts and coerces named fields from an upstream value using a list of rules. Never calls a model and never bills tokens. Use it to pull specific fields out of an agent's output, apply defaults, and coerce types (`string`, `number`, `boolean`, `datetime`).

`publish`

Declares one or more named outputs. The `output_fields` on the workflow's publish nodes are the AI Employee's public output contract — they become the run's `named_outputs` and the columns of a dashboard. A workflow with no publish nodes produces no named outputs.

`end`

A terminal node marking the close of a path.

> [INFO]
> **Agent type vs. node type.** Every reasoning node has `"type": "agent"`. Which agent type it runs is set separately in `agent_config.agent_type` (for example `intent_classification` or `search_respond`). The special `feedback_router` agent type routes a run based on end-user feedback. See [Agents](/builder/v2/core-concepts/agents).

### Human in the loop, conditions, and Tools

There is no dedicated "ask a human" node type. Two distinct mechanisms pause or branch a run, and both live on the structures above:

-   **Human in the loop** is configured with a `hitl_config` block on an `agent` node. The run pauses when the node reaches it and resumes when a person responds. See [Human in the Loop](/builder/v2/core-concepts/human-in-the-loop).
-   **Conditions** live on **edges**, not nodes. The condition on an outgoing edge decides whether that branch runs. See [Conditions and Expressions](/builder/v2/core-concepts/conditions-and-expressions).
-   **Tools** (integration functions and MCP servers) are selected on an `agent` node via `action_ids` and `mcp_server_ids`. The agent runtime exposes them to the model as callable tools.

### Edges and conditions

An edge looks like this:

```json
{
  "from": "classify",
  "to": "escalate",
  "condition": {
    "type": "field",
    "field": "output.priority",
    "operator": "eq",
    "value": "HIGH"
  }
}
```

When the `from` node completes, the platform evaluates each outgoing edge's condition. If the condition is true (or absent), the `to` node becomes eligible. If it is false, that branch is skipped. Edge conditions can only reference the source node's `output.*` namespace — `workflow_input.*` and other namespaces are rejected at save time so misplaced references surface in the builder instead of silently failing every run. The full operator catalog and the branching rules are in [Conditions and Expressions](/builder/v2/core-concepts/conditions-and-expressions).

### Passing data between nodes

A node pulls its inputs from upstream nodes and from the run input through `input_mapping`, using `{{...}}` references:

-   `{{workflow_input.field}}` — a field from the run's `input_params`.
-   `{{node_id.output.field}}` — a field from the output of an upstream node.

An input can be marked optional. If an optional input's source node was skipped, the input is omitted and the node still runs; if a required input's source was skipped, this node is skipped too (skip propagation). See [Conditions and Expressions](/builder/v2/core-concepts/conditions-and-expressions#passing-data-with-variables).

### Parallelism and limits

Nodes with no dependency between them run concurrently. The platform caps how many run at once and bounds the size of the graph so execution stays predictable:

Limit

Default

Environment variable

Maximum nodes per workflow

50

`EMU_MAX_DAG_NODES`

Maximum edges per workflow

200

`EMU_MAX_DAG_EDGES`

Maximum nodes running in parallel

5

`EMU_MAX_PARALLELISM`

The node and edge limits are enforced on every draft save and again at publish. The parallelism limit shapes execution at run time.

## Drafts, versions, and publishing

A workflow is edited as a **draft** and deployed by **publishing**. The two states are kept separate so you can iterate freely without affecting what end users see.

-   **Saving** a changed DAG writes a new immutable version row, marked as a draft (not yet published, not active). You can save as many drafts as you like.
-   **Publishing** validates the draft, captures the live configuration and the attached knowledge bases into a snapshot, stamps the version as published, and marks it the single **active** version — all in one transaction. New runs resolve to the active version.
-   **Promote** makes an earlier published version active again (a forward activation).
-   **Revert** makes an earlier version active and reconciles the attached knowledge bases back to the state captured when that version was published.

Each version is a self-contained snapshot: agent configuration is stored inline on each node, so editing an agent template later never changes a published workflow. A version may carry an optional name and description as a human-friendly alias.

The builder distinguishes "the current DAG is live" (the active version is the latest) from "you have unpublished changes" (the active version is older than your latest draft), so you always know whether your edits are reaching end users.

> [WARNING]
> **Publishing can fail pre-flight.** The most common blockers are `NO_LLM_PROVIDER` (an agent references an LLM provider not enabled for your tenant), `HITL_INVALID_ASSIGNEE` (a human-in-the-loop node assigns to a user who doesn't exist), and structural errors (a `DAG_INVALID_*` code such as `DAG_INVALID_NODE` or `DAG_CYCLE_DETECTED`, or `DAG_TOO_LARGE`). Fix the reported issue and publish again.

### DAG validation

Validation runs on every draft save and again at publish. It checks structure, not behavior:

-   Size within the node and edge limits.
-   A unique `id` on every node, and edges that reference only existing nodes.
-   Exactly one `start` node, and required fields present on each node.
-   No cycles — the graph must remain acyclic.
-   Edge conditions that are well-formed and reference only the allowed `output.*` namespace.

Structural failures return a specific `DAG_INVALID_*` code (for example `DAG_INVALID_NODE`, `DAG_INVALID_EDGE`, or `DAG_CYCLE_DETECTED`) — or `DAG_TOO_LARGE` when a size limit is exceeded — so you get a tight feedback loop in the builder.

## Runs and steps

A **run** is one execution of a workflow. Starting a run creates a new run record; the workflow's identity (`workflow_id`) never changes, but every run gets a fresh `run_id`.

Start a run by POSTing to the run endpoint:

```http
POST /api/v1/workflow/workflows/{id}/run
Content-Type: application/json

{
  "input_params": { "message": "Where is my order?" },
  "session_id": "b2c1…"
}
```

`input_params` is validated against the `start` node's `input_schema`. `session_id` is optional and groups chat runs into a conversation. Use the parallel `/dry-run` endpoint to test a workflow without committing the result.

A run moves through these states:

Run status

Meaning

`pending`

Created, not yet executing.

`running`

Executing nodes.

`paused`

Waiting on a human-in-the-loop response or an async step.

`completed`

All reachable nodes finished; named outputs collected.

`failed`

A node failed, or the run timed out.

`cancelled`

Cancelled by a user or by the run-cancel cascade.

Each node execution is recorded as a **step**, with its own status:

Step status

Meaning

`pending`

Not yet started.

`running`

Executing.

`completed`

Finished successfully.

`failed`

Errored.

`skipped`

Its branch condition was false, or a required input came from a skipped node.

`paused`

Waiting on a human-in-the-loop response.

A step record stores the resolved input, the output, the status, token usage, the agent configuration it ran with, and — when the node completed normally — the per-edge condition results (`edge_evaluations`), so the run history can show exactly why each branch was taken or skipped.

### Reading a run

-   `GET /api/v1/workflow/runs/{run_id}` — the run record, including `status`, `total_tokens`, `output`, and `named_outputs`.
-   `GET /api/v1/workflow/runs/{run_id}/steps` — the per-step trace.
-   `GET /api/v1/workflow/runs/{run_id}/stream` — real-time run events over SSE while the run is in progress.

`named_outputs` is a flat map keyed by your publish nodes' output field keys — the AI Employee's public output contract. Workflows with no publish nodes return a null `named_outputs` by design.

### A worked example

Consider a support workflow: a `start` node, a classification agent, two branches, and a publish node.

```text
start ──▶ classify ──[priority == "HIGH"]──▶ escalate ──▶ publish
                  └──[priority == "LOW"]───▶ auto_resolve ─▶ publish
```

1.  A run starts with `input_params = {"message": "My account is locked!"}`.
2.  The `classify` agent (an `intent_classification` agent) emits `output.priority`.
3.  The platform evaluates the two edges leaving `classify`. If `priority` is `"HIGH"`, the `escalate` branch runs and `auto_resolve` is skipped; if `"LOW"`, the reverse.
4.  The `publish` node writes the AI Employee's named outputs from whichever branch ran.

The run's step trace shows `classify` as `completed`, one branch `completed`, the other `skipped`, and the `edge_evaluations` on `classify` recording which condition was true.

## Where to go next

-   [Agents](/builder/v2/core-concepts/agents) — what an `agent` node can do.
-   [Conditions and Expressions](/builder/v2/core-concepts/conditions-and-expressions) — edge conditions and `{{variable}}` data passing.
-   [Human in the Loop](/builder/v2/core-concepts/human-in-the-loop) — pausing and resuming a run.
-   [API reference](/builder/v2/api-reference) — the full run and version API.
