Skip to main content

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.
tip

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.
warning

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, phone so 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.