Skip to content

Events

FlowKit emits events throughout the conversation lifecycle. Use events for logging, analytics, debugging, and custom integrations.

Event Types

EventEmitted When
flow:startConversation begins
flow:endConversation ends (reached .done())
step:enterEntering a new step
step:exitLeaving a step
extract:successData extracted successfully
extract:failExtraction failed
tool:callTool is about to be called
tool:resultTool returned a result
user:messageUser sent a message
agent:messageAgent sent a response
handoff:detectedHandoff to human requested
handoff:transferHandoff transfer executed
timeoutTimeout occurred (message, session, inactivity)
errorAn error occurred

Listening to Events

Pass an onEvent handler when creating the FlowEngine:

typescript
import { FlowEngine, EngineEvent } from "@andresaya/flowkit";

const engine = new FlowEngine(flow, {
  llm: adapter,
  storage,
  onEvent: (event: EngineEvent) => {
    console.log(`[${event.type}]`, event);
  }
});

Event Handler

The onEvent function receives all events. Filter by event.type:

typescript
const handleEvent = (event: EngineEvent) => {
  switch (event.type) {
    case "flow:start":
      console.log(`Session ${event.conversationId} started`);
      break;
    case "flow:end":
      console.log(`Session ${event.conversationId} ended`, event.slots);
      break;
    case "step:enter":
      console.log(`Entering step: ${event.step}`);
      break;
    case "step:exit":
      console.log(`Exiting step: ${event.step} -> ${event.next}`);
      break;
    case "extract:success":
      console.log(`Extracted ${event.extractType} = ${event.value}`);
      break;
    case "extract:fail":
      console.log(`Failed to extract: ${event.reason}`);
      break;
    case "user:message":
      console.log(`User said: ${event.text}`);
      break;
    case "agent:message":
      console.log(`Agent said: ${event.text}`);
      break;
    case "error":
      console.error(`Error:`, event.error);
      break;
  }
};

const engine = new FlowEngine(flow, {
  llm: adapter,
  storage,
  onEvent: handleEvent
});

Event Data

flow:start

typescript
{
  type: "flow:start",
  conversationId: string,
  step: string
}

flow:end

typescript
{
  type: "flow:end",
  conversationId: string,
  slots: Record<string, JsonValue>
}

step:enter

typescript
{
  type: "step:enter",
  conversationId: string,
  step: string
}

step:exit

typescript
{
  type: "step:exit",
  conversationId: string,
  step: string,
  next: string
}

extract:success

typescript
{
  type: "extract:success",
  conversationId: string,
  extractType: string,
  value: JsonValue
}

extract:fail

typescript
{
  type: "extract:fail",
  conversationId: string,
  reason: string
}

user:message

typescript
{
  type: "user:message",
  conversationId: string,
  text: string
}

agent:message

typescript
{
  type: "agent:message",
  conversationId: string,
  text: string
}

tool:call

typescript
{
  type: "tool:call",
  conversationId: string,
  tool: string,
  payload: JsonValue
}

tool:result

typescript
{
  type: "tool:result",
  conversationId: string,
  tool: string,
  result: JsonValue
}

handoff:detected

typescript
{
  type: "handoff:detected",
  conversationId: string,
  trigger: string,
  userMessage: string
}

timeout

typescript
{
  type: "timeout",
  conversationId: string,
  timeoutType: "message" | "session" | "inactivity",
  elapsed: number
}

error

typescript
{
  type: "error",
  conversationId: string,
  error: Error
}

Use Cases

Logging

typescript
import winston from "winston";

const logger = winston.createLogger({ /* config */ });

const engine = new FlowEngine(flow, {
  llm: adapter,
  storage,
  onEvent: (event) => {
    switch (event.type) {
      case "flow:start":
        logger.info("Flow started", { conversationId: event.conversationId });
        break;
      case "flow:end":
        logger.info("Flow ended", { conversationId: event.conversationId, slots: event.slots });
        break;
      case "step:enter":
        logger.debug("Step enter", { conversationId: event.conversationId, step: event.step });
        break;
      case "error":
        logger.error("Error", { conversationId: event.conversationId, error: event.error.message });
        break;
    }
  }
});

