Overview

The Sessions API provides a sophisticated abstraction layer over the traditional messaging infrastructure, enabling persistent, stateful conversations with automatic timeout management and renewal capabilities.

Why Use Sessions?

The Sessions API eliminates the complexity of channel management. Traditional messaging approaches require you to:
  1. Create or find a server
  2. Create or find a channel within that server
  3. Add agents to the channel
  4. Manage channel participants
  5. Handle channel lifecycle (creation, deletion, cleanup)
With Sessions API, you simply:
  1. Create a session with an agent
  2. Send messages
  3. (Optional) Configure timeout and renewal policies

Key Features

  • Zero Channel Management: No need to create servers, channels, or manage participants
  • Instant Setup: Start conversations immediately with just agent and user IDs
  • Automatic Timeout Management: Sessions automatically expire after periods of inactivity
  • Session Renewal: Support for both automatic and manual session renewal
  • Expiration Warnings: Get notified when sessions are about to expire
  • Configurable Policies: Customize timeout, renewal, and duration limits per session or agent
  • Resource Optimization: Automatic cleanup of expired sessions to prevent memory leaks
  • Persistent Conversations: Maintain chat history and context across multiple messages
  • State Management: Track conversation stage, renewal count, and expiration status
  • Multi-Platform Support: Works across different platforms with metadata support

Sessions Architecture

Core Design Principles

Abstraction Over Complexity

The Sessions API abstracts away channel and server management complexity:
// Traditional approach (complex)
const server = await createServer({ name: 'My Server' });
const channel = await createChannel({ serverId: server.id });
await addAgentToServer(server.id, agentId);
await addAgentToChannel(channel.id, agentId);
await sendMessage(channel.id, { content: 'Hello' });

// Sessions approach (simple)
const { sessionId } = await createSession({ agentId, userId });
await sendSessionMessage(sessionId, { content: 'Hello' });

State Management

Sessions maintain state across multiple dimensions:
interface Session {
  // Identity
  id: string;
  agentId: UUID;
  userId: UUID;
  channelId: UUID;
  
  // Temporal State
  createdAt: Date;
  lastActivity: Date;
  expiresAt: Date;
  
  // Configuration
  timeoutConfig: SessionTimeoutConfig;
  
  // Lifecycle State
  renewalCount: number;
  warningState?: {
    sent: boolean;
    sentAt: Date;
  };
  
  // Application State
  metadata: Record<string, any>;
}

Session Lifecycle

Lifecycle Phases

  1. Creation: Initialize session with configuration
  2. Active: Process messages and maintain state
  3. Near Expiration: Warning state before timeout
  4. Renewed: Lifetime extended (auto or manual)
  5. Expired: Session exceeded timeout
  6. Deleted: Explicit termination
  7. Cleanup: Resource cleanup

Timeout Configuration

Configuration Interface

interface SessionTimeoutConfig {
  timeoutMinutes?: number;           // Inactivity timeout (5-1440)
  autoRenew?: boolean;               // Auto-renew on activity
  maxDurationMinutes?: number;       // Maximum total duration
  warningThresholdMinutes?: number;  // Warning threshold
}

Configuration Hierarchy

Configuration follows a three-tier precedence:
// Priority Order (highest to lowest)
// 1. Session-specific config
// 2. Agent-specific config
// 3. Global defaults

const finalConfig = {
  ...globalDefaults,
  ...agentConfig,
  ...sessionConfig
};

Environment Variables

Global default configuration:
  • SESSION_DEFAULT_TIMEOUT_MINUTES (default: 30)
  • SESSION_MIN_TIMEOUT_MINUTES (default: 5)
  • SESSION_MAX_TIMEOUT_MINUTES (default: 1440)
  • SESSION_MAX_DURATION_MINUTES (default: 720)
  • SESSION_WARNING_THRESHOLD_MINUTES (default: 5)

Quick Start

Complete Example: Chat Application

