Skip to content

Flows

Flows define the conversation structure - what questions to ask, what to say, and how to branch based on user responses.

Basic Structure

typescript
import { flow } from "@andresaya/flowkit";

const myFlow = flow("flow-id", agentConfig)
  .ask(...)   // Ask a question, extract data
  .say(...)   // Say something (no extraction)
  .when(...)  // Branch based on value
  .then(...)  // Go to next step
  .do(...)    // Execute a tool
  .done()     // Mark as final step
  .build();   // Build the flow

Step Types

.ask() - Ask and Extract

Ask a question and extract information from the user's response.

typescript
.ask(stepId, message, extractor, slotName)
ParameterTypeDescription
stepIdstringUnique identifier for this step
messagestringWhat to say (supports interpolation)
extractorExtractTypeWhat to extract from response
slotNamestringWhere to store the extracted value
typescript
.ask("get_name", "What's your name?", name(), "customer_name")
.ask("get_age", "How old are you, {{customer_name}}?", number(), "age")
.ask("get_issue", "What brings you here?", oneOf(["billing", "tech", "other"]), "issue_type")

.say() - Just Say

Display a message without expecting specific data extraction.

typescript
.say(stepId, message)
typescript
.say("welcome", "Welcome to our service, {{customer_name}}!")
.say("transfer", "I'll transfer you to a specialist now.")
.say("bye", "Thanks for chatting! Have a great day! 👋")

.when() - Branch

Route to different steps based on the extracted value.

typescript
.when({ value1: "step1", value2: "step2", ... })
typescript
.ask("choice", "Do you want A or B?", oneOf(["A", "B"]), "choice")
.when({ "A": "handle_a", "B": "handle_b" })

// With yes/no
.ask("confirm", "Is this correct?", yesNo(), "confirmed")
.when({ "yes": "proceed", "no": "retry" })

// Wildcard fallback
.when({ "billing": "billing_dept", "tech": "tech_dept", "*": "general" })

.then() - Linear Next

Go to a specific step (for linear flows without branching).

typescript
.then(nextStepId)
typescript
.ask("name", "What's your name?", name(), "name")
.then("ask_email")  // Always go to ask_email next

.ask("ask_email", "What's your email?", email(), "email")
.then("confirm")

.do() - Execute Tool

Execute a registered tool/action.

typescript
.do(toolName, payload)
typescript
.say("sending", "I'm sending your confirmation email...")
.do("send_email", { template: "confirmation" })
.then("done")

// Multiple actions
.say("processing", "Processing your request...")
.do("log_event", { event: "request_started" })
.do("notify_team", { channel: "support" })
.then("wait")

.done() - End Flow

Mark the current step as a terminal step (conversation ends here).

typescript
.say("goodbye", "Thanks for your time!")
.done()

Flow Patterns

Linear Flow

typescript
flow("survey", agent)
  .ask("q1", "How satisfied are you? (1-10)", number(), "satisfaction")
  .then("q2")
  .ask("q2", "Would you recommend us?", yesNo(), "recommend")
  .then("q3")
  .ask("q3", "Any comments?", text(), "comments")
  .then("thanks")
  .say("thanks", "Thank you for your feedback!")
  .done()
  .build();

Branching Flow

typescript
flow("support", agent)
  .ask("issue", "What's your issue?", oneOf(["billing", "tech", "other"]), "type")
  .when({ "billing": "billing", "tech": "tech", "other": "general" })
  
  .say("billing", "I'll connect you with billing.")
  .do("transfer", { dept: "billing" })
  .done()
  
  .say("tech", "Let me get a technician.")
  .do("transfer", { dept: "tech" })
  .done()
  
  .say("general", "How can I help?")
  .done()
  
  .build();

Nested Branching

typescript
flow("qualification", agent)
  .ask("company_size", "How many employees?", oneOf(["small", "medium", "large"]), "size")
  .when({ "small": "small_flow", "medium": "medium_flow", "large": "large_flow" })
  
  // Small company path
  .ask("small_flow", "Are you looking for basic or pro plan?", oneOf(["basic", "pro"]), "plan")
  .when({ "basic": "basic_signup", "pro": "pro_signup" })
  
  .say("basic_signup", "Great! Sign up at example.com/basic")
  .done()
  
  .say("pro_signup", "Pro is perfect for growing teams!")
  .do("schedule_demo", {})
  .done()
  
  // Medium/Large paths...
  .build();

Loop Back (Retry)

typescript
flow("verification", agent)
  .ask("get_code", "Please enter your 6-digit code:", custom("6-digit number"), "code")
  .then("verify")
  
  .ask("verify", "Verifying... Is {{code}} correct?", yesNo(), "confirmed")
  .when({ "yes": "success", "no": "get_code" })  // Loop back!
  
  .say("success", "Verified! Welcome.")
  .done()
  
  .build();

Slot Interpolation

Use to insert collected values into messages:

typescript
.ask("name", "What's your name?", name(), "user_name")
.then("greet")
.say("greet", "Hello {{user_name}}! How can I help?")
.then("bye")
.say("bye", "Goodbye {{user_name}}! Your session ID is {{session_id}}.")

Tips

  1. Unique step IDs - Each step needs a unique ID within the flow

  2. Always end paths - Every branch should eventually reach a .done()

  3. Use meaningful IDs - ask_email is better than step_3

  4. Test all branches - Make sure every path works

  5. Keep messages concise - Especially in strict mode where exact messages are used

Released under the MIT License.