Graph model

A Flow workflow is a directed graph: an object with two lists, nodes and edges. Nodes do the work; edges decide what runs next and which value arrives there.

Entry
LLM Response
LLM
Exit
  • Execute + Data
  • LLM
  • Execute + Data + Streaming
A minimal chat workflow: entry, model answer, exit. The violet edge carries the model configuration.

A node

A node has an id, a registry type, a canvas position and a configuration:

{
  "id": "llm_response_1",
  "type": "llm_response",
  "position": { "x": 320, "y": 80 },
  "data": { "label": "Answer", "config": { "prompt_template": "..." } }
}

The type determines everything else: the set of ports, the configuration fields, the badges on the card. The full list is in the node reference.

An edge

An edge connects a port of the source to a port of the target:

{
  "id": "e1",
  "source": "entry_1", "target": "llm_response_1",
  "sourceHandle": "output", "targetHandle": "message",
  "edgeType": "execute_data"
}

sourceHandle and targetHandle are the ids of the ports the node renders for its current configuration: some ports appear and disappear with a toggle (on_error, for instance, is only visible once the node’s error output is enabled). edgeType is the wire type; the editor derives it from the ports you connected — see wires.

Ports

A port is a named connection point. It has two independent properties: the wire (does the edge carry a trigger, a value, a token stream) and the payload type (string, object, file, table — see port types).

Most nodes expose one combined output port: it both triggers the next node and hands it the value. The “Split input ports” / “Split output ports” toggles break such a port into two legs — <port>_exec and <port>_data — for when order and value must travel different routes.

Entry and exit

The entry node is the single starting point, and there must be exactly one. It has no execution input and two outputs:

  • output — the user’s message (a string) for chat, Telegram and webhooks; when the run is started with a plain JSON object, the whole object arrives here instead;
  • data — the trigger context: trigger kind, session and workflow ids, the Telegram block, the webhook payload.

The exit node returns the result to the user or as the API response. At least one is required; its end input takes the final value and the optional files input returns files alongside the answer. The “Output Expression” field, when non-empty, replaces whatever arrived on end.

How the engine decides what to run

This is where Flow parts ways with flowchart intuition. The engine does not topologically sort the graph and does not walk the nodes top to bottom. It seeds only the entry point (and the entry points of nested subgraphs), then works by events: when a node finishes, each of its outgoing edges either activates its target or is declared dead.

  • A node starts once all of its incoming dependencies have fired. Independent branches run concurrently rather than one after another.
  • If some inputs are dead (an if went the other way) while the others fired, the node still runs — with the payload of the branch that made it.
  • If every input is dead, the node does not run: the execution report marks it skipped and gives a reason — branch_not_taken, upstream_failed, upstream_not_run and the like.

Two practical rules follow. First, a node with no activating input never starts on its own, even when it has “nothing to wait for”. Second, an edge drawn back upstream is not a loop — it deadlocks the scheduler, and validation rejects such a graph with graph.cycle. Repetition is expressed with the while_loop and for_each nodes, never with a hand-drawn cycle.

What next