// Initialize a chat with timeout management
async function startChat(agentId, userId) {
  const response = await fetch('/api/messaging/sessions', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ 
      agentId, 
      userId,
      timeoutConfig: {
        timeoutMinutes: 30,        // 30 minutes of inactivity
        autoRenew: true,           // Auto-renew on each message
        maxDurationMinutes: 180,   // 3 hour maximum session
        warningThresholdMinutes: 5 // Warn 5 minutes before expiry
      }
    })
  });
  
  const { sessionId, expiresAt, timeoutConfig } = await response.json();
  
  console.log(`Session expires at: ${expiresAt}`);
  console.log(`Auto-renewal: ${timeoutConfig.autoRenew ? 'enabled' : 'disabled'}`);
  
  return sessionId;
}

// Send messages with session status tracking
async function sendMessage(sessionId, message) {
  const response = await fetch(
    `/api/messaging/sessions/${sessionId}/messages`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ content: message })
    }
  );
  
  const data = await response.json();
  
  // Check session status
  if (data.sessionStatus) {
    console.log(`Session renewed: ${data.sessionStatus.wasRenewed}`);
    console.log(`Expires at: ${data.sessionStatus.expiresAt}`);
    
    if (data.sessionStatus.isNearExpiration) {
      console.warn('Session is about to expire!');
    }
  }
  
  return data;
}

// Keep session alive with heartbeat
async function keepAlive(sessionId) {
  const response = await fetch(
    `/api/messaging/sessions/${sessionId}/heartbeat`,
    { method: 'POST' }
  );
  
  const { expiresAt, timeRemaining } = await response.json();
  console.log(`Session renewed, ${Math.floor(timeRemaining / 60000)} minutes remaining`);
  
  return response.json();
}

Session Creation

Creation Process

async function createSession(request: CreateSessionRequest) {
  // Phase 1: Validation
  validateUUIDs(request.agentId, request.userId);
  validateMetadata(request.metadata);
  
  // Phase 2: Agent verification
  const agent = agents.get(request.agentId);
  if (!agent) throw new AgentNotFoundError();
  
  // Phase 3: Configuration resolution
  const agentConfig = getAgentTimeoutConfig(agent);
  const finalConfig = mergeTimeoutConfigs(
    request.timeoutConfig,
    agentConfig
  );
  
  // Phase 4: Infrastructure setup
  const sessionId = uuidv4();
  const channelId = uuidv4();
  
  // Atomic channel creation
  await serverInstance.createChannel({
    id: channelId,
    name: `session-${sessionId}`,
    type: ChannelType.DM,
    metadata: {
      sessionId,
      agentId: request.agentId,
      userId: request.userId,
      timeoutConfig: finalConfig,
      ...request.metadata
    }
  });
  
  // Phase 5: Session registration
  const session = new Session(sessionId, channelId, finalConfig);
  sessions.set(sessionId, session);
  
  return session;
}

Session Operations

Send Messages

const messageResponse = await fetch(
  `http://localhost:3000/api/messaging/sessions/${sessionId}/messages`,
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      content: 'Hello, I need help with my account',
      metadata: {
        userTimezone: 'America/New_York'
      }
    })
  }
);

const response = await messageResponse.json();
console.log(response.content); // Agent's response
console.log(response.metadata.thought); // Agent's internal reasoning

Retrieve Message History

// Initial fetch
const messagesResponse = await fetch(
  `http://localhost:3000/api/messaging/sessions/${sessionId}/messages?limit=20`,
  {
    method: 'GET',
  }
);

const { messages, hasMore, cursors } = await messagesResponse.json();

// Pagination - get older messages
if (hasMore && cursors?.before) {
  const olderMessages = await fetch(
    `/api/messaging/sessions/${sessionId}/messages?before=${cursors.before}&limit=20`
  );
}

// Get newer messages
if (cursors?.after) {
  const newerMessages = await fetch(
    `/api/messaging/sessions/${sessionId}/messages?after=${cursors.after}&limit=20`
  );
}

Manual Session Renewal

// Useful when auto-renew is disabled or to extend before expiration
const renewResponse = await fetch(
  `http://localhost:3000/api/messaging/sessions/${sessionId}/renew`,
  {
    method: 'POST',
  }
);

