packages/docs/runtime/sessions-api.mdx
The Sessions API provides a sophisticated abstraction layer over the traditional messaging infrastructure, enabling persistent, stateful conversations with automatic timeout management and renewal capabilities.
The Sessions API eliminates the complexity of channel management. Traditional messaging approaches require you to:
With Sessions API, you simply:
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" });
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, unknown>;
}
flowchart TD
Create[Creation] --> Active[Active]
Active --> Warning[Near Expiration]
Warning --> Renewed[Renewed]
Warning --> Expired[Expired]
Renewed --> Active
Active --> Deleted[Deleted]
Expired --> Cleanup[Cleanup]
classDef active fill:#4caf50,color:#fff
classDef warning fill:#ff9800,color:#fff
classDef ended fill:#f44336,color:#fff
classDef transition fill:#2196f3,color:#fff
class Create,Renewed transition
class Active active
class Warning warning
class Expired,Deleted,Cleanup ended
interface SessionTimeoutConfig {
timeoutMinutes?: number; // Inactivity timeout (5-1440)
autoRenew?: boolean; // Auto-renew on activity
maxDurationMinutes?: number; // Maximum total duration
warningThresholdMinutes?: number; // Warning threshold
}
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,
};
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)// 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();
}
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;
}
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
// 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`,
);
}
// 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`);
// 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();
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(),
};
}
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;
}
}
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();
}
}
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;
}
}
}
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);
}
}
}
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,
};
}
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;
}
}
}
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();
}
}
abstract class SessionError extends Error {
constructor(
message: string,
public code: string,
public statusCode: number,
public details?: Record<string, unknown>,
) {
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?: Record<string, unknown>) {
super(
`Cannot renew session: ${reason}`,
"SESSION_RENEWAL_FAILED",
422, // Unprocessable Entity
{ sessionId, reason, ...details },
);
}
}
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);
}
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);
}
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());
}
}
class BoundedCache<T> extends Map<string, T> {
private maxSize: number;
constructor(maxSize: number = 1000) {
super();
this.maxSize = maxSize;
}
set(key: string, value: T) {
// 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);
class CacheWithTTL<T> extends Map<string, T> {
private ttl: number;
private timestamps = new Map<string, number>();
constructor(ttl: number = 5 * 60 * 1000) {
super();
this.ttl = ttl;
}
set(key: string, value: T) {
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);
}
}
Session Not Found (404)
Session Expired (410)
expiresAt timestamp in error detailsCannot Renew Session (422)
maxDurationMinutes configurationInvalid Timeout Configuration (400)
Agent Not Available
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
For production deployments:
Session Store Distribution
Message Queue Integration
Database Optimization
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;
}
// 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`,
);
}
}
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}`,
},
};
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",
},
};
The Sessions API provides a comprehensive set of endpoints for managing stateful conversations with ElizaOS agents. This reference covers all available endpoints, request/response schemas, and error handling.
http://localhost:3000/api/messaging/sessions
Currently, the Sessions API does not require authentication. In production environments, you should implement appropriate authentication mechanisms.
Creates a new conversation session with an agent.
<Card title="POST /api/messaging/sessions" icon="plus"> Create a new session with configurable timeout policies </Card>Request Body:
interface CreateSessionRequest {
agentId: string; // UUID of the agent
userId: string; // UUID of the user
metadata?: {
// Optional session metadata
[key: string]: unknown;
};
timeoutConfig?: {
// Optional timeout configuration
timeoutMinutes?: number; // 5-1440 minutes
autoRenew?: boolean; // Default: true
maxDurationMinutes?: number; // Maximum session duration
warningThresholdMinutes?: number; // When to warn about expiration
};
}
Response (201 Created):
interface CreateSessionResponse {
sessionId: string;
agentId: string;
userId: string;
createdAt: Date;
metadata: object;
expiresAt: Date;
timeoutConfig: SessionTimeoutConfig;
}
Example:
curl -X POST http://localhost:3000/api/messaging/sessions \
-H "Content-Type: application/json" \
-d '{
"agentId": "123e4567-e89b-12d3-a456-426614174000",
"userId": "987f6543-e21b-12d3-a456-426614174000",
"timeoutConfig": {
"timeoutMinutes": 30,
"autoRenew": true
}
}'
Retrieves detailed information about a session including its current status.
<Card title="GET /api/messaging/sessions/{sessionId}" icon="info"> Get session details and current status </Card>Response (200 OK):
interface SessionInfoResponse {
sessionId: string;
agentId: string;
userId: string;
createdAt: Date;
lastActivity: Date;
metadata: object;
expiresAt: Date;
timeoutConfig: SessionTimeoutConfig;
renewalCount: number;
timeRemaining: number; // Milliseconds until expiration
isNearExpiration: boolean; // True if within warning threshold
}
Errors:
404 Not Found - Session does not exist410 Gone - Session has expiredSends a message within a session. Automatically renews the session if auto-renewal is enabled.
<Card title="POST /api/messaging/sessions/{sessionId}/messages" icon="paper-plane"
Send a message in the conversation </Card>
Request Body:
interface SendMessageRequest {
content: string; // Message content (max 4000 chars)
metadata?: {
// Optional message metadata
[key: string]: unknown;
};
attachments?: Attachment[]; // Optional attachments
}
Response (201 Created):
interface MessageResponse {
id: string;
content: string;
authorId: string;
createdAt: Date;
metadata: object;
sessionStatus?: {
// Session renewal information
expiresAt: Date;
renewalCount: number;
wasRenewed: boolean;
isNearExpiration: boolean;
};
}
Errors:
400 Bad Request - Invalid content or metadata404 Not Found - Session not found410 Gone - Session expiredRetrieves messages from a session with pagination support.
<Card title="GET /api/messaging/sessions/{sessionId}/messages" icon="messages"> Retrieve conversation history </Card>Query Parameters:
interface GetMessagesQuery {
limit?: string; // Number of messages (1-100, default: 50)
before?: string; // Timestamp for pagination (older messages)
after?: string; // Timestamp for pagination (newer messages)
}
Response (200 OK):
interface GetMessagesResponse {
messages: SimplifiedMessage[];
hasMore: boolean;
cursors?: {
before?: number; // Use for getting older messages
after?: number; // Use for getting newer messages
};
}
interface SimplifiedMessage {
id: string;
content: string;
authorId: string;
isAgent: boolean;
createdAt: Date;
metadata: {
thought?: string; // Agent's internal reasoning
actions?: string[]; // Actions taken by agent
[key: string]: unknown;
};
}
Manually renews a session to extend its expiration time.
<Card title="POST /api/messaging/sessions/{sessionId}/renew" icon="refresh"> Manually extend session lifetime </Card>Response (200 OK):
interface SessionInfoResponse {
sessionId: string;
agentId: string;
userId: string;
createdAt: Date;
lastActivity: Date;
expiresAt: Date;
timeoutConfig: SessionTimeoutConfig;
renewalCount: number;
timeRemaining: number;
isNearExpiration: boolean;
}
Errors:
404 Not Found - Session not found410 Gone - Session expired422 Unprocessable Entity - Cannot renew (max duration reached)Updates the timeout configuration for an active session.
<Card title="PATCH /api/messaging/sessions/{sessionId}/timeout" icon="clock"> Modify session timeout settings </Card>Request Body:
interface SessionTimeoutConfig {
timeoutMinutes?: number; // 5-1440 minutes
autoRenew?: boolean;
maxDurationMinutes?: number;
warningThresholdMinutes?: number;
}
Response (200 OK):
Returns updated SessionInfoResponse
Errors:
400 Bad Request - Invalid timeout configuration404 Not Found - Session not found410 Gone - Session expiredKeeps a session alive and optionally renews it if auto-renewal is enabled.
<Card title="POST /api/messaging/sessions/{sessionId}/heartbeat" icon="heart"> Keep session alive with periodic heartbeat </Card>Response (200 OK):
Returns SessionInfoResponse with updated expiration information.
Errors:
404 Not Found - Session not found410 Gone - Session expiredExplicitly ends and removes a session.
<Card title="DELETE /api/messaging/sessions/{sessionId}" icon="trash"> End and delete a session </Card>Response (200 OK):
{
success: true,
message: "Session {sessionId} deleted successfully"
}
Errors:
404 Not Found - Session not foundLists all active sessions in the system. This is an administrative endpoint.
<Card title="GET /api/messaging/sessions" icon="list"> List all active sessions </Card>Response (200 OK):
interface ListSessionsResponse {
sessions: SessionInfoResponse[];
total: number;
stats: {
totalSessions: number;
activeSessions: number;
expiredSessions: number;
};
}
Checks the health status of the Sessions API service.
<Card title="GET /api/messaging/sessions/health" icon="heart-pulse"> Check service health </Card>Response (200 OK):
interface HealthCheckResponse {
status: "healthy" | "degraded" | "unhealthy";
activeSessions: number;
timestamp: string;
expiringSoon?: number; // Sessions near expiration
invalidSessions?: number; // Corrupted sessions detected
uptime?: number; // Service uptime in seconds
}
All error responses follow a consistent format:
interface ErrorResponse {
error: string; // Error message
details?: {
// Additional context
[key: string]: unknown;
};
}
| Status Code | Error Type | Description |
|---|---|---|
400 | Bad Request | Invalid input parameters or request body |
404 | Not Found | Session or resource not found |
410 | Gone | Session has expired |
422 | Unprocessable Entity | Operation cannot be completed (e.g., max duration reached) |
500 | Internal Server Error | Unexpected server error |
The API uses specific error classes for different scenarios:
SessionNotFoundError - Session does not existSessionExpiredError - Session has exceeded its timeoutSessionCreationError - Failed to create sessionAgentNotFoundError - Specified agent not foundInvalidUuidError - Invalid UUID formatMissingFieldsError - Required fields missingInvalidContentError - Message content validation failedInvalidMetadataError - Metadata exceeds size limitInvalidPaginationError - Invalid pagination parametersInvalidTimeoutConfigError - Invalid timeout configurationSessionRenewalError - Cannot renew sessionMessageSendError - Failed to send messageCurrently, the Sessions API does not implement rate limiting. In production, you should implement appropriate rate limiting based on your requirements.
When using WebSocket connections alongside the Sessions API, the following events are available:
// Join a session for real-time updates
socket.emit("join", { roomId: sessionId });
// Listen for new messages
socket.on("messageBroadcast", (message) => {
// Handle new message
});
// Session expiration warning
socket.on("sessionExpirationWarning", (data) => {
// data.sessionId, data.minutesRemaining
});
// Session expired
socket.on("sessionExpired", (data) => {
// data.sessionId, data.expiredAt
});
// Session renewed
socket.on("sessionRenewed", (data) => {
// data.sessionId, data.expiresAt, data.renewalCount
});
Configure the Sessions API behavior using these environment variables:
| Variable | Default | Description |
|---|---|---|
SESSION_DEFAULT_TIMEOUT_MINUTES | 30 | Default session timeout |
SESSION_MIN_TIMEOUT_MINUTES | 5 | Minimum allowed timeout |
SESSION_MAX_TIMEOUT_MINUTES | 1440 | Maximum allowed timeout (24 hours) |
SESSION_MAX_DURATION_MINUTES | 720 | Maximum total session duration (12 hours) |
SESSION_WARNING_THRESHOLD_MINUTES | 5 | When to trigger expiration warning |
SESSION_CLEANUP_INTERVAL_MINUTES | 5 | How often to clean expired sessions |
CLEAR_SESSIONS_ON_SHUTDOWN | false | Clear all sessions on server shutdown |
class SessionsAPIClient {
constructor(private baseUrl: string = "http://localhost:3000") {}
async createSession(
agentId: string,
userId: string,
config?: SessionTimeoutConfig,
) {
const response = await fetch(`${this.baseUrl}/api/messaging/sessions`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ agentId, userId, timeoutConfig: config }),
});
if (!response.ok)
throw new Error(`Failed to create session: ${response.status}`);
return response.json();
}
async sendMessage(sessionId: string, content: string, metadata?: object) {
const response = await fetch(
`${this.baseUrl}/api/messaging/sessions/${sessionId}/messages`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ content, metadata }),
},
);
if (!response.ok)
throw new Error(`Failed to send message: ${response.status}`);
return response.json();
}
async getMessages(sessionId: string, limit = 50, before?: number) {
const params = new URLSearchParams({ limit: limit.toString() });
if (before) params.append("before", before.toString());
const response = await fetch(
`${this.baseUrl}/api/messaging/sessions/${sessionId}/messages?${params}`,
);
if (!response.ok)
throw new Error(`Failed to get messages: ${response.status}`);
return response.json();
}
async renewSession(sessionId: string) {
const response = await fetch(
`${this.baseUrl}/api/messaging/sessions/${sessionId}/renew`,
{ method: "POST" },
);
if (!response.ok)
throw new Error(`Failed to renew session: ${response.status}`);
return response.json();
}
async sendHeartbeat(sessionId: string) {
const response = await fetch(
`${this.baseUrl}/api/messaging/sessions/${sessionId}/heartbeat`,
{ method: "POST" },
);
if (!response.ok) throw new Error(`Heartbeat failed: ${response.status}`);
return response.json();
}
}
import requests
from typing import Optional, Dict, Any
class SessionsAPIClient:
def __init__(self, base_url: str = "http://localhost:3000"):
self.base_url = base_url
self.session = requests.Session()
def create_session(
self,
agent_id: str,
user_id: str,
timeout_config: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""Create a new session with an agent."""
payload = {
"agentId": agent_id,
"userId": user_id
}
if timeout_config:
payload["timeoutConfig"] = timeout_config
response = self.session.post(
f"{self.base_url}/api/messaging/sessions",
json=payload
)
response.raise_for_status()
return response.json()
def send_message(
self,
session_id: str,
content: str,
metadata: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""Send a message in a session."""
payload = {"content": content}
if metadata:
payload["metadata"] = metadata
response = self.session.post(
f"{self.base_url}/api/messaging/sessions/{session_id}/messages",
json=payload
)
response.raise_for_status()
return response.json()
def get_messages(
self,
session_id: str,
limit: int = 50,
before: Optional[int] = None,
after: Optional[int] = None
) -> Dict[str, Any]:
"""Retrieve messages from a session."""
params = {"limit": str(limit)}
if before:
params["before"] = str(before)
if after:
params["after"] = str(after)
response = self.session.get(
f"{self.base_url}/api/messaging/sessions/{session_id}/messages",
params=params
)
response.raise_for_status()
return response.json()
def renew_session(self, session_id: str) -> Dict[str, Any]:
"""Manually renew a session."""
response = self.session.post(
f"{self.base_url}/api/messaging/sessions/{session_id}/renew"
)
response.raise_for_status()
return response.json()
def send_heartbeat(self, session_id: str) -> Dict[str, Any]:
"""Send a heartbeat to keep session alive."""
response = self.session.post(
f"{self.base_url}/api/messaging/sessions/{session_id}/heartbeat"
)
response.raise_for_status()
return response.json()