Analytics

typescript
import { track } from "./analytics";

const engine = new FlowEngine(flow, {
  llm: adapter,
  storage,
  onEvent: (event) => {
    switch (event.type) {
      case "flow:start":
        track("conversation_started", { sessionId: event.conversationId });
        break;
      case "flow:end":
        track("conversation_completed", { 
          sessionId: event.conversationId,
          dataCollected: Object.keys(event.slots).length
        });
        break;
      case "extract:success":
        track("data_extracted", { sessionId: event.conversationId, field: event.slot });
        break;
    }
  }
});

Real-time Updates

typescript
import { WebSocket } from "ws";

const connections = new Map<string, WebSocket>();

const engine = new FlowEngine(flow, {
  llm: adapter,
  storage,
  onEvent: (event) => {
    const ws = connections.get(event.conversationId);
    if (!ws) return;
    
    switch (event.type) {
      case "step:enter":
        ws.send(JSON.stringify({ type: "step_change", step: event.step }));
        break;
      case "agent:message":
        ws.send(JSON.stringify({ type: "message", text: event.text }));
        break;
      case "flow:end":
        ws.send(JSON.stringify({ type: "completed" }));
        ws.close();
        connections.delete(event.conversationId);
        break;
    }
  }
});

Debugging

typescript
// Enable verbose logging in development
const engine = new FlowEngine(flow, {
  llm: adapter,
  storage,
  onEvent: process.env.NODE_ENV === "development" 
    ? (event) => {
        console.log(`[${new Date().toISOString()}] ${event.type}`, 
          JSON.stringify(event, null, 2));
      }
    : undefined
});

Error Alerting

typescript
import { sendAlert } from "./alerting";

const engine = new FlowEngine(flow, {
  llm: adapter,
  storage,
  onEvent: async (event) => {
    if (event.type === "error") {
      await sendAlert({
        level: "error",
        message: `Flow error in session ${event.conversationId}`,
        error: event.error.message,
        stack: event.error.stack
      });
    }
    
    if (event.type === "extract:fail") {
      await sendAlert({
        level: "warning",
        message: `Extraction failed for ${event.slot}`,
        sessionId: event.conversationId,
        error: event.error
      });
    }
  }
});

Metrics Collection

typescript
import { Counter, Histogram } from "prom-client";

const flowsStarted = new Counter({
  name: "flowkit_flows_started_total",
  help: "Total flows started"
});

const flowsCompleted = new Counter({
  name: "flowkit_flows_completed_total", 
  help: "Total flows completed"
});

const stepTimers = new Map<string, number>();
const stepDuration = new Histogram({
  name: "flowkit_step_duration_seconds",
  help: "Time spent in each step",
  labelNames: ["step_id"]
});

const engine = new FlowEngine(flow, {
  llm: adapter,
  storage,
  onEvent: (event) => {
    switch (event.type) {
      case "flow:start":
        flowsStarted.inc();
        break;
      case "flow:end":
        flowsCompleted.inc();
        break;
      case "step:enter":
        stepTimers.set(`${event.conversationId}:${event.step}`, Date.now());
        break;
      case "step:exit":
        const start = stepTimers.get(`${event.conversationId}:${event.step}`);
        if (start) {
          const duration = (Date.now() - start) / 1000;
          stepDuration.labels(event.step).observe(duration);
          stepTimers.delete(`${event.conversationId}:${event.step}`);
        }
        break;
    }
  }
});

Tips

  1. Don't block - Keep event handlers fast and use async operations
  2. Handle errors - Wrap handlers in try/catch to prevent crashes
  3. Filter wisely - Only process events you need
  4. Type safety - Use the EngineEvent type for proper TypeScript support
  5. Performance - For high-volume apps, batch analytics events case "timeout": console.log(Timeout: ${event.timeoutType}); break;

Released under the MIT License.