const { 
  expiresAt, 
  timeRemaining, 
  renewalCount 
} = await renewResponse.json();

console.log(`Session renewed ${renewalCount} times`);
console.log(`New expiration: ${expiresAt}`);
console.log(`Time remaining: ${Math.floor(timeRemaining / 60000)} minutes`);

Update Timeout Configuration

// Dynamically update timeout settings for an active session
const updateResponse = await fetch(
  `http://localhost:3000/api/messaging/sessions/${sessionId}/timeout`,
  {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      timeoutMinutes: 120,        // Extend to 2 hours
      autoRenew: false,           // Disable auto-renewal
      maxDurationMinutes: 480     // Increase max to 8 hours
    })
  }
);

const updatedSession = await updateResponse.json();

Active Session Management

Message Handling

async function handleMessage(sessionId: string, message: SendMessageRequest) {
  const session = sessions.get(sessionId);
  
  // Expiration check
  if (session.isExpired()) {
    sessions.delete(sessionId);
    throw new SessionExpiredError();
  }
  
  // Activity tracking
  session.updateLastActivity();
  
  // Renewal logic
  if (session.timeoutConfig.autoRenew) {
    const renewed = session.attemptRenewal();
    if (renewed) {
      logger.info(`Session ${sessionId} auto-renewed`);
    }
  }
  
  // Warning detection
  if (session.isNearExpiration()) {
    session.markWarningState();
  }
  
  // Message creation
  const dbMessage = await serverInstance.createMessage({
    channelId: session.channelId,
    authorId: session.userId,
    content: message.content,
    metadata: {
      sessionId,
      ...message.metadata
    }
  });
  
  // Response enrichment
  return {
    ...dbMessage,
    sessionStatus: session.getStatus()
  };
}

Renewal Mechanism

Renewal Engine

class SessionRenewalEngine {
  attemptRenewal(session: Session): boolean {
    // Check if renewal is allowed
    if (!session.timeoutConfig.autoRenew) {
      return false;
    }
    
    // Check maximum duration constraint
    const totalDuration = Date.now() - session.createdAt.getTime();
    const maxDurationMs = session.timeoutConfig.maxDurationMinutes * 60 * 1000;
    
    if (totalDuration >= maxDurationMs) {
      logger.warn(`Session ${session.id} reached max duration`);
      return false;
    }
    
    // Calculate new expiration
    const timeoutMs = session.timeoutConfig.timeoutMinutes * 60 * 1000;
    const remainingMaxDuration = maxDurationMs - totalDuration;
    const effectiveTimeout = Math.min(timeoutMs, remainingMaxDuration);
    
    // Update session
    session.lastActivity = new Date();
    session.expiresAt = new Date(Date.now() + effectiveTimeout);
    session.renewalCount++;
    session.warningState = undefined; // Reset warning
    
    return true;
  }
}

Heartbeat Strategy

class SessionManager {
  constructor(sessionId) {
    this.sessionId = sessionId;
    this.heartbeatInterval = null;
    this.warningShown = false;
  }
  
  startHeartbeat(intervalMs = 5 * 60 * 1000) {
    this.heartbeatInterval = setInterval(async () => {
      try {
        const response = await this.sendHeartbeat();
        
        if (response.isNearExpiration && !this.warningShown) {
          this.onExpirationWarning(response.timeRemaining);
          this.warningShown = true;
        }
        
        if (response.timeRemaining > response.timeoutConfig.warningThresholdMinutes * 60000) {
          this.warningShown = false; // Reset warning flag
        }
      } catch (error) {
        this.stopHeartbeat();
        this.onSessionLost(error);
      }
    }, intervalMs);
  }
  
  stopHeartbeat() {
    if (this.heartbeatInterval) {
      clearInterval(this.heartbeatInterval);
      this.heartbeatInterval = null;
    }
  }
  
  async sendHeartbeat() {
    const response = await fetch(
      `/api/messaging/sessions/${this.sessionId}/heartbeat`,
      { method: 'POST' }
    );
    
    if (!response.ok) {
      throw new Error(`Heartbeat failed: ${response.status}`);
    }
    
    return response.json();
  }
}

