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

# Transitions, Variables & the Director

> How a Voice Workflow moves between nodes — typed variables, prompt vs equation edge conditions, the Director that extracts and routes, and global interruptions.

# Transitions, Variables & the Director

A Voice Workflow is a state machine: the caller is always sitting on exactly one node, and after each thing they say the engine decides where to go next. This page explains the machinery behind that decision — the **variables** the flow tracks, the **edge conditions** that wire nodes together, the **resolution order** the engine follows every turn, and the **Director** (a fast LLM) that extracts data and picks ambiguous routes.

For the nodes these transitions connect, see [Node Types](/docs/voice/workflows/nodes). To build and arrange the graph, see [The Workflow Builder](/docs/voice/workflows/builder).

## Variables

Variables are the workflow's memory. You declare them in the **Variables** panel (the right sidebar in the builder), reference them anywhere as `{{name}}`, and the flow uses them for two things:

* **Collection** — a Conversation or Collector node `extract`s declared variables from what the caller says, and `require`s certain ones before the flow may move on.
* **Branching** — edge conditions and Condition nodes read variables to decide the next node.

Each variable is **typed**, and may optionally be constrained to an **enum** (a fixed list of allowed values, useful for keeping the agent on a known set of options).

| Type      | Holds         | Example value | Notes                                              |
| --------- | ------------- | ------------- | -------------------------------------------------- |
| `string`  | Free text     | `"Mumbai"`    | Use an enum to restrict to known options           |
| `integer` | Whole numbers | `42`          | Compare with `>`, `<`, `>=`, `<=` in equations     |
| `number`  | Decimals      | `19.99`       | For amounts, durations, scores                     |
| `boolean` | true / false  | `true`        | Ideal for branch flags (e.g. `{{slot_available}}`) |

<Tip>
  Give variables clear, lowercase names you can read inside `{{ }}` — for example `{{caller_name}}`, `{{preferred_date}}`, `{{slot_available}}`. The same name is reused when an [API Request](/docs/voice/workflows/api-request) maps a response field back into a variable, so consistency pays off.
</Tip>

## Edge conditions

Edges are top-down: each edge leaves the **bottom** of one node and connects to the **top** of another, and the canvas prints each edge's condition as a label so you can read the routing at a glance.

You set an edge's condition in the **edge condition editor** (select an edge; the Inspector tab switches to the editor). It has a single mode toggle with three options:

<CardGroup cols={3}>
  <Card title="Unconditional" icon="arrow-right-long">
    The "else" path. No test — taken when no other edge from this node matches.
  </Card>

  <Card title="Prompt" icon="comments">
    A natural-language condition the Director judges (a textarea), e.g. "caller agreed to continue".
  </Card>

  <Card title="Equation" icon="code-branch">
    A deterministic expression evaluated in code, e.g. `{{age}} > 18`. A guided builder (variable / operator / value) plus a raw-expression option.
  </Card>
</CardGroup>

### Prompt conditions

