Skip to content

Flows API

The flow() function creates a flow builder for defining conversation structure.

Basic Usage

typescript
import { agent, flow, name, yesNo } from "@andresaya/flowkit";

const myAgent = agent("Bot").build();

const myFlow = flow("greeting", myAgent)
  .ask("name", "What's your name?", name(), "user_name")
  .then("confirm")
  .ask("confirm", "Is {{user_name}} correct?", yesNo(), "confirmed")
  .when({ yes: "done", no: "name" })
  .say("done", "Thanks {{user_name}}!")
  .done()
  .build();

API Reference

flow(id: string, agent: AgentConfig)

Creates a new flow builder.

Parameters:

  • id - Unique identifier for the flow
  • agent - The agent configuration to use

Returns: FlowBuilder


FlowBuilder Methods

.ask(id, message, extractor, slot)

Adds a step that asks a question and extracts data.

typescript
.ask("get_name", "What's your name?", name(), "user_name")

Parameters:

  • id - Step identifier
  • message - Question to ask (supports interpolation)
  • extractor - Extractor function (name(), email(), etc.)
  • slot - Slot name to store the extracted value

.say(id, message)

Adds a step that displays a message without extraction.

typescript
.say("welcome", "Welcome to our service!")

.then(stepId)

Sets the next step in sequence.

typescript
.ask("name", "What's your name?", name(), "user_name")
.then("email")  // Go to "email" step next

.when(branches)

Creates conditional branching based on the extracted value.

typescript
.ask("choice", "A or B?", oneOf(["A", "B"]), "choice")
.when({
  A: "step_a",
  B: "step_b"
})

.done()

Marks the current step as an ending point.

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

.do(toolName, payload?)

Executes a registered tool.

typescript
.say("saving", "Saving your data...")
.do("save_to_db", { name: "{{user_name}}", email: "{{user_email}}" })

.start(stepId)

Sets the starting step (defaults to first step).

typescript
.start("welcome")

.skipIf(condition)

Skips a step based on a condition.

typescript
.ask("email", "What's your email?", email(), "user_email")
.skipIf({ slot: "user_email", exists: true })

.onlyIf(condition)

Only executes a step if condition is met.

typescript
.say("premium_offer", "Check out our premium plan!")
.onlyIf({ slot: "is_premium", equals: false })

.build()

Builds and returns the FlowConfig.


Slot Interpolation

Use to insert slot values into messages:

typescript
.ask("name", "What's your name?", name(), "user_name")
.then("greet")
.say("greet", "Hello {{user_name}}! How can I help?")

Flow Patterns

Linear Flow

typescript
flow("linear", agent)
  .ask("step1", "...", extractor, "slot1")
  .then("step2")
  .ask("step2", "...", extractor, "slot2")
  .then("step3")
  .say("step3", "Done!")
  .done()
  .build();

Branching Flow

typescript
flow("branching", agent)
  .ask("choice", "Option A or B?", oneOf(["A", "B"]), "choice")
  .when({ A: "path_a", B: "path_b" })
  
  .say("path_a", "You chose A")
  .done()
  
  .say("path_b", "You chose B")
  .done()
  
  .build();

Loop Flow

typescript
flow("loop", agent)
  .ask("item", "Enter item (or 'done'):", text(), "item")
  .then("check")
  
  .ask("check", "Add more?", yesNo(), "more")
  .when({ yes: "item", no: "finish" })
  
  .say("finish", "All items collected!")
  .done()
  
  .build();

Conditional Steps

typescript
flow("conditional", agent)
  .ask("age", "How old are you?", number(), "age")
  .then("offer")
  
  .say("offer", "Special youth discount!")
  .onlyIf({ slot: "age", lessThan: 25 })
  .then("checkout")
  
  .say("checkout", "Proceed to checkout")
  .done()
  
  .build();

FlowConfig Type

typescript
interface FlowConfig {
  id: string;
  agent: AgentConfig;
  steps: Record<string, StepConfig>;
  startStep?: string;
}

interface StepConfig {
  id: string;
  message: string;
  extract?: ExtractType;
  slot?: string;
  next?: string;
  branches?: Record<string, string>;
  end?: boolean;
  action?: ActionConfig;
  skipIf?: StepCondition;
  onlyIf?: StepCondition;
}

Best Practices

Step IDs

Use descriptive step IDs like ask_name, confirm_order, show_receipt.

Message Length

Keep messages concise. Long messages can confuse users and LLMs.

Always End

Ensure all flow paths eventually reach a .done() step.

Avoid Dead Ends

Make sure every step either has .then(), .when(), or .done().

Released under the MIT License.