Session Store

In-Memory Storage

class SessionStore {
  private sessions = new Map<string, Session>();
  private metrics = {
    totalCreated: 0,
    totalExpired: 0,
    totalDeleted: 0,
    peakConcurrent: 0
  };
  
  set(sessionId: string, session: Session) {
    this.sessions.set(sessionId, session);
    this.metrics.totalCreated++;
    this.updatePeakConcurrent();
  }
  
  get(sessionId: string): Session | undefined {
    const session = this.sessions.get(sessionId);
    
    // Lazy expiration check
    if (session && session.isExpired()) {
      this.delete(sessionId);
      this.metrics.totalExpired++;
      return undefined;
    }
    
    return session;
  }
  
  delete(sessionId: string): boolean {
    const deleted = this.sessions.delete(sessionId);
    if (deleted) {
      this.metrics.totalDeleted++;
    }
    return deleted;
  }
  
  private updatePeakConcurrent() {
    const current = this.sessions.size;
    if (current > this.metrics.peakConcurrent) {
      this.metrics.peakConcurrent = current;
    }
  }
}

Cleanup Service

Automatic Cleanup

class SessionCleanupService {
  private cleanupInterval: NodeJS.Timeout;
  
  start(intervalMs: number = 5 * 60 * 1000) {
    this.cleanupInterval = setInterval(() => {
      this.performCleanup();
    }, intervalMs);
  }
  
  performCleanup() {
    const now = Date.now();
    const stats = {
      cleaned: 0,
      expired: 0,
      warned: 0,
      invalid: 0
    };
    
    for (const [sessionId, session] of sessions.entries()) {
      // Validate session structure
      if (!this.isValidSession(session)) {
        sessions.delete(sessionId);
        stats.invalid++;
        continue;
      }
      
      // Remove expired sessions
      if (session.expiresAt.getTime() <= now) {
        sessions.delete(sessionId);
        stats.expired++;
        stats.cleaned++;
        
        // Optional: Clean up associated resources
        this.cleanupChannelResources(session.channelId);
      }
      // Issue expiration warnings
      else if (this.shouldWarn(session)) {
        session.markWarningState();
        stats.warned++;
        
        // Optional: Emit warning event
        this.emitExpirationWarning(session);
      }
    }
    
    if (stats.cleaned > 0 || stats.warned > 0) {
      logger.info('Cleanup cycle completed:', stats);
    }
  }
  
  stop() {
    if (this.cleanupInterval) {
      clearInterval(this.cleanupInterval);
    }
  }
}

Integration Examples

React Hook with Session Management

import { useState, useCallback, useEffect, useRef } from 'react';

