Conversation-flow agents
When the cost of going off-script is high, payments, identity verification, structured booking, model the conversation as a flow. A flow is a typed state machine: nodes hold prompts, edges declare conditions, and the LLM still does the talking.
When a flow beats a single prompt
- There is a sequence of facts you must collect in order.
- Some branches need a tool call (lookup, charge, write to a system of record).
- Compliance requires deterministic copy at certain steps (consent, disclosure).
- You want a human-readable diagram of every path the agent can take.
Defining a flow
await saaya.agents.create({
name: "Refund Resolver",
flow: {
start: "greet",
nodes: {
greet: {
prompt: "Greet the caller and ask for their order id.",
on: { collected: { orderId: "lookup" } },
},
lookup: {
tool: "orders.fetch",
args: { orderId: "{{vars.orderId}}" },
on: { ok: "decide", notFound: "apologize" },
},
decide: {
prompt:
"Summarize the order. If within 30 days, offer a refund. Otherwise offer store credit.",
branches: [
{ when: "{{order.daysOld}} <= 30", to: "refund" },
{ else: true, to: "credit" },
],
},
refund: { tool: "payments.refund", args: { orderId: "{{vars.orderId}}" }, on: { ok: "thank" } },
credit: { tool: "credits.issue", args: { orderId: "{{vars.orderId}}" }, on: { ok: "thank" } },
apologize: { prompt: "Apologize, take down their email, escalate.", on: { collected: { email: "end" } } },
thank: { prompt: "Confirm the outcome and end warmly.", end: true },
},
},
voice: { provider: "elevenlabs", voiceId: "rachel" },
llm: { provider: "anthropic", model: "claude-opus-4-7" },
});Variables and validation
Every node can declare slots it expects to collect. Saaya validates them with a schema and re-prompts if the user gives a malformed answer (e.g. an email that is not RFC-shaped). Validation messages are part of the node, not the global prompt, they only fire on that step.
Fallbacks and escapes
A flow does not lock the user into a track. Each node has an implicit `fallback` edge that fires when the LLM cannot map the utterance to any declared transition. The fallback usually escalates, to the single-prompt fallback, to a human, or to a "what would help most right now?" reset.
Keep the flow shallow