Multi-step calls (flow agents)
When you describe a call with several scenarios or conditions — "if they're a new patient do this, if they're existing do that, and if they ask for a person put them through" — your assistant builds a flow agent: the call as a graph of steps, with a condition on every way from one step to the next.
A single-context agent is one conversation with one goal. A flow agent is several, joined by rules: each step has its own instructions (and, if you like, its own model, voice or variables to collect), and each exit says when the call moves on and where to. The assistant uses create_flow_agent for this; the graph is the same metadata.flow the flow builder draws and the Flow agents API takes.
This needs the Create agents permission. Steps that transfer calls or call APIs also need Give agents tools (Permissions, limits & safety).
When your assistant reaches for a flow
| You say something like… | It becomes |
|---|---|
| "If they're new, take their details; if they've been before, check their date of birth." | Two branches on a captured answer (equation exits on an enum). |
| "Press 1 for appointments, 2 for billing." | A keypad step with one exit per digit (dtmf exits). |
| "Ask their budget first, then their timeline, then book a demo if both fit." | Steps in order, each collecting its values before the next. |
| "Whenever they ask for a person, transfer to the front desk." | A global step reachable from every step. |
| "If it's an emergency, tell them to call 108 and hang up." | A global ending step. |
| "Hand billing questions to our billing agent." | An agent hand-off step. |
Example: a clinic front desk
You: Build an agent for Sunrise Clinic's phone line. Find out if the caller is a new or an existing patient. New patients: get their name and the day they'd like to come. Existing: check their date of birth, then ask what they need. Either way, end politely. If anyone mentions an emergency, tell them to call 108 and hang up. If they ask for a person, transfer to +91 80 4567 8900.
Assistant: Here's the plan — five steps: Greet (new or existing?) → New patient (name, preferred day) or Existing patient (date of birth, reason) → Goodbye; plus two that can happen at any point: Emergency (call 108, then hang up) and Front desk (transfer). I'll use the clinic's usual voice. About $0.06 a minute. Create it?
You: Yes.
The assistant sends:
{
"name": "create_flow_agent",
"arguments": {
"name": "Sunrise Clinic front desk",
"system_prompt": "You are the front desk of Sunrise Clinic. Be warm and brief. Never give medical advice.",
"opening_line": "Hello, Sunrise Clinic. I'm the clinic's AI assistant.",
"country": "IN",
"flow": {
"startNodeId": "greet",
"nodes": [
{
"id": "greet", "kind": "conversation", "name": "Greet",
"prompt": "Find out whether the caller is a new or an existing patient.",
"extract": [{ "name": "patient_type", "type": "enum", "values": ["new", "existing"], "description": "whether this is their first visit" }]
},
{
"id": "new", "kind": "conversation", "name": "New patient",
"prompt": "Ask for their full name and the day they would like to come in.",
"extract": [
{ "name": "patient_name", "type": "string", "required": true },
{ "name": "preferred_day", "type": "string", "required": true }
]
},
{
"id": "existing", "kind": "conversation", "name": "Existing patient",
"prompt": "Ask for their date of birth to find their record, then what they need."
},
{ "id": "bye", "kind": "end", "name": "Goodbye", "entryMessage": "Thank you for calling Sunrise Clinic. Goodbye." },
{
"id": "emergency", "kind": "end", "name": "Emergency",
"isGlobal": true, "globalCondition": "the caller describes a medical emergency",
"entryMessage": "This sounds urgent. Please hang up and call 108 right now."
},
{
"id": "front_desk", "kind": "transfer", "name": "Front desk",
"isGlobal": true, "globalCondition": "the caller asks to speak to a person",
"globalExamples": ["can I talk to someone", "put me through to reception"],
"config": { "destinations": [{ "numbers": ["+918045678900"] }], "message": "Of course — connecting you to the front desk." }
}
],
"edges": [
{ "id": "to-new", "source": "greet", "target": "new", "priority": 0, "condition": { "kind": "equation", "expr": "{patient_type} == 'new'" } },
{ "id": "to-existing", "source": "greet", "target": "existing", "priority": 1, "condition": { "kind": "equation", "expr": "{patient_type} == 'existing'" } },
{ "id": "new-done", "source": "new", "target": "bye", "condition": { "kind": "ai", "describe": "the name and preferred day are both given" } },
{ "id": "existing-done", "source": "existing", "target": "bye", "condition": { "kind": "ai", "describe": "the caller's question has been answered or noted" } }
]
}
}
}
Telenow checks the graph exactly as a call will run it, then creates the agent:
{
"agent_id": "8b1f…",
"name": "Sunrise Clinic front desk",
"kind": "flow",
"warnings": [],
"message": "Created. Use agent_id with place_call (agent_id) or create_campaign; the owner can refine it in the Telenow agent builder."
}
Assistant: Done — it's in your agents as "Sunrise Clinic front desk". Want me to call you with it so you can try it?
The Front desk step is a transfer, so this example needs Give agents tools. Without it, the assistant is told so and can build the rest, leaving the transfer for you to add in the flow builder.
Example: a keypad menu
You: Make an outbound reminder that says the appointment time, then: press 1 to confirm, 2 to reschedule.
{
"name": "Appointment reminder",
"opening_line": "Hello {name}, this is Sunrise Clinic's AI assistant.",
"variables": [
{ "name": "name", "required": true },
{ "name": "time", "required": true }
],
"flow": {
"startNodeId": "remind",
"nodes": [
{
"id": "remind", "kind": "dtmf", "name": "Remind",
"entryMessage": "Your appointment is tomorrow at {time}. Press 1 to confirm, or 2 to reschedule.",
"guards": { "dtmfRouteOnly": true }
},
{ "id": "confirmed", "kind": "end", "entryMessage": "Thank you, you're confirmed. See you tomorrow." },
{ "id": "reschedule", "kind": "conversation", "prompt": "Ask which day and time suit them better, and note it." },
{ "id": "noted", "kind": "end", "entryMessage": "Thanks — the clinic will confirm your new time. Goodbye." }
],
"edges": [
{ "id": "press-1", "source": "remind", "target": "confirmed", "label": "Confirm", "condition": { "kind": "dtmf", "digit": "1" } },
{ "id": "press-2", "source": "remind", "target": "reschedule", "label": "Reschedule", "condition": { "kind": "dtmf", "digit": "2" } },
{ "id": "done", "source": "reschedule", "target": "noted", "condition": { "kind": "ai", "describe": "a new day and time are given" } }
]
}
}
{name} and {time} are declared in variables, so every call must pass them — one-off with place_call, or per person in a campaign. As the start step, the keypad menu is read right after the opening line. dtmfRouteOnly sends a key press straight to its exit, with no model turn.
Rules the graph follows
Telenow checks these on every create and update, and refuses a graph that breaks one — such a graph would be thrown away on every call and the agent would run as a single-context agent without saying so:
- every step has a unique
idand a knownkind, andstartNodeIdis one of them; - every exit has a unique
id, a realsource(or"*", every step) andtarget, and a complete condition —aineedsdescribe,equationneedsexpr,dtmfneedsdigit; - a
transferstep has a phone number, anagentstep a target agent, aplay_audiostep a recording; - at most 100 steps and 300 exits.
A graph of one step with no exits is refused too: calls would run it as a single-context agent and ignore the step.
What would run but may misbehave comes back as warnings — for example a goodbye left in config.message (an end step says its entryMessage), an equation that reads a variable nothing collects, or a pass-through step with no way out. The full list is in Flow agents API.
A few things worth knowing when you describe a flow:
- Captured values are text. To branch on an answer, collect it as an
enumwith the allowedvalues(the caller's words are matched to one of them) and compare with quotes:{patient_type} == 'new'. - A fallback exit beats the AI exits on the same step — a step uses one or the other.
- Rule exits need no model. A keypad exit needs no router call either, and with
guards.dtmfRouteOnlya key press goes straight to its exit with no model turn at all. AI exits ask a model on each caller turn. Prefer a keypad or rule exit when a branch can be decided without the model. - Transfer and hand-off steps say their
config.messagebefore connecting — never anentryMessage. - The start step's own
entryMessageis not said when it is a conversation step — the agent's opening line is. - Changing a flow later: "add a step for insurance questions" — the assistant reads the graph (
get_agent), changes it, and sends the whole graph back (update_agent).