Skip to content

Handoff & Timeouts

FlowKit includes built-in support for human handoff detection and conversation timeouts.

Human Handoff

Detect when users want to speak with a human agent and handle the transition gracefully.

Basic Usage

typescript
const engine = new FlowEngine(flow, {
  llm: adapter,
  storage: new MemoryStorage(),
  // Simply enable handoff detection
  handoff: true,
});

Advanced Configuration

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

const engine = new FlowEngine(flow, {
  llm: adapter,
  storage: new MemoryStorage(),
  handoff: {
    enabled: true,
    // Add custom triggers to defaults
    triggers: extendHandoffTriggers([
      "quiero quejarme",
      "supervisor",
      "esto es ridiculo",
    ]),
    // Step to go when handoff is detected
    targetStep: "human_handoff",
    // Custom handler
    onHandoff: async (ctx) => {
      console.log(`Handoff triggered by: "${ctx.trigger}"`);
      console.log(`User said: "${ctx.userMessage}"`);
      // Create ticket, notify supervisor, etc.
      await createSupportTicket({
        customerId: ctx.slots.customer_id,
        reason: ctx.trigger,
      });
    },
    // Optional: custom message (overrides step message)
    message: "Te transfiero con un agente ahora mismo...",
  },
});

Default Triggers

FlowKit includes triggers in multiple languages:

Spanish:

  • "hablar con humano", "hablar con una persona", "agente humano"
  • "persona real", "operador", "representante", "transferir"

English:

  • "talk to human", "speak to a person", "human agent"
  • "live agent", "real person", "customer service"

Portuguese:

  • "falar com humano", "agente humano", "atendente"

Handoff Events

typescript
onEvent: (ev) => {
  if (ev.type === "handoff:detected") {
    // User requested human
    console.log(`Trigger: ${ev.trigger}`);
    console.log(`Message: ${ev.userMessage}`);
  }
  if (ev.type === "handoff:transfer") {
    // Transfer is happening
    console.log(`Going to step: ${ev.targetStep}`);
  }
}

Handoff Slots

When handoff occurs, these slots are automatically set:

  • _handoff: true
  • _handoffTrigger: "the trigger phrase"

Timeouts

Handle conversation timeouts to improve user experience and resource management.

Timeout Types

TypeDescription
messageTimeoutUser hasn't responded to a message
sessionTimeoutTotal conversation duration exceeded
inactivityTimeoutNo activity (either party) for too long

Basic Usage

typescript
const engine = new FlowEngine(flow, {
  llm: adapter,
  storage: new MemoryStorage(),
  timeout: {
    messageTimeout: 60000,   // 1 minute to respond
    sessionTimeout: 900000,  // 15 minutes max session
  },
});

Duration Values

FlowKit uses milliseconds (like setTimeout):

typescript
timeout: {
  messageTimeout: 30000,       // 30 seconds
  messageTimeout: 120000,      // 2 minutes
  sessionTimeout: 3600000,     // 1 hour
  inactivityTimeout: 1800000,  // 30 minutes
  sessionTimeout: 86400000,    // 1 day
}

Advanced Configuration

typescript
const engine = new FlowEngine(flow, {
  llm: adapter,
  storage: new MemoryStorage(),
  timeout: {
    messageTimeout: 30000,
    sessionTimeout: 600000,
    inactivityTimeout: 120000,
    // Step to go when timeout occurs
    onTimeoutStep: "timeout_step",
    // Custom message (overrides step message)
    timeoutMessage: "Tu sesión ha expirado...",
    // Custom handler
    onTimeout: async (ctx) => {
      console.log(`Timeout type: ${ctx.type}`);
      console.log(`Elapsed: ${ctx.elapsed}ms`);
      // Save conversation state, schedule follow-up, etc.
      await savePartialConversation(ctx.conversationId, ctx.slots);
    },
  },
});

Manual Timeout Handling

For async/polling scenarios:

typescript
// Check if timeout occurred
const timeoutType = engine.checkTimeout(conversationId);
if (timeoutType) {
  console.log(`Timeout: ${timeoutType}`);
}

// Handle timeout and get response
const result = await engine.handleTimeout(conversationId);
if (result) {
  console.log(`Timeout message: ${result.message}`);
}

// Get timeout info
const info = engine.getTimeoutInfo(conversationId);
console.log(`Session duration: ${info.sessionDuration}ms`);
console.log(`Waiting for response: ${info.waitingTime}ms`);

Timeout Events

typescript
onEvent: (ev) => {
  if (ev.type === "timeout") {
    console.log(`Type: ${ev.timeoutType}`);
    console.log(`Elapsed: ${ev.elapsed}ms`);
  }
}

Timeout Slots

When timeout occurs, these slots are automatically set:

  • _timeout: true
  • _timeoutType: "message" | "session" | "inactivity"

Complete Example

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

// Define flow with handoff and timeout steps
const supportFlow = flow("support", assistant)
  .ask("greeting", "Hi! What's your name?", name(), "name")
  .then("ask_help")
  
  .ask("ask_help", "How can I help you, {{name}}?", yesNo(), "needs_help")
  .when({ yes: "provide_help", no: "goodbye" })
  
  .say("provide_help", "I'll help you with that!")
  .done()
  
  .say("goodbye", "Goodbye, {{name}}!")
  .done()
  
  // Handoff step
  .say("human_handoff", "Connecting you with a human agent, {{name}}. Please wait...")
  .do("transfer_to_human", { reason: "user_requested" })
  .done()
  
  // Timeout step
  .say("timeout_step", "{{name}}, you seem busy. Feel free to reach out again later!")
  .done()
  
  .build();

// Create engine with both features
const engine = new FlowEngine(supportFlow, {
  llm: new OllamaAdapter({ model: "llama3.2" }),
  storage: new MemoryStorage(),
  
  handoff: {
    enabled: true,
    targetStep: "human_handoff",
  },
  
  timeout: {
    messageTimeout: 60000,
    sessionTimeout: 600000,
    onTimeoutStep: "timeout_step",
  },
  
  onEvent: (ev) => {
    if (ev.type === "handoff:detected") {
      console.log("🧑‍💼 Human requested!");
    }
    if (ev.type === "timeout") {
      console.log(`⏰ Timeout: ${ev.timeoutType}`);
    }
  },
});

Utility Functions

Handoff Utilities

typescript
import { 
  detectHandoffIntent,
  getDefaultHandoffTriggers,
  extendHandoffTriggers,
} from "@andresaya/flowkit";

// Check if a message requests handoff
const result = detectHandoffIntent("quiero hablar con un humano");
// { detected: true, trigger: "hablar con un humano" }

// Get all default triggers
const defaults = getDefaultHandoffTriggers();
// ["hablar con humano", "talk to human", ...]

// Extend defaults with custom triggers
const extended = extendHandoffTriggers(["mi jefe", "supervisor"]);

Released under the MIT License.