Skip to main content

Conversation flows

A conversation flow is an AI agent’s in-conversation routing map. Attach one under an agent’s config.conversation_flow and every turn picks an active node whose instructions replace the single static base system prompt that turn — instead of relying on one prompt for the whole conversation. This is the multi-state routing model most operators know from voice agents, available on any chat or voice agent in Orbit. Classic Flows route contacts through channel automations (send, delay, branch, run an agent). A conversation flow routes the conversation itself between named states once the agent has taken over.

Concept

  • Each node carries its own instructions — the per-state prompt that becomes the active prompt segment while that node is selected.
  • Edges carry routing conditions evaluated against the customer’s latest message (first match wins).
  • When no condition matches, the turn either falls through a node’s unconditional edge or stays on the current node — the conversation never resets mid-flow.
  • A flat routing preamble is prepended ahead of your base persona/policy prompt: “This agent is a multi-state conversation flow. Stay on the ACTIVE NODE’s instructions; branch to one of the fallback nodes only when the customer’s message matches its condition.” — the base prompt stays in place below it.
Limits per agent: 64 nodes, 16 edges per node, 30 000 characters of node instructions, 8 fallback ids.

Graph anatomy

The persisted shape, stored verbatim in config.conversation_flow:
Field semantics:

Conditions

A condition is matched case-insensitively against the customer’s latest message, in one of three shapes: An edge with a missing or empty condition is unconditional — it is the node’s fallback branch, taken only when no condition edge fires. A /pattern/flags string that fails to compile is treated as a keyword list instead, so a bad regex cannot stall routing. The evaluation order per turn: walk the active node’s edges in declaration order; the first condition that matches wins. If nothing matches, take the unconditional edge if one exists; otherwise stay on the active node.

Fallbacks

fallbacks names nodes the model may branch to when the active node itself has no answer. Fallback entries that name the entry node are deduplicated away. The composed system prompt lists fallbacks explicitly as [FALLBACKS — branch ONLY when one of these matches] with their instructions plus routing conditions, so the model can swap states mid-conversation without inventing a node that is not on the graph.

Validation contract

Save validation rejects the dangerous classes up front, with one issue per path so the API error names exactly which edge or fallback is wrong:
  • Duplicate node ids — nodesDuplicate conversation-flow node id….
  • entry that names a node not on the graph — entry.
  • An edge whose to names an unknown node — nodes[i].edges[j].to.
  • A fallback id that names an unknown node — fallbacks.
Two limits apply at save too: no more than 64 nodes and 16 edges per node; each instruction is capped at 30 000 chars and each condition at 500 chars. Malformed saved rows degrade cleanly. Conversation-flow config is a JSONB config-bag value; a row that fails the schema (for example an empty nodes array, a number where a string belongs, or a malformed edge) is not fatal at run time. The executor re-validates the blob when it builds the turn, treats any failure as undefined, and the agent runs exactly as it did before flows existed — on its base system prompt alone. A bad save can never block a customer-facing turn; at worst, the routing layer disappears until the config is corrected.

Versioning & experiments

The flow graph is versioned through agent versions and A/B experiments:
  1. Bump version in config.conversation_flow (2, 3, …) for a meaningful revision.
  2. Save an agent version snapshot (POST /agents/:id/versions) — the snapshot captures the whole agent config, including the flow blob.
  3. Run that candidate as an experiment variant; the variant’s snapshot carries conversation_flow verbatim, so the experiment pins a specific flow revision and an A/B winner promotes the candidate’s flow along with its prompt.
Everything on this surface is tenant-owned — the graph, the snapshots, and the experiment pins live in your workspace; nothing crosses tenant boundaries.

Deterministic replay

Every turn replays the flow from the entry node over the conversation’s user messages (chronological order, current turn appended). Resolution is deliberately stateless — no per-pod map, no shared cache — so:
  • Any replica recomputes the identical active node for a given history.
  • Both turn paths — run and streamTurn — produce the same routing.
  • Voice and chat agents share branch semantics: the same graph routes a phone call and a chat session the same way.
A turn that no edge matches stays on the active node instead of hopping back to entry, so a conversation does not reset mid-flow.

Testing a route before you send

The same logic powers a simulate step: pick an active node, supply a hypothetical customer message, and resolve which node would activate — without mutating any conversation state. Prove a branch set before traffic ever sees it.

Dashboard surface

Two editors in the dashboard are built around conversation flows:
  • Agent Studio (/agents/studio) — the visual canvas where you draw nodes, drag edges, and edit per-node instructions with their conditions. The same graph is what save-validation and the runtime executor consume.
  • Conversations (/agents/conversations) — per-conversation transcripts. When an agent has a flow attached, the transcript shows the same deterministic routing the executor used, so a mis-routed branch is inspectable per turn against the customer’s message.
Conversation flows are edited and versioned alongside the rest of the agent config; see Agent versions for how snapshots and experiments pin a revision.

Worked examples

Billing / tech triage

The overview-page sketch expanded into a complete graph — an unconditional edge plus one fallback keeps the agent on the rails when the customer mentions something outside both intents:
Turn flow: “My invoice looks wrong” — keyword invoice fires on the first edge, the turn runs on billing. “The app keeps crashing” — the tech regex fires, the turn runs on tech. “What are your hours?” — no condition matches, the triage node has no unconditional edge, so the conversation stays on triage; the model can still use the general fallback.

Escalation with fallbacks

An escalation graph where unresolved or unhappy customers move to an escalation node with an explicit human-handoff fallback:
Turn flow: “I want a supervisor” — an explicit human request fires, the turn activates escalation. “I’ll file a chargeback” — the high-stakes regex fires. Anything else stays on support; the unconditional fallback pool still offers escalation when the support node itself cannot answer.

See also