> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ryvo.so/llms.txt
> Use this file to discover all available pages before exploring further.

# Flows: split a conversation into stages

> What a conversation flow is, how the agent moves between stages, and how to read and change a flow from the API, the MCP server and the CLI.

A **flow** splits a conversation into **stages**. Each stage has its own instructions and its own tools, and the agent moves from one to the next when a condition is met: the customer wants to book, a tool answered, a variable has a value. An agent without a flow works on a single prompt, as always.

In the Builder, the **Flow** section shows the flow as a canvas. Everything you build there is also available from the [public API](/api-reference), the [MCP server](/mcp/overview) and the [CLI](/guides/developers/cli), with the same rules.

## How a flow works

A flow is a graph: **nodes** (the steps) joined by **edges** (the ways out of each step).

| Node type | What it does |
| - | - |
| `prompt` | A conversation stage: the agent talks with the customer following the stage `prompt`. |
| `say` | The agent says the text in `prompt` and moves on. |
| `tool` | Runs every tool in `tool_ids` when it is entered, without the model choosing. |
| `set_variables` | Writes values into the flow variables. |
| `end` | Ends the conversation. |

The conversation starts at `entry_node_id`. At the end of every turn, the current node's edges are evaluated in this order:

1. `tool_result` and `variable` conditions, which need no model, by `priority` (highest first).
2. Every `llm_decision` condition, in a single model call: the model picks the way out that matches, guided by each `description`.
3. The `always` edge, which is the explicit "else".
4. If nothing applies, the conversation stays where it is.

**Global nodes** (`global_nodes`) can be entered from any stage through their `global_condition`, and when they finish the conversation returns to the stage it came from. Useful for "the customer asks for a human" or "the customer wants to cancel".

<Warning>
  A conversation stage (`prompt`) cannot have an `always` edge. The agent would leave the stage on the very first turn in which the model does not choose another way out. Use `llm_decision`, `tool_result` or `variable` instead. `always` is fine on `say`, `tool` and `set_variables` nodes, which do not wait for the customer. The opposite also holds: an `llm_decision` edge only works from a `prompt` stage, because the other steps do not talk and the model never chooses.
</Warning>

## A minimal flow

Qualify the caller, book if they want to, and say goodbye:

```json theme={null}
{
  "version": 2,
  "entry_node_id": "qualify",
  "nodes": [
    {
      "node_id": "qualify",
      "node_type": "prompt",
      "label": "Qualify",
      "prompt": "Find out whether the caller wants to book an appointment or has a question.",
      "edges": [
        { "edge_id": "to_book", "to_node_id": "book", "condition": { "type": "llm_decision", "description": "The caller wants to book an appointment" } },
        { "edge_id": "to_goodbye", "to_node_id": "goodbye", "condition": { "type": "llm_decision", "description": "The caller has nothing else to ask" } }
      ]
    },
    {
      "node_id": "book",
      "node_type": "prompt",
      "label": "Book",
      "prompt": "Ask for the preferred day and time and confirm it.",
      "edges": [
        { "edge_id": "book_done", "to_node_id": "goodbye", "condition": { "type": "llm_decision", "description": "The appointment is confirmed" } }
      ]
    },
    { "node_id": "goodbye", "node_type": "end", "prompt": "" }
  ]
}
```

Every field, every condition and every rule is in the `Flow` schema of the [API reference](/api-reference).

## Read the flow

`GET /v1/agents/{id}` returns it in `config.flow`, and `GET /v1/agents/{id}/versions/{version}` in `flow`. It is `null` when the agent has no flow.

```bash theme={null}
curl https://api.ryvo.so/v1/agents/$AGENT_ID \
  -H "Authorization: Bearer $RYVO_API_KEY"
```

## Change the flow

Send the **whole** graph in `flow` with `PATCH /v1/agents/{id}`. It replaces the current one: there is no way to send just one node, so read the flow first, change it and send it back.

```bash theme={null}
curl -X PATCH https://api.ryvo.so/v1/agents/$AGENT_ID \
  -H "Authorization: Bearer $RYVO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "flow": { "version": 2, "entry_node_id": "qualify", "nodes": [ ... ] }, "expected_version": 4 }'
```

* `"flow": null` removes the flow, and the agent goes back to working on its prompt.
* Like any configuration change, it is saved as a new **draft** version. Publish it with `POST /v1/agents/{id}/publish` so calls use it.
* Pass `expected_version` (the `current_version` you read) so you do not overwrite a change made in the Builder in the meantime.

If the graph breaks a rule (an edge to a node that does not exist, an `always` edge on a stage, an `llm_decision` from a step that does not talk, a blank `description`, a tool the agent does not have), the answer is `400 invalid_flow` and **nothing is saved**, not even the `name` or `status` sent in the same request. A node can only name the agent's tools: built-in ones and the ones in the version's `tool_ids`.

## From the MCP server and the CLI

* **MCP**: `get_agent` returns the flow and `update_agent` accepts `flow` with the same rules. Your AI client can read the flow, change it and send it back whole.
* **CLI**: `ryvo agents get <id>` shows a `flow` line with the number of nodes and the entry stage; add `--json` to see the whole graph. To change it, pass the body with `--data`:

```bash theme={null}
ryvo agents get $AGENT_ID --json > agent.json
# edit config.flow and save it as flow.json: { "flow": { ... } }
ryvo agents update $AGENT_ID --data @flow.json
ryvo agents publish $AGENT_ID
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.