function useElizaSession(agentId, userId) {
  const [sessionId, setSessionId] = useState(null);
  const [messages, setMessages] = useState([]);
  const [loading, setLoading] = useState(false);
  const [sessionStatus, setSessionStatus] = useState(null);
  const [expirationWarning, setExpirationWarning] = useState(false);
  const heartbeatInterval = useRef(null);

  const startSession = useCallback(async () => {
    const response = await fetch('/api/messaging/sessions', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ 
        agentId, 
        userId,
        timeoutConfig: {
          timeoutMinutes: 30,
          autoRenew: true,
          maxDurationMinutes: 120,
          warningThresholdMinutes: 5
        }
      })
    });
    
    const data = await response.json();
    setSessionId(data.sessionId);
    setSessionStatus({
      expiresAt: data.expiresAt,
      timeoutConfig: data.timeoutConfig
    });
    
    // Start heartbeat
    startHeartbeat(data.sessionId);
    
    return data.sessionId;
  }, [agentId, userId]);

  const startHeartbeat = useCallback((sid) => {
    if (heartbeatInterval.current) {
      clearInterval(heartbeatInterval.current);
    }
    
    heartbeatInterval.current = setInterval(async () => {
      try {
        const response = await fetch(
          `/api/messaging/sessions/${sid}/heartbeat`,
          { method: 'POST' }
        );
        
        const status = await response.json();
        setSessionStatus(status);
        setExpirationWarning(status.isNearExpiration);
        
      } catch (error) {
        console.error('Heartbeat failed:', error);
      }
    }, 60000); // Every minute
  }, []);

  const sendMessage = useCallback(async (content) => {
    if (!sessionId) {
      const newSessionId = await startSession();
      setSessionId(newSessionId);
    }
    
    setLoading(true);
    try {
      const response = await fetch(
        `/api/messaging/sessions/${sessionId}/messages`,
        {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ content })
        }
      );
      
      if (!response.ok) {
        if (response.status === 404 || response.status === 410) {
          // Session expired, create new one
          const newSessionId = await startSession();
          setSessionId(newSessionId);
          // Retry with new session
          return sendMessage(content);
        }
        throw new Error(`Failed to send message: ${response.status}`);
      }
      
      const message = await response.json();
      setMessages(prev => [...prev, message]);
      
      // Update session status if provided
      if (message.sessionStatus) {
        setSessionStatus(message.sessionStatus);
        setExpirationWarning(message.sessionStatus.isNearExpiration);
      }
      
      return message;
    } finally {
      setLoading(false);
    }
  }, [sessionId, startSession]);
  
  const renewSession = useCallback(async () => {
    if (!sessionId) return;
    
    const response = await fetch(
      `/api/messaging/sessions/${sessionId}/renew`,
      { method: 'POST' }
    );
    
    const status = await response.json();
    setSessionStatus(status);
    setExpirationWarning(false);
    
    return status;
  }, [sessionId]);

  useEffect(() => {
    // Cleanup heartbeat on unmount
    return () => {
      if (heartbeatInterval.current) {
        clearInterval(heartbeatInterval.current);
      }
    };
  }, []);

  return { 
    sessionId, 
    messages, 
    sendMessage, 
    loading,
    sessionStatus,
    expirationWarning,
    renewSession
  };
}

WebSocket Integration

import { io } from 'socket.io-client';

class SessionWebSocketClient {
  constructor(serverUrl) {
    this.socket = io(serverUrl);
    this.sessionId = null;
    this.setupEventHandlers();
  }
  
  setupEventHandlers() {
    // Session expiration warning via WebSocket
    this.socket.on('sessionExpirationWarning', (data) => {
      if (data.sessionId === this.sessionId) {
        console.warn(`Session expires in ${data.minutesRemaining} minutes`);
        this.onExpirationWarning?.(data);
      }
    });
    
    // Session expired notification
    this.socket.on('sessionExpired', (data) => {
      if (data.sessionId === this.sessionId) {
        console.error('Session has expired');
        this.onSessionExpired?.(data);
        this.sessionId = null;
      }
    });
    
    // Session renewed notification
    this.socket.on('sessionRenewed', (data) => {
      if (data.sessionId === this.sessionId) {
        console.log('Session renewed until:', data.expiresAt);
        this.onSessionRenewed?.(data);
      }
    });
  }
  
  joinSession(sessionId) {
    this.sessionId = sessionId;
    this.socket.emit('join', { 
      roomId: sessionId,
      type: 'session'
    });
  }
  
  leaveSession() {
    if (this.sessionId) {
      this.socket.emit('leave', { 
        roomId: this.sessionId 
      });
      this.sessionId = null;
    }
  }
}

Handling Session Expiration

class ResilientSessionClient {
  constructor(agentId, userId) {
    this.agentId = agentId;
    this.userId = userId;
    this.sessionId = null;
    this.sessionConfig = {
      timeoutMinutes: 30,
      autoRenew: true,
      maxDurationMinutes: 180
    };
  }
  
  async ensureSession() {
    if (!this.sessionId) {
      await this.createSession();
      return;
    }
    
    // Check if session is still valid
    try {
      const response = await fetch(`/api/messaging/sessions/${this.sessionId}`);
      
      if (!response.ok) {
        if (response.status === 404 || response.status === 410) {
          // Session not found or expired
          await this.createSession();
        }
      }
    } catch (error) {
      console.error('Session check failed:', error);
      await this.createSession();
    }
  }
  