A prompt condition is plain English describing when this edge should fire. The [Director](#the-director) reads the caller's latest turn and decides whether the condition is met. Use prompt conditions when intent is fuzzy and only a model can judge it:

* `caller agreed to continue`
* `caller wants to speak to a human`
* `caller raised a pricing objection`

### Equation conditions

An equation is a deterministic expression over variables, numbers, quoted strings, and booleans. Because equations run in code — not through the model — they always resolve the same way for the same inputs.

| Operator   | Meaning                        | Example                                         |
| ---------- | ------------------------------ | ----------------------------------------------- |
| `exists`   | The variable has been captured | `{{email}} exists`                              |
| `==`       | Equal to                       | `{{slot_available}} == true`                    |
| `!=`       | Not equal to                   | `{{plan}} != "free"`                            |
| `>`        | Greater than                   | `{{age}} > 18`                                  |
| `<`        | Less than                      | `{{quantity}} < 5`                              |
| `>=`       | Greater than or equal          | `{{score}} >= 0.8`                              |
| `<=`       | Less than or equal             | `{{wait_minutes}} <= 10`                        |
| `CONTAINS` | Substring / membership         | `{{city}} CONTAINS "Mumbai"`                    |
| `AND`      | Both sides true                | `{{age}} >= 18 AND {{consent}} == true`         |
| `OR`       | Either side true               | `{{plan}} == "pro" OR {{plan}} == "enterprise"` |

Operands may be `{{variable}}` references, bare numbers, `"quoted strings"`, or `true` / `false`.

## Resolution order

This is the most important rule on the page. After each caller turn, the engine decides the next node by checking, **in this exact order**:

<Steps>
  <Step title="Equation edges first (deterministic)">
    Every equation edge leaving the current node is evaluated **in code**, top to bottom. The **first one that is true wins** — the flow takes that edge immediately. No model is involved, so this step is fully predictable.
  </Step>

  <Step title="The Director's prompt-edge pick">
    If no equation edge matched, the Director's chosen prompt-condition edge is taken — but **only if the node's `require[]` variables are all satisfied**. If a required variable is still missing, the flow does not leave (see [Staying on a node](#require-and-staying-on-a-node)).
  </Step>

  <Step title="Global enter-conditions">
    If neither of the above produced a transition, the engine checks whether any [global node's](#global-nodes-and-interruptions) enter-condition matches. If one does, the flow jumps to that global node, handles the situation, then returns.
  </Step>

  <Step title="Otherwise, stay">
    If nothing matched, the caller stays on the current node and it speaks again.
  </Step>
</Steps>

<Info>
  **Why equation-first matters.** Equations are checked deterministically before the model ever sees the routing decision. That makes critical branches — eligibility gates, "did we get a yes", "is a slot available" — debuggable and reproducible: the same variables always take the same edge, every call. Reserve prompt conditions for the genuinely fuzzy cases the model is good at.
</Info>

## The Director

The **Director** is a fast LLM that runs each caller turn to do two jobs:

1. **Extract** the current node's declared variables from what the caller just said (filling in `{{name}}`, `{{preferred_date}}`, and so on).
2. **Pick** which **prompt-condition** edge is met, when routing requires judgement.

Equation conditions and global enter-conditions are evaluated alongside this (equations in code, as above) — the Director only owns the prompt-based part of the decision.

The Director is **gated**: it only runs when routing is genuinely ambiguous, so it does not add latency to turns that an equation already resolves. You choose the Director **model** and **temperature** in the builder's **Settings** tab. The default is a fast model; see [Publishing & Going Live](/docs/voice/workflows/publishing) for guidance on choosing it.

<Warning>
  A slow Director model adds latency on **every** turn that needs routing — the caller waits longer before the agent replies. Keep the Director on a fast model and push deterministic branches into equation edges; only reach for a heavier model if extraction quality genuinely demands it.
</Warning>

## require\[] and staying on a node

A Conversation or Collector node can mark some of its variables as `require[]` — the flow may not leave until those variables are captured. If the caller hasn't yet given a required value, the engine **stays on the node and re-asks**, turn after turn, until it has what it needs.

This is how you guarantee data before moving on: a booking node that requires `{{preferred_date}}`, or a collector that requires a verified `{{email}}`, will keep the caller on that phase until the value is in hand. Only then does Step 2 of the [resolution order](#resolution-order) (the Director's prompt-edge pick) become eligible. Equation edges are still checked first regardless — so a require gate governs prompt-edge transitions, not hard equation branches you've wired for edge cases like an early exit.

## Global nodes and interruptions

A **global node** is a Conversation node marked **Global** with an **enter-condition** (a prompt or an equation). It is reachable from **any interruptible node** — when its enter-condition matches, the flow jumps to the global node no matter where the caller currently is.

This is how you handle situations that can come up anywhere in the call: the caller raises a pricing objection, asks for a human, or goes off-script. The global node handles the situation, then **returns the caller to the node they were on** so the main flow resumes where it left off.

* **Enter-condition** — a prompt (e.g. "caller asks to speak to a human") or an equation, evaluated as part of the resolution order's globals step.
* **Interruptible toggle** — each Conversation node controls whether globals may interrupt it. A node with interrupts off is shielded from globals during its turn.
* **Cooldown** — measured in turns, this prevents a global from re-triggering immediately after it just ran, so the caller isn't bounced back into it on the very next turn.

<Note>
  Globals fire only **after** equation edges and the Director's prompt-edge pick have had their chance (step 3 of the [resolution order](#resolution-order)). A clean, in-script transition always wins over a global interruption.
</Note>

## Putting it together

A typical turn looks like this: the caller speaks → the Director extracts the current node's variables → equation edges are checked in code (first-true-wins) → if none match and `require[]` is satisfied, the Director's prompt-edge is taken → otherwise a global may interrupt → otherwise the caller stays. The destination node then produces the reply.

For the worked end-to-end example — an API call that maps a response field into a boolean a Condition then branches on — see the [API Request Node](/docs/voice/workflows/api-request). To start from a ready-made graph, see [Workflow Templates](/docs/voice/workflows/templates).
