Best Practices
These are the patterns and pitfalls worth knowing before you ship an AgentFlow. Most of them come from real flows that almost worked — a small habit here saves you a confusing debugging session later. Skim the whole page once; come back to the footguns when something misbehaves.
Writing good system prompts
The system prompt is where your Agent and LLM nodes get their personality, rules, and boundaries. A few habits make a big difference:
- Be specific about the role. "You are a friendly support assistant for a flower shop" beats "You are a helpful assistant." The more concrete the role, the more consistent the answers.
- State what the bot should NOT do. Tell it when to hand off to a human, what topics to refuse, and never to invent prices, stock, or policies it wasn't given.
- Give it the facts it needs, don't make it guess. Paste opening hours, return policy, or a short FAQ right into the prompt, or pull them in with a Retriever node.
- Show the format you want. If you need short replies, say "reply in one or two sentences." If you need a specific structure, use Structured Output instead of hoping the model formats it correctly.
- Keep it focused. One node, one job. A giant prompt that tries to do everything is harder to tune than two smaller nodes with a Condition between them.
Reference live values in your prompt with variables — e.g. Greet {{$flow.state.name}} or
Today is {{$current_date_time}}. See Variables for the full list.
Designing flow state
Flow state is the flow's memory of the current conversation — the key/value data your nodes read and write as the conversation moves along. Plan it before you build:
- Name keys for what they hold, in lowercase:
name,email,order_number,issue_type. - Declare the keys you'll need on the Start node so they exist from the first turn, then update them with a node's Update Flow State setting as you collect information.
- Turn on Persist Variables when the state must survive across turns (a multi-message lead capture, a support ticket being filled in). Leave it off for scratch values you only need within one turn. See Memory for how this interacts with conversation history.
- Read state back with
{{$flow.state.<key>}}in prompts, conditions, and tool arguments.
State keys named name, email, or phone are special — they sync straight to a customer's
Lead record. See the footgun below.
Branching cleanly
Flows get messy when routing is unclear. Keep branches easy to follow:
- Use Condition for rules you can write down — a value equals something, a number is over a threshold, a state key is set. It's fast, predictable, and free (no model call).
- Use Condition Agent for intent — "is this a complaint, a question, or a sales lead?" — where you need the model to read meaning, not match a value.
- Always cover the leftover case. Every branch point needs a path for input that matches nothing. Condition has a built-in Else; Condition Agent does not (see below).
- Label your branches. A named edge ("complaint", "else") makes the canvas readable months later.
- Don't nest deeply when a flat set of conditions will do. Wide and shallow is easier to read and debug than a deep ladder of nodes.
Error handling
Things fail — an API times out, a customer sends something unexpected, the model returns nothing. Design for it:
- Give every branch a destination. Unrouted input silently goes nowhere, which looks like the bot "ignoring" the customer.
- Have a fallback reply. When nothing matches, route to a Direct Reply that says "I didn't quite catch that — could you rephrase?" rather than dead-ending.
- Validate before you act. Before writing to a CRM or sending a confirmation, check with a Condition that the values you need are actually present in flow state.
- Hand off to a human for the hard cases. Use Human Input or manual mode so a person can take over when the bot is stuck.
- Watch what really happened. Use Executions to trace a run node-by-node and see exactly where a flow went wrong.
Footguns worth memorizing
These are the specific traps that trip up most builders. Each one is silent — the flow saves fine and only misbehaves at runtime — so they're worth committing to memory.
Condition Agent has no automatic "Else"
A Condition Agent creates exactly one output branch per scenario you define — and nothing else. Unlike a plain Condition, it does not add an implicit Else branch. If a customer's message matches none of your scenarios, it is silently dropped and the flow simply stops for that turn.
Always add an explicit "other / anything else" scenario as your catch-all, and wire it to a sensible fallback (a Direct Reply, a human handoff). Treat it as required, not optional.
{{$question}} needs the dollar sign
Inside {{ }} template expressions, runtime values are $-prefixed. The customer's latest
message is {{$question}} — with the $. The dollarless form {{question}} looks almost
identical but never resolves to anything; it just passes through as empty text.
This bites most often in a Condition Agent's scenario input, which must reference {{$question}} to
see what the customer said. The same rule applies to {{$flow.state.name}}, {{$vars.myVar}},
and every other variable. See Variables.
State keys name / email / phone become Lead columns
Flowera automatically turns flow state into a customer Lead — no save
node required. When a session's state updates, three key names are treated specially
(case-insensitive): name, email, and phone map to the Lead's own columns. Every other
key you define lands in the Lead's custom fields.
Two practical consequences:
- Building a lead-collector? Name your state keys exactly
name,email,phoneso they drop neatly into the Lead columns. See the Lead Collector tutorial. - Don't accidentally use those names for unrelated data (say, the name of a product) — it will overwrite the customer's name column.
Structured output + {{output.x}}
When an LLM node uses Structured Output, it returns
named fields (e.g. intent, sentiment) instead of one blob of text. Structured Output is an LLM
node feature — the Agent node does not have it.
The footgun: {{output.x}} is self-only. It works inside the producing node — typically its own
Update State rows — and nowhere else. On any other node it matches nothing, and the raw text
{{output.intent}} is sent to the customer instead of the value.
From another node, reference the producing node by its label: {{ Classify.intent }}. The label
before the dot must match the canvas label character-for-character, including case — this dotted
form is not normalized, so {{ classify.intent }} silently resolves to nothing. (The bare
{{ Classify }} form is case-insensitive, which makes the difference easy to miss.) Better
still, have the producing node write the field into flow state, then read it back anywhere with
{{$flow.state.<key>}} — state is unambiguous and survives any distance in the flow.