> For the complete documentation index, see [llms.txt](https://docs.visdom.virtuslab.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.visdom.virtuslab.com/visdom-orchestrator/user-guide/flows-nodes-ports-and-edges.md).

# Flows: nodes, ports and edges

A flow is a directed graph. Nodes do the work; edges carry either a value from one node's output port to another node's input port, or nothing at all, which is pure sequencing. The executor is reactive: a node runs when its upstream edges are satisfied, so parallelism falls out of the topology rather than being declared.

## Node kinds

| Kind        | What it does                                                            | Key fields                                                                                                                                    |
| ----------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `trigger`   | Entry point. Flow parameters enter here and fan out to the first nodes. | none                                                                                                                                          |
| `agent`     | Runs an agent definition as a pod.                                      | `agent`, `agent_version`, `inputs`, `outputs`, `on_failure`                                                                                   |
| `decision`  | Branches on CEL predicates evaluated against named inputs.              | `predicate_inputs`, `branches[{name, when}]`                                                                                                  |
| `approval`  | Pauses until a human with the named role approves or rejects.           | `approver_role`, `title`, `context_inputs`, `expires_after_seconds`, `on_expire`, `branches`                                                  |
| `loop`      | Repeats a sub-flow while a condition holds, or over a collection.       | `mode`, `body`, `body_inputs`, `continue_when`, `max_iterations`, `over`/`as_var`, `iteration_concurrency`, `on_iteration_failure`, `outputs` |
| `sub-flow`  | Runs another flow as a single node.                                     | `flow`, `inputs`, `outputs`, `on_failure`                                                                                                     |
| `parallel`  | Fan-out gate: every downstream edge fires at once.                      | none                                                                                                                                          |
| `join`      | Wait-all gate: succeeds when every upstream edge is terminal.           | inputs declared via `__control__`                                                                                                             |
| `terminate` | Ends the run early with an explicit status.                             | `reason`, `flow_status`                                                                                                                       |
| `output`    | Collects the flow's declared outputs.                                   | `inputs`                                                                                                                                      |

## Edges carry data or sequence

An edge names a source node, a source port, a target node and a target port. When the target port is a declared input, the value flows. When it is the reserved port `__control__`, nothing flows: the edge exists only to say *this happens after that*.

Here is the shape, from the shipped issue-to-PR template:

```yaml
edges:
  # data: the trigger's issue_url reaches two nodes
  - { from: trigger,   port: issue_url, to: design,  to_port: issue_url }
  - { from: trigger,   port: issue_url, to: open_pr, to_port: issue_url }
  # data: one node's output becomes another's named input
  - { from: design,    port: output,    to: implement, to_port: design_path }
  # sequencing only: run_quality waits for run_tests, no value passed
  - { from: run_tests, port: output,    to: run_quality, to_port: __control__ }
```

Node inputs are bound with `${node.port}` references, so a node declaration reads as its own small contract:

```yaml
- id: implement
  kind: agent
  agent: implement
  agent_version: 1
  inputs:
    design_path: { from: "${design.output}" }
  outputs:
    - { name: output }
```

## Loops

A loop node runs a **sub-flow** as its body, not an inline block. It has a mode (`while`, or iterating `over` a collection with `as_var`), a `continue_when` CEL expression, a hard `max_iterations`, an `iteration_concurrency`, and an `on_iteration_failure` policy that decides whether one bad round kills the loop or is skipped.

Inside the condition, the sub-flow's own outputs are addressable per iteration, and the count is available for downstream sequencing:

```yaml
- id: review_loop
  kind: loop
  mode: while
  body:
    sub_flow_name: review-fix-iteration   # resolved to an id at materialise time
    version: 1
  body_inputs:
    pr_url: { from: "${open_pr.output}" }
  continue_when: "${loop.iter.had_fixes} != ''"
  max_iterations: 5
  iteration_concurrency: 1
  on_iteration_failure: continue
  outputs:
    - { name: iterations, from: "${loop.count}" }
```

{% hint style="danger" %}
**Three names break most first runs.** The edge out of a trigger uses the port `__control__`, not `ok`. The edge leaving a loop uses `__control__`, not `done`. And the loop variables are exactly `loop.iter.<output>` and `loop.count`. A near-miss in a CEL expression fails the run at the first edge, which reads like a broken graph rather than a typo.
{% endhint %}

## Validation and editing

Flow definitions are validated against a schema catalog before they persist, so a graph referencing a port that does not exist is rejected at save time rather than at run time.

The flow editor in the UI is currently a **read-only viewer**: graph rendering with per-kind node colours, no save. Editing a flow happens through the API or by installing a template. See [Known limitations](/visdom-orchestrator/user-guide/known-limitations.md).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.visdom.virtuslab.com/visdom-orchestrator/user-guide/flows-nodes-ports-and-edges.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
