Master the art of building effective, maintainable, and performant Flow Agents with proven patterns and strategies used by top developers on the Hamsa platform.
Flow Design Principles
1. Start Simple, Scale Gradually
Build your MVP flow first: Create a basic happy path with 3-5 nodes, test it thoroughly, then add complexity.
Phase 1 - Happy Path:
Phase 2 - Add Error Handling:
Phase 3 - Add Branching:
2. Design for Failure
Every node should handle:
- Invalid user input
- Missing variables
- Tool failures
- Timeout scenarios
- User confusion
Example: Robust Input Collection
❌ Fragile Design:
Node: "Get Account Number"
Message: "What's your account number?"
Transitions:
- Natural Language: "provided number" → Verify_Account
✅ Robust Design:
Node: "Get Account Number" (attempt 1/3)
Message: "Please provide your 8-digit account number"
Variable Extraction:
- account_number (type: string, validation: length == 8)
Transitions:
- Equation: account_number.length == 8 → Verify_Account
- Equation: attempt_count < 3 → Retry_Account_Number
- Equation: attempt_count >= 3 → Transfer_to_Agent
- Natural Language: "don't know" → Account_Lookup_Alternative
- Always → Clarify_Format
3. Keep Nodes Focused
Each node should have one primary purpose:
❌ Multi-Purpose Node (Avoid):
Node: "Handle Everything"
Message: "Tell me your account number, the issue you're having, when it started, and what you've tried so far"
# Too much at once - overwhelming for user and AI
✅ Single-Purpose Nodes (Preferred):
Node 1: "Collect Account Number"
Message: "Please provide your account number"
Node 2: "Describe Issue"
Message: "What issue are you experiencing?"
Node 3: "When Started"
Message: "When did this issue begin?"
Node 4: "Previous Attempts"
Message: "What have you tried so far?"
4. Provide Clear Escape Hatches
Users should always know how to:
- Transfer to a human
- Return to main menu
- End the call
- Get help
Implementation:
Global Nodes (Always Available):
- "Transfer to Agent" (trigger: "speak to human" OR DTMF: 0)
- "Main Menu" (trigger: "main menu" OR DTMF: 9)
- "End Call" (trigger: "end call" OR DTMF: #)
- "Help" (trigger: "help" OR DTMF: *)
Announcements:
Welcome Message: "...You can press 0 at any time to speak with an agent."
Every Menu: "...or press 9 to return to the main menu."
Node Organization
Naming Conventions
Use descriptive, action-oriented names:
❌ Bad Names:
- Node 1
- Conversation
- Router
- Tool
- Transfer
✅ Good Names:
- Welcome_and_Identify_Caller
- Collect_Account_Number
- Verify_Customer_Identity
- Route_by_Issue_Type
- Lookup_Account_Balance
- Transfer_to_Billing_Specialist
Naming Pattern:
{Action}_{Object}_{Context}
Examples:
- Collect_Phone_Number_for_Callback
- Verify_Account_Status_Before_Support
- Route_to_Department_by_Issue
- Send_Confirmation_Email_After_Booking
Visual Layout
Organize flows left-to-right, top-to-bottom:
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Start │ → │ Greet │ → │ Identify │
└──────────┘ └──────────┘ └──────────┘
│
↓
┌──────────┐
│ Router │
└──────────┘
↙ │ ↘
┌─────────┐ │ ┌─────────┐
│ Billing │ │ │ Tech │
└─────────┘ │ └─────────┘
↓
┌─────────┐
│ Sales │
└─────────┘
Layout Guidelines:
- Start node: Top-left
- Main flow: Left to right
- Branches: Vertical spread from routers
- End nodes: Right side
- Global nodes: Separate area (bottom or side)
- Error handling: Below main flow
Use visual proximity and naming to group related functionality:
- Verify_Account_Identity
- Check_Account_Status
- Update_Account_Info
- Account_Security_Check
- Billing_Issue_Categorization
- Check_Payment_Status
- Process_Payment_Update
- Billing_Dispute_Handler
- Technical_Issue_Triage
- Troubleshooting_Steps
- Escalation_Decision
- Create_Support_Ticket
Variable Management
Naming Conventions
Always use snake_case:
✅ Good:
- customer_name
- account_balance
- issue_category
- callback_requested
- payment_due_date
❌ Bad:
- customerName (camelCase)
- Customer-Name (kebab-case)
- CUSTOMER_NAME (SCREAMING_SNAKE)
- customer name (spaces)
Be descriptive and specific:
❌ Vague:
- number
- id
- status
- data
✅ Specific:
- account_number
- customer_id
- payment_status
- customer_data
Variable Scope Planning
Plan variable lifecycle before building:
Variables Needed:
System Variables (built-in):
- session_id
- current_time
- user_input
- call_duration
Custom Variables (API-provided):
- customer_tier
- business_hours
- service_region
Extracted Variables (conversation):
- customer_name (Node: Welcome)
- account_number (Node: Identify)
- issue_type (Node: Categorize)
- issue_severity (Node: Triage)
- resolution_notes (Node: Resolve)
Avoid Variable Pollution
Don’t extract variables you won’t use:
❌ Over-Extraction:
Node: "Collect Info"
Extract:
- customer_name
- customer_age
- customer_gender
- customer_location
- customer_occupation
- favorite_color
# Only actually using customer_name and location
✅ Minimal Extraction:
Node: "Collect Info"
Extract:
- customer_name
- customer_location
# Only what you actually need
Default Values and Validation
Always provide fallbacks:
✅ Safe Variable Usage:
Message: "Hello {{customer_name || 'valued customer'}}, your balance is ${{account_balance || 0}}."
# "Hello valued customer, your balance is $0."
Validate before using:
Router: "Check Account Status"
Transitions:
- Logic: all
Conditions:
- Variable: account_number
Operator: exists
- Variable: account_number
Operator: not_equals
Value: ""
Target: Proceed_with_Lookup
- Always → Request_Account_Number_Again
Transition Design
Priority Strategy
Use consistent priority ranges:
Priority Ranges:
900-1000: Critical overrides (emergency, DTMF shortcuts)
700-800: High priority (VIP routing, urgent issues)
500-600: Medium priority (standard categorization)
300-400: Low priority (fallback natural language)
100-200: Very low priority (broad catch-alls)
0-50: Always transitions (fallbacks)
Example:
Transitions:
- DTMF: 0 → Emergency_Transfer (priority: 1000)
- Natural: "urgent" → Priority_Path (priority: 700)
- Natural: "billing" → Billing_Path (priority: 500)
- Natural: "general question" → General_Path (priority: 300)
- Always → Default_Path (priority: 0)
Natural Language vs. Equations
Decision Matrix:
Use Natural Language When: ✓ Detecting user intent
✓ Sentiment analysis
✓ Topic categorization
✓ Fuzzy matching
✓ Understanding context
Use Equations When: ✓ Variable comparisons
✓ Numeric thresholds
✓ String matching
✓ Boolean logic
✓ Data validation
Use DTMF When: ✓ IVR menus
✓ Simple selection
✓ Quick shortcuts
✓ Accessibility needed
Example: Optimal Combination:
Node: "VIP Routing"
Transitions:
# DTMF for instant access
- DTMF: 1 → VIP_Sales (priority: 1000)
# Equations for data-based routing
- Equation: customer_tier == "platinum" → VIP_Concierge (priority: 800)
- Equation: lifetime_value > 50000 → VIP_Account_Manager (priority: 700)
# Natural language for intent
- Natural: "urgent issue" → Priority_Support (priority: 500)
- Natural: "general question" → Standard_Support (priority: 300)
# Always as fallback
- Always → Standard_Path (priority: 0)
Avoiding Transition Conflicts
Test for overlap:
❌ Conflicting Transitions:
- Natural: "User wants help" (priority: 100)
- Natural: "User needs assistance" (priority: 100)
✅ Distinct Transitions:
- Natural: "User wants to speak with a human agent" (priority: 200)
- Natural: "User wants to return to the main menu" (priority: 100)
Error Handling Patterns
Pattern 1: Retry with Guidance
Node: "Collect Account Number" (Attempt 1)
Message: "Please provide your account number"
Transitions:
- Equation: account_number_valid == true → Verify_Account
- Equation: retry_count < 3 → Retry_with_Help
- Always → Transfer_to_Agent
Node: "Retry Account Number" (Attempt 2)
Message: "I didn't get a valid account number. It should be 8 digits. You can say them one at a time or all together."
# Provides more guidance
Node: "Final Retry" (Attempt 3)
Message: "Let me transfer you to an agent who can help you look up your account."
# Gives up gracefully
Pattern 2: Progressive Escalation
Stage 1: "Self-Service"
→ Try automated solution
Stage 2: "Guided Self-Service"
→ Provide detailed instructions
Stage 3: "Specialist Transfer"
→ Transfer to trained agent
Stage 4: "Priority Escalation"
→ Route to senior support
Implementation:
Router: "Escalation Decision"
Transitions:
- Equation: attempt_count == 1 → Automated_Solution
- Equation: attempt_count == 2 → Guided_Instructions
- Equation: attempt_count >= 3 → Transfer_to_Specialist
Pattern 3: Fallback Chains
Primary Method:
→ Try main solution
Secondary Method:
→ Try alternative approach
Tertiary Method:
→ Offer workaround
Final Fallback:
→ Human transfer or graceful failure
Example:
Node: "Verify Customer"
Transitions:
- Try account number lookup → Success_Path
- Try phone number lookup → Success_Path
- Try email lookup → Success_Path
- Offer manual verification → Manual_Verification
- Transfer to agent → Agent_Verification
Pattern 4: Context Preservation on Error
Node: "Before Error"
Extract:
- error_context_node: "Payment Processing"
- error_context_data: "{{payment_amount}}"
- error_timestamp: "{{current_time}}"
Transitions:
- Tool fails → Error_Handler
Node: "Error Handler"
Message: "I encountered an issue while processing your {{error_context_data}} payment at {{error_timestamp}}. Let me get you help."
# User knows exactly what went wrong
Minimize Natural Language Transitions
Impact:
- Each natural language transition: 200-800ms latency
- Cost: ~100-300 tokens per evaluation
- Multiple NL transitions multiply these costs
Strategy:
❌ Slow (5 NL transitions = 1-4 second delay):
Transitions:
- Natural: "billing question" → Billing
- Natural: "technical issue" → Tech
- Natural: "account inquiry" → Account
- Natural: "sales question" → Sales
- Natural: "general question" → General
✅ Fast (1 NL transition + equations):
Transitions:
- Natural: "categorize user intent" → Intent_Router
# Single NL evaluation to categorize
Intent_Router:
- Equation: intent_category == "billing" → Billing
- Equation: intent_category == "technical" → Tech
- Equation: intent_category == "account" → Account
- Equation: intent_category == "sales" → Sales
- Always → General
# Fast equation-based routing
Optimize Global Nodes
Limit to 3-5 essential global nodes:
✅ Optimized (3 global nodes):
- Transfer to Agent (critical)
- Main Menu Return (navigation)
- End Call (exit)
Total: 3 × 300ms = 900ms max overhead per turn
❌ Slow (10 global nodes):
- Transfer to Sales Agent
- Transfer to Support Agent
- Transfer to Billing Agent
- Transfer to Tech Support
- Main Menu
- Help
- End Call
- Cancel Process
- Repeat Last
- Account Info
Total: 10 × 300ms = 3000ms (3 seconds!) overhead per turn
Call multiple tools in parallel when possible:
❌ Sequential (slow):
Tool Node 1: "Get Account Info" → 2 seconds
→ Go to next node
Tool Node 2: "Get Payment History" → 2 seconds
→ Go to next node
Total: 4 seconds
✅ Parallel (fast):
Tool Node: "Get All Data"
Tool 1: Get Account Info
Tool 2: Get Payment History
Execute in parallel → 2 seconds
Total: 2 seconds
Cache Frequently Accessed Data
Pattern: Cache in Custom Variables
# Set at start of call
Start Node Tool: "Load Customer Profile"
Output: customer_tier, account_status, payment_method
Store as custom variables
# Reuse throughout flow
Router: "Check Customer Tier"
- Equation: customer_tier == "platinum" → ...
# No additional API call needed
Use DTMF for Hot Paths
Node: "Main Menu"
Message: "Press 1 for Billing, 2 for Support, or describe your issue"
Transitions:
- DTMF: 1 → Billing (instant)
- DTMF: 2 → Support (instant)
- Natural: categorize → Router (200-800ms)
# New users can still speak naturally
Conversation Design
Write Natural Prompts
Sound like a human, not a robot:
❌ Robotic:
"Please state your account identification number consisting of eight numerical digits."
✅ Natural:
"What's your account number? It should be 8 digits."
Keep Messages Concise
Users’ attention spans are short:
❌ Too Long:
"Thank you for calling our customer support line. We really appreciate your business and want to help you today. Before we begin, I need to collect some information from you to ensure I can provide you with the best possible service. First, I'll need your account number, which you can find on your most recent statement or invoice, or in your account portal online. It should be exactly 8 digits long. Please provide that now."
✅ Concise:
"Thanks for calling! To help you, I'll need your 8-digit account number."
Provide Clear Instructions
Tell users exactly what to do:
❌ Vague:
"Let me know about your issue."
✅ Specific:
"Briefly describe the problem you're experiencing in one or two sentences."
Pattern: Confirm-Correct-Proceed
1. Collect data
2. Repeat back to user
3. Ask for confirmation
4. Allow correction
5. Proceed when confirmed
Example:
Node: "Collect Phone Number"
Extract: callback_number
Node: "Confirm Phone Number"
Message: "I have {{callback_number}} as your callback number. Is that correct?"
Transitions:
- Natural: "yes" → Schedule_Callback
- Natural: "no" → Collect_Phone_Number_Again
- Always → Repeat_Confirmation
Handle Ambiguity Gracefully
# (Vague - billing? technical? access?)
Node: "Clarify Account Issue"
Message: "I can help with your account. Is this about:
- Billing or payments
- Technical access issues
- Updating account information
- Something else?"
Transitions:
- Natural: "billing" → Billing_Path
- Natural: "technical" → Tech_Path
- Natural: "update info" → Account_Update_Path
- Natural: "other" → General_Inquiry
Testing Strategies
Unit Testing: Individual Nodes
Test each node in isolation:
Node: "Collect Account Number"
Test Cases:
✓ Valid input: "12345678"
✓ Valid input spoken: "one two three four five six seven eight"
✗ Too short: "123456"
✗ Too long: "123456789"
✗ Invalid characters: "abcd1234"
✗ Empty input: ""
✓ User doesn't know: "I don't have it"
✓ User needs help: "where do I find that?"
Integration Testing: Flow Paths
Test complete user journeys:
Test Journey 1: Happy Path - Simple Inquiry
Start → Greet → Identify → Categorize → Resolve → End
Expected: < 2 minute duration, no errors
Test Journey 2: Complex Path - Multiple Attempts
Start → Greet → Identify (fail) → Retry (fail) → Transfer
Expected: Graceful degradation to human agent
Test Journey 3: Global Node Interruption
Start → Greet → [User requests agent] → Transfer
Expected: Immediate transfer, context preserved
Edge Case Testing
Test Scenarios: ✓ User speaks over agent (interrupt handling)
✓ Long silence periods (timeout behavior)
✓ Unexpected input at every node
✓ Rapid repeated inputs
✓ Very long user responses (> 1 minute)
✓ Background noise (phone quality)
✓ Multiple language switching
✓ DTMF during voice response
✓ Network interruption/reconnection
Regression Testing
After any flow changes, re-test:
Regression Test Suite: 1. All critical paths (must work)
2. All transition conditions (must evaluate correctly)
3. All variable extractions (must capture accurately)
4. All tool integrations (must execute successfully)
5. All global nodes (must trigger appropriately)
6. All error handlers (must recover gracefully)
Common Pitfalls to Avoid
1. Over-Complicated First Version
❌ Don’t build the perfect flow upfront
- Trying to handle every edge case from day one
- Creating 50+ nodes before testing basic functionality
- Over-engineering for scenarios that may never happen
✅ Start simple, iterate based on real usage
2. No Fallback Transitions
❌ Leaving nodes without Always transitions
# User conversation stalls!
✅ Always include fallback paths
3. Variable Name Inconsistency
❌ Using different formats:
- customerName
- customer_phone
- CUSTOMER-EMAIL
- Customer Address
✅ Consistent snake_case everywhere
4. Unclear Global Conditions
❌ Vague global triggers:
globalCondition: 'User needs help'
✅ Specific global triggers:
globalCondition: 'User explicitly requests to speak with a human agent'
❌ Multiple natural language transitions per node
❌ 10+ global nodes
❌ Sequential tool calls that could be parallel
✅ Optimize for speed and cost
6. Poor Error Messages
❌ Generic errors:
'An error occurred. Please try again.'
✅ Specific, actionable errors:
"I couldn't verify that account number. Please check that it's 8 digits and try again, or press 0 to speak with an agent who can help."
7. No User Testing
❌ Only developer testing
- You know how the flow works
- You know what inputs to provide
- You know the “correct” path
✅ Test with real users
- They’ll find issues you never considered
- They’ll use unexpected phrasing
- They’ll expose unclear instructions
8. Forgetting Mobile Context
❌ Assuming perfect audio
- Users may be in cars, on street, in noisy environments
- Phone connection quality varies
- Background noise interferes
✅ Design for real-world conditions
- Repeat critical information
- Offer DTMF alternatives
- Confirm important data verbally
Production Readiness Checklist
Before deploying your flow to production:
Functionality
User Experience
Validation
Documentation
Monitoring
Maintenance Best Practices
Version Control
Approach: 1. Use snapshot history feature
2. Name snapshots descriptively
3. Test changes in development snapshots
4. Rollback if issues found
Snapshot Naming: ✓ "v1.2 - Added billing FAQ path"
✓ "v1.3 - Fixed account verification bug"
✗ "snapshot 1"
✗ "test"
Regular Audits
Monthly Flow Review:
- Check for unused nodes (remove)
- Review most/least used paths (optimize)
- Identify high error rates (improve)
- Update outdated information
- Verify all tools still work
Gradual Changes
Don’t make sweeping changes in production:
❌ Risky: Rebuild entire flow at once
✅ Safe: Change one branch at a time
Process: 1. Identify improvement area
2. Test change in snapshot
3. Deploy to production
4. Monitor for issues
5. Move to next improvement
A/B Testing
Pattern: Duplicate flow for testing
Flow A: Current version (80% of traffic)
Flow B: Experimental version (20% of traffic)
Monitor:
- Completion rates
- Average call duration
- User satisfaction scores
- Transfer rates
If Flow B performs better:
→ Gradually shift traffic (50/50, then 20/80, then 100%)
Examples: Before & After
Example 1: Inefficient to Optimized
❌ Before:
Node: "Billing Help"
Message: "I can help with billing. Tell me everything about your billing issue including your account number, what the charge is for, when it occurred, whether you've disputed it before, and what resolution you're looking for."
Transitions:
- Natural: "wants refund" → Refund_Flow
- Natural: "has question" → Billing_FAQ
- Natural: "wants to pay" → Payment_Flow
- Natural: "dispute charge" → Dispute_Flow
- Natural: "update billing info" → Update_Info
- Natural: "something else" → General_Billing
# - Overwhelming message (too many questions at once)
# - No fallback transition
✅ After:
Node: "Billing Help"
Message: "I can help with billing. Are you calling about a charge, payment, or billing information update?"
Transitions:
- Natural: "charge or dispute" → Charge_Router (priority: 200)
- Natural: "payment" → Payment_Flow (priority: 200)
- Natural: "update info" → Update_Info (priority: 200)
- Always → Billing_Clarification (priority: 0)
Charge_Router:
Transitions:
- Natural: "wants refund" → Refund_Flow
- Natural: "wants to dispute" → Dispute_Flow
- Always → General_Charge_Help
# - Focused question (one thing at a time)
# - Fallback transition (safer)
Example 2: Fragile to Robust
❌ Before:
Tool Node: "Check Account Balance"
Tool: "GetBalanceAPI"
Parameters:
- account_id: "{{account_number}}"
Transitions:
- Always → Show_Balance
# - Doesn't check if account_number exists
# - No validation of account_number format
✅ After:
Router: "Validate Account Number"
Transitions:
- Logic: all
Conditions:
- account_number exists
- account_number.length == 8
Target: Check_Account_Balance
- Always → Request_Account_Number
Tool Node: "Check Account Balance"
Tool: "GetBalanceAPI"
Parameters:
- account_id: "{{account_number}}"
Timeout: 5000ms
Retry Attempts: 2
On Error: continue
Transitions:
- Equation: tool_success == true → Show_Balance
- Equation: error_code == "not_found" → Account_Not_Found
- Equation: error_code == "timeout" → API_Timeout
- Always → Generic_Error_Handler
# - Validates account_number exists and is correct format
# - Different paths for different error types
Next Steps