  async createSession() {
    const response = await fetch('/api/messaging/sessions', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        agentId: this.agentId,
        userId: this.userId,
        timeoutConfig: this.sessionConfig
      })
    });
    
    const data = await response.json();
    this.sessionId = data.sessionId;
    
    // Start heartbeat for new session
    this.startHeartbeat();
    
    return this.sessionId;
  }
  
  async sendMessage(content) {
    await this.ensureSession();
    
    const response = await fetch(
      `/api/messaging/sessions/${this.sessionId}/messages`,
      {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ content })
      }
    );
    
    if (!response.ok && (response.status === 404 || response.status === 410)) {
      // Session was lost, recreate and retry
      await this.createSession();
      return this.sendMessage(content);
    }
    
    return response.json();
  }
}

Error Handling

Custom Error Classes

abstract class SessionError extends Error {
  constructor(
    message: string,
    public code: string,
    public statusCode: number,
    public details?: any
  ) {
    super(message);
    this.name = this.constructor.name;
  }
}

class SessionNotFoundError extends SessionError {
  constructor(sessionId: string) {
    super(
      `Session not found: ${sessionId}`,
      'SESSION_NOT_FOUND',
      404,
      { sessionId }
    );
  }
}

class SessionExpiredError extends SessionError {
  constructor(sessionId: string, expiresAt: Date) {
    super(
      `Session has expired`,
      'SESSION_EXPIRED',
      410, // Gone
      { sessionId, expiresAt }
    );
  }
}

class SessionRenewalError extends SessionError {
  constructor(sessionId: string, reason: string, details?: any) {
    super(
      `Cannot renew session: ${reason}`,
      'SESSION_RENEWAL_FAILED',
      422, // Unprocessable Entity
      { sessionId, reason, ...details }
    );
  }
}

Error Handling Examples

try {
  const response = await fetch(`/api/messaging/sessions/${sessionId}/messages`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ content: message })
  });

  if (!response.ok) {
    const error = await response.json();
    
    switch (response.status) {
      case 404:
        // Session not found
        console.error('Session not found:', error.details);
        // Create new session
        break;
        
      case 410:
        // Session expired
        console.error('Session expired at:', error.details.expiresAt);
        // Create new session or notify user
        break;
        
      case 400:
        // Validation error
        if (error.error.includes('content')) {
          console.error('Invalid message content');
        } else if (error.error.includes('metadata')) {
          console.error('Metadata too large');
        }
        break;
        
      case 422:
        // Session cannot be renewed
        console.error('Max duration reached:', error.details);
        // Must create new session
        break;
        
      default:
        console.error('Error:', error.message);
    }
  }
} catch (error) {
  console.error('Network error:', error);
}

Performance Optimization

Pagination Strategy

async function getMessagesOptimized(
  sessionId: string,
  query: GetMessagesQuery
) {
  const session = sessions.get(sessionId);
  
  // Smart fetching strategy
  if (query.after && !query.before) {
    // Forward pagination - fetch extra for filtering
    const messages = await fetchMessages(
      session.channelId,
      query.limit * 2
    );
    
    return messages
      .filter(m => m.createdAt > query.after)
      .slice(0, query.limit);
  }
  
  if (query.before && !query.after) {
    // Backward pagination - direct fetch
    return await fetchMessages(
      session.channelId,
      query.limit,
      query.before
    );
  }
  
  // Default - latest messages
  return await fetchMessages(session.channelId, query.limit);
}

Configuration Caching

class AgentConfigCache {
  private cache = new Map<UUID, SessionTimeoutConfig>();
  private maxAge = 5 * 60 * 1000; // 5 minutes
  private timestamps = new Map<UUID, number>();
  
  get(agentId: UUID): SessionTimeoutConfig | undefined {
    const timestamp = this.timestamps.get(agentId);
    
    if (timestamp && Date.now() - timestamp > this.maxAge) {
      // Cache expired
      this.cache.delete(agentId);
      this.timestamps.delete(agentId);
      return undefined;
    }
    
    return this.cache.get(agentId);
  }
  
