Conversation Flow Design
Real conversations are not linear. Users change topics, ask clarifying questions, and skip ahead. Good flow design handles these patterns gracefully.
Linear vs Branching Flows
Linear flow: A to B to C. Simple but rigid.
Branching flow: A leads to B or C depending on user input. Flexible but complex.
Most production agents need branching.
Designing Flows
Start by mapping the conversation:
State Machine Approach
Track conversation state explicitly:
1 from enum import Enum 2 3 4 class ConversationState(Enum): 5 GREETING = "greeting" 6 COLLECTING_ORDER_ID = "collecting_order_id" 7 SHOWING_STATUS = "showing_status" 8 HANDLING_CANCELLATION = "handling_cancellation" 9 CONFIRMING = "confirming" 10 DONE = "done" 11 12 13 class OrderAgent(OutputAgentNode): 14 def __init__(self): 15 super().__init__(name="order-agent") 16 self.state = ConversationState.GREETING 17 self.order_id = None 18 self.pending_action = None 19 20 async def generate_response(self): 21 # Get last user message 22 user_msgs = [m for m in self.context.messages if m["role"] == "user"] 23 user_message = user_msgs[-1]["content"] if user_msgs else "" 24 25 if self.state == ConversationState.GREETING: 26 yield "Hello! I can help you check an order or cancel one." 27 yield " What would you like to do?" 28 self.state = ConversationState.COLLECTING_ORDER_ID 29 30 elif self.state == ConversationState.COLLECTING_ORDER_ID: 31 # Extract order ID 32 self.order_id = self._extract_order_id(user_message) 33 34 if self.order_id: 35 yield f"Got it, order {self.order_id}. " 36 yield "Do you want to check the status or cancel it?" 37 self.state = ConversationState.CONFIRMING 38 else: 39 yield "I need your order ID. It starts with ORD-." 40 41 elif self.state == ConversationState.CONFIRMING: 42 if "status" in user_message.lower(): 43 self.state = ConversationState.SHOWING_STATUS 44 async for chunk in self._show_status(): 45 yield chunk 46 elif "cancel" in user_message.lower(): 47 self.state = ConversationState.HANDLING_CANCELLATION 48 async for chunk in self._handle_cancel(): 49 yield chunk
Conditional Branching
Branch based on user input or data:
1 async def generate_response(self): 2 user_intent = self._classify_intent() 3 4 if user_intent == "order_status": 5 async for chunk in self._handle_order_status(): 6 yield chunk 7 8 elif user_intent == "cancel_order": 9 # Check if order is cancellable first 10 order = await self._get_order() 11 12 if order.status == "shipped": 13 yield "This order has already shipped. " 14 yield "Would you like to start a return instead?" 15 self.pending_action = "return" 16 else: 17 yield "I can cancel this order. " 18 yield "This action cannot be undone. Should I proceed?" 19 self.pending_action = "cancel" 20 21 elif user_intent == "confirm": 22 if self.pending_action == "cancel": 23 await self._cancel_order() 24 yield "Done. Your order has been cancelled." 25 elif self.pending_action == "return": 26 async for chunk in self._start_return(): 27 yield chunk
Collecting Information
Gather required data step by step:
1 class IntakeAgent(OutputAgentNode): 2 def __init__(self): 3 super().__init__(name="intake-agent") 4 self.required_fields = ["name", "email", "issue"] 5 self.collected = {} 6 self.current_field = 0 7 8 async def generate_response(self): 9 # Get last user message 10 user_msgs = [m for m in self.context.messages if m["role"] == "user"] 11 user_message = user_msgs[-1]["content"] if user_msgs else "" 12 13 # Store the answer to the previous question 14 if self.current_field > 0: 15 prev_field = self.required_fields[self.current_field - 1] 16 self.collected[prev_field] = user_message 17 18 # Check if we have everything 19 if self.current_field >= len(self.required_fields): 20 yield "Thanks! I have everything I need. " 21 yield f"Name: {self.collected['name']}, " 22 yield f"Email: {self.collected['email']}. " 23 yield "Someone will reach out about your issue shortly." 24 return 25 26 # Ask for the next field 27 field = self.required_fields[self.current_field] 28 self.current_field += 1 29 30 prompts = { 31 "name": "What is your name?", 32 "email": "What is your email address?", 33 "issue": "Please describe your issue briefly." 34 } 35 36 yield prompts[field]
Handling Topic Changes
Users switch topics. Detect and handle it:
1 async def generate_response(self): 2 # Get last user message 3 user_msgs = [m for m in self.context.messages if m["role"] == "user"] 4 user_message = user_msgs[-1]["content"] if user_msgs else "" 5 6 # Detect topic change 7 new_topic = self._detect_topic(user_message) 8 9 if new_topic and new_topic != self.current_topic: 10 # Acknowledge the switch 11 yield f"Switching to {new_topic}. " 12 13 # Clean up previous topic state 14 self._reset_topic_state() 15 16 self.current_topic = new_topic 17 18 # Handle current topic 19 if self.current_topic == "orders": 20 async for chunk in self._handle_orders(): 21 yield chunk 22 elif self.current_topic == "billing": 23 async for chunk in self._handle_billing(): 24 yield chunk
Confirmation Loops
For important actions, confirm before proceeding:
1 async def generate_response(self): 2 # Get last user message 3 user_msgs = [m for m in self.context.messages if m["role"] == "user"] 4 user_message = user_msgs[-1]["content"] if user_msgs else "" 5 6 if self.awaiting_confirmation: 7 if self._is_affirmative(user_message): 8 await self._execute_action() 9 yield "Done." 10 self.awaiting_confirmation = False 11 elif self._is_negative(user_message): 12 yield "Okay, I won't do that. Is there anything else?" 13 self.awaiting_confirmation = False 14 else: 15 yield "Please say yes or no." 16 return 17 18 # Normal flow... 19 if self._wants_to_delete(): 20 yield "This will permanently delete your data. Are you sure?" 21 self.awaiting_confirmation = True 22 self.pending_action = "delete"
Flow Recovery
Help users who get lost:
1 async def generate_response(self): 2 # Get last user message 3 user_msgs = [m for m in self.context.messages if m["role"] == "user"] 4 user_message = user_msgs[-1]["content"] if user_msgs else "" 5 6 # Detect if user is confused 7 confusion_signals = ["what", "huh", "confused", "start over", "help"] 8 9 if any(sig in user_message.lower() for sig in confusion_signals): 10 yield "No problem! Let me help. " 11 yield "You can ask me to: " 12 yield "Check an order, cancel an order, or talk to someone. " 13 yield "What would you like?" 14 15 # Reset to known state 16 self.state = ConversationState.GREETING 17 return 18 19 # Normal flow...
Tips
Use enums for state
Explicit states prevent bugs. You always know exactly where in the flow you are.
Handle confusion gracefully
Detect words like “help”, “confused”, “start over” and offer a reset.
Confirm destructive actions
Always ask “are you sure?” before deleting, cancelling, or changing important data.

