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
| Type | Description |
|---|---|
messageTimeout | User hasn't responded to a message |
sessionTimeout | Total conversation duration exceeded |
inactivityTimeout | No 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"]);