  set(agentId: UUID, config: SessionTimeoutConfig) {
    this.cache.set(agentId, config);
    this.timestamps.set(agentId, Date.now());
  }
}

Memory Management

Memory Leak Prevention

class BoundedCache extends Map {
  private maxSize: number;
  
  constructor(maxSize: number = 1000) {
    super();
    this.maxSize = maxSize;
  }
  
  set(key: string, value: any) {
    // Remove oldest entries if at capacity
    if (this.size >= this.maxSize) {
      const firstKey = this.keys().next().value;
      this.delete(firstKey);
    }
    return super.set(key, value);
  }
}

// Process lifecycle hooks
process.once('SIGTERM', clearAllIntervals);
process.once('SIGINT', clearAllIntervals);
process.once('beforeExit', clearAllIntervals);

Cache with TTL

class CacheWithTTL extends Map {
  private ttl: number;
  private timestamps = new Map<string, number>();
  
  constructor(ttl: number = 5 * 60 * 1000) {
    super();
    this.ttl = ttl;
  }
  
  set(key: string, value: any) {
    this.timestamps.set(key, Date.now());
    return super.set(key, value);
  }
  
  get(key: string) {
    const timestamp = this.timestamps.get(key);
    if (timestamp && Date.now() - timestamp > this.ttl) {
      this.delete(key);
      return undefined;
    }
    return super.get(key);
  }
}

Troubleshooting

Common Issues

  1. Session Not Found (404)
    • Session may have expired or been deleted
    • Create a new session and retry
    • Check session ID format
  2. Session Expired (410)
    • Session exceeded its timeout period
    • Check the expiresAt timestamp in error details
    • Create a new session or adjust timeout configuration
  3. Cannot Renew Session (422)
    • Session has reached maximum duration limit
    • Check maxDurationMinutes configuration
    • Must create a new session
  4. Invalid Timeout Configuration (400)
    • Timeout values outside allowed range (5-1440 minutes)
    • Check configuration values against limits
    • Adjust to valid ranges
  5. Agent Not Available
    • Ensure the agent is started and running
    • Check agent logs for errors
    • Verify agent ID is correct

Debugging

Enable debug logging:
const response = await fetch('/api/messaging/sessions', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Debug': 'true'  // Enable debug info in response
  },
  body: JSON.stringify({ 
    agentId, 
    userId,
    // Debug mode may include additional session state info
  })
});
Check session cleanup logs:
# Server logs will show cleanup activity
[Sessions API] Cleanup cycle completed: 3 expired sessions removed, 2 warnings issued
[Sessions API] Session renewed via heartbeat: session-123
[Sessions API] Session abc-123 has reached maximum duration

When to Use Sessions vs Traditional Messaging

Use Sessions When:

  • Building chat interfaces: Web apps, mobile apps, or any UI with a chat component
  • Direct user-to-agent conversations: One-on-one interactions between a user and an agent
  • Simplified integration: You want to get up and running quickly without infrastructure complexity
  • Stateful conversations: You need the agent to maintain context throughout the conversation
  • Session management required: You need timeout, renewal, and expiration handling
  • Resource optimization: You want automatic cleanup of inactive conversations
  • Personal assistants: Building AI assistants that remember user preferences and conversation history

Use Traditional Messaging When:

  • Multi-agent coordination: Multiple agents need to communicate in the same channel
  • Group conversations: Multiple users and agents interacting together
  • Platform integrations: Integrating with Discord, Slack, or other platforms that have their own channel concepts
  • Broadcast scenarios: One agent sending messages to multiple channels/users
  • Complex routing: Custom message routing logic between different channels and servers
  • Permanent history: You need conversations to persist indefinitely without timeout

Scalability Considerations

Horizontal Scaling

For production deployments:
  1. Session Store Distribution
    • Use Redis for distributed session storage
    • Implement session affinity for WebSocket connections
    • Use consistent hashing for session distribution
  2. Message Queue Integration
    • Decouple message processing from API responses
    • Use message queues for agent processing
    • Implement async response patterns
  3. Database Optimization
    • Index session-related columns
    • Implement connection pooling
    • Consider read replicas for message retrieval

Monitoring Metrics

interface SessionMetrics {
  // Volume metrics
  sessionsCreated: Counter;
  sessionsExpired: Counter;
  sessionsRenewed: Counter;
  
  // Performance metrics
  messageLatency: Histogram;
  renewalLatency: Histogram;
  
  // Health metrics
  activeSessions: Gauge;
  sessionsByAgent: Gauge;
  expirationWarnings: Counter;
  
  // Error metrics
  validationErrors: Counter;
  expirationErrors: Counter;
  renewalFailures: Counter;
}

Security Considerations

Input Validation

// UUID validation
function validateUuid(value: string): boolean {
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
  return uuidRegex.test(value);
}

// Content validation
function validateContent(content: unknown): void {
  if (typeof content !== 'string') {
    throw new InvalidContentError('Content must be a string');
  }
  
  if (content.length === 0) {
    throw new InvalidContentError('Content cannot be empty');
  }
  
  if (content.length > MAX_CONTENT_LENGTH) {
    throw new InvalidContentError(
      `Content exceeds maximum length of ${MAX_CONTENT_LENGTH}`
    );
  }
}

// Metadata validation
function validateMetadata(metadata: unknown): void {
  if (!metadata) return;
  
  const size = JSON.stringify(metadata).length;
  if (size > MAX_METADATA_SIZE) {
    throw new InvalidMetadataError(
      `Metadata exceeds maximum size of ${MAX_METADATA_SIZE} bytes`
    );
  }
}

Rate Limiting

interface RateLimitConfig {
  windowMs: number;
  maxRequests: number;
  keyGenerator: (req: Request) => string;
}

const sessionRateLimits = {
  create: {
    windowMs: 60 * 1000,
    maxRequests: 10,
    keyGenerator: (req) => req.ip
  },
  message: {
    windowMs: 60 * 1000,
    maxRequests: 100,
    keyGenerator: (req) => `${req.params.sessionId}:${req.ip}`
  }
};

Best Practices

Session Design

  • Appropriate Timeouts: Choose timeouts based on use case
  • Auto-Renewal: Enable for active conversations
  • Max Duration: Set limits to prevent infinite sessions
  • Warning Handling: Notify users before expiration
  • Cleanup Strategy: Regular cleanup of expired sessions

Implementation

  • Error Recovery: Graceful handling of session loss
  • State Persistence: Consider persistent storage for production
  • Monitoring: Track session metrics and health
  • Testing: Test timeout and renewal scenarios
  • Documentation: Document session behavior clearly

Agent Configuration

Configure timeout defaults for specific agents via environment variables:
# Agent-specific settings (in agent's environment)
SESSION_TIMEOUT_MINUTES=60
SESSION_AUTO_RENEW=true
SESSION_MAX_DURATION_MINUTES=240
SESSION_WARNING_THRESHOLD_MINUTES=10
Or configure programmatically in the agent:
// In agent configuration
const agentConfig = {
  name: 'CustomerServiceBot',
  settings: {
    SESSION_TIMEOUT_MINUTES: '45',
    SESSION_AUTO_RENEW: 'true',
    SESSION_MAX_DURATION_MINUTES: '180',
    SESSION_WARNING_THRESHOLD_MINUTES: '5'
  }
};

API Reference

Endpoints

  • POST /api/messaging/sessions - Create a new session
  • GET /api/messaging/sessions/:sessionId - Get session information
  • POST /api/messaging/sessions/:sessionId/messages - Send a message
  • GET /api/messaging/sessions/:sessionId/messages - Get message history
  • POST /api/messaging/sessions/:sessionId/renew - Manually renew session
  • PATCH /api/messaging/sessions/:sessionId/timeout - Update timeout configuration
  • POST /api/messaging/sessions/:sessionId/heartbeat - Send heartbeat to keep alive
  • DELETE /api/messaging/sessions/:sessionId - End session
  • GET /api/messaging/sessions - List all active sessions (admin)
  • GET /api/messaging/sessions/health - Health check endpoint

What’s Next?