Event System
Comprehensive guide to the event-driven architecture and event handling in ElizaOS v1.2.0
Event System
ElizaOS v1.2.0 implements a comprehensive event-driven architecture that enables loose coupling between components. The event system facilitates communication between agents, plugins, and services through a standardized event interface with typed payloads, handler registration, and enhanced domain-based routing.
Event Types
The core event types are defined in the EventType
enum:
export enum EventType {
// World events
WORLD_JOINED = "WORLD_JOINED",
WORLD_CONNECTED = "WORLD_CONNECTED",
WORLD_LEFT = "WORLD_LEFT",
// Entity events
ENTITY_JOINED = "ENTITY_JOINED",
ENTITY_LEFT = "ENTITY_LEFT",
ENTITY_UPDATED = "ENTITY_UPDATED",
// Room events
ROOM_JOINED = "ROOM_JOINED",
ROOM_LEFT = "ROOM_LEFT",
// Message events
MESSAGE_RECEIVED = "MESSAGE_RECEIVED",
MESSAGE_SENT = "MESSAGE_SENT",
MESSAGE_DELETED = "MESSAGE_DELETED",
// Channel events
CHANNEL_CLEARED = "CHANNEL_CLEARED",
// Voice events
VOICE_MESSAGE_RECEIVED = "VOICE_MESSAGE_RECEIVED",
VOICE_MESSAGE_SENT = "VOICE_MESSAGE_SENT",
// Interaction events
REACTION_RECEIVED = "REACTION_RECEIVED",
POST_GENERATED = "POST_GENERATED",
INTERACTION_RECEIVED = "INTERACTION_RECEIVED",
// Run events
RUN_STARTED = "RUN_STARTED",
RUN_ENDED = "RUN_ENDED",
RUN_TIMEOUT = "RUN_TIMEOUT",
// Action events
ACTION_STARTED = "ACTION_STARTED",
ACTION_COMPLETED = "ACTION_COMPLETED",
// Evaluator events
EVALUATOR_STARTED = "EVALUATOR_STARTED",
EVALUATOR_COMPLETED = "EVALUATOR_COMPLETED",
// Model events
MODEL_USED = "MODEL_USED",
// Enhanced events in v1.2.0
MODEL_PROVIDER_FAILED = "MODEL_PROVIDER_FAILED",
SERVICE_HEALTH_CHECK_FAILED = "SERVICE_HEALTH_CHECK_FAILED",
PLUGIN_LOADED = "PLUGIN_LOADED",
PLUGIN_FAILED = "PLUGIN_FAILED",
RUNTIME_STARTING = "RUNTIME_STARTING",
RUNTIME_STOPPING = "RUNTIME_STOPPING",
}
Event Payloads
Each event type has a corresponding payload interface:
Base Payload
export interface EventPayload {
runtime: IAgentRuntime;
source: string;
onComplete?: () => void;
}
Message Events
export interface MessagePayload extends EventPayload {
message: Memory;
callback?: HandlerCallback;
onComplete?: () => void;
}
Entity Events
export interface EntityPayload extends EventPayload {
entityId: UUID;
worldId?: UUID;
roomId?: UUID;
metadata?: {
orginalId: string;
username: string;
displayName?: string;
[key: string]: any;
};
}
Channel Events
export interface ChannelClearedPayload extends EventPayload {
roomId: UUID;
channelId: string;
memoryCount: number;
}
Run Events
export interface RunEventPayload extends EventPayload {
runId: UUID;
messageId: UUID;
roomId: UUID;
entityId: UUID;
startTime: number;
status: "started" | "completed" | "timeout";
endTime?: number;
duration?: number;
error?: string;
}
v1.2.0 Domain-Based Events
Domain Event System
ElizaOS v1.2.0 introduces domain-based event routing for better organization and filtering:
// Domain-based event structure
interface DomainEvent {
domain: string;
event: string;
payload: any;
timestamp: number;
runId: UUID;
source: string;
}
// Emit domain events
await runtime.emitDomainEvent("database", "connection_error", {
error: "Connection timeout",
retryCount: 3,
timestamp: Date.now(),
});
await runtime.emitDomainEvent("ai", "model_response", {
modelType: "TEXT_LARGE",
provider: "openai",
tokens: 150,
latency: 2500,
});
await runtime.emitDomainEvent("plugin", "migration_completed", {
pluginName: "discord",
version: "1.2.0",
tablesAffected: ["discord_messages", "discord_users"],
});
Domain Event Handlers
Register handlers for specific domain events:
// Listen to database domain events
runtime.registerEvent("database:connection_error", async (context) => {
console.log(`Database error in run ${context.runId}: ${context.error}`);
// Implement retry logic
await retryDatabaseConnection();
});
runtime.registerEvent("database:query_slow", async (context) => {
console.log(`Slow query detected: ${context.query} (${context.duration}ms)`);
// Log for optimization
await logSlowQuery(context);
});
// Listen to AI domain events
runtime.registerEvent("ai:model_response", async (context) => {
console.log(`Model ${context.modelType} responded in ${context.latency}ms`);
// Track performance metrics
await trackModelPerformance(context);
});
runtime.registerEvent("ai:model_error", async (context) => {
console.log(`Model ${context.modelType} failed: ${context.error}`);
// Implement fallback logic
await handleModelFailure(context);
});
Enhanced Event Context
All events in v1.2.0 include enhanced context information:
interface EnhancedEventContext {
eventId: UUID;
runId: UUID;
timestamp: string;
source: string;
actionContext?: ActionContext;
serviceContext?: ServiceContext;
metadata?: Record<string, any>;
}
// Example enhanced event emission
await runtime.emitEvent("message_processed", {
messageId: message.id,
agentId: runtime.agentId,
runId: runtime.runId,
actionCount: actions.length,
processingTime: endTime - startTime,
metadata: {
modelCalls: modelCallCount,
memoryLookups: memoryLookupCount,
serviceInteractions: serviceInteractionCount,
},
});
Event Emitting
Runtime Event Emission
// Emit events from agent runtime
export class AgentRuntime implements IAgentRuntime {
async emitEvent<T extends keyof EventPayloadMap>(
eventType: T,
payload: EventPayloadMap[T]
): Promise<void> {
const handlers = this.eventHandlers.get(eventType) || [];
for (const handler of handlers) {
try {
await handler(payload);
} catch (error) {
logger.error(`Error in event handler for ${eventType}:`, error);
}
}
}
}
Common Event Emissions
// Message received
await runtime.emitEvent(EventType.MESSAGE_RECEIVED, {
runtime,
message: incomingMessage,
callback: responseHandler,
source: "message-bus",
});
// Entity joined
await runtime.emitEvent(EventType.ENTITY_JOINED, {
runtime,
entityId: userId,
worldId: serverId,
roomId: channelId,
metadata: { type: ChannelType.DM },
source: "websocket",
});
// Run started
await runtime.emitEvent(EventType.RUN_STARTED, {
runtime,
runId: generateUUID(),
messageId: message.id,
roomId: message.roomId,
entityId: message.entityId,
startTime: Date.now(),
status: "started",
source: "messageHandler",
});
Event Handlers
Handler Registration
Event handlers are registered through plugins:
// Plugin event handlers
const events = {
[EventType.MESSAGE_RECEIVED]: [
async (payload: MessagePayload) => {
await messageReceivedHandler({
runtime: payload.runtime,
message: payload.message,
callback: payload.callback,
onComplete: payload.onComplete,
});
},
],
[EventType.ENTITY_JOINED]: [
async (payload: EntityPayload) => {
await handleEntityJoined(payload);
},
],
[EventType.MESSAGE_DELETED]: [
async (payload: MessagePayload) => {
await messageDeletedHandler({
runtime: payload.runtime,
message: payload.message,
});
},
],
};
// Register in plugin
export const bootstrapPlugin: Plugin = {
name: "bootstrap",
events: events as any as PluginEvents,
// ... other plugin properties
};
Handler Implementation
// Message received handler
const messageReceivedHandler = async ({
runtime,
message,
callback,
onComplete,
}: MessageReceivedHandlerParams): Promise<void> => {
try {
// Process the message
logger.info(`Processing message from ${message.entityId}`);
// Generate response
const response = await generateResponse(runtime, message);
// Execute callback with response
if (callback) {
await callback(response);
}
// Signal completion
onComplete?.();
} catch (error) {
logger.error("Message processing failed:", error);
throw error;
}
};
// Entity joined handler
const handleEntityJoined = async (payload: EntityPayload) => {
const { entityId, worldId, roomId, metadata } = payload;
// Ensure user connection
await syncSingleUser(
entityId,
payload.runtime,
worldId,
roomId,
metadata?.type || ChannelType.GROUP,
payload.source
);
};
Event Lifecycle
Message Processing Lifecycle
The complete message processing lifecycle involves multiple events:
// 1. Message received
await runtime.emitEvent(EventType.MESSAGE_RECEIVED, {
runtime,
message: incomingMessage,
callback: async (response: Content) => {
// 2. Generate response
const responseMessage = createResponseMessage(response);
// 3. Message sent
await runtime.emitEvent(EventType.MESSAGE_SENT, {
runtime,
message: responseMessage,
source: "agent_response",
});
// 4. Post to external systems
await postToExternalSystems(responseMessage);
},
onComplete: () => {
logger.info("Message processing completed");
},
});
Run Tracking
// Track agent run lifecycle
class RunTracker {
private activeRuns = new Map<UUID, RunEventPayload>();
async startRun(messageId: UUID, roomId: UUID, entityId: UUID) {
const runId = generateUUID();
const startTime = Date.now();
const payload: RunEventPayload = {
runtime,
runId,
messageId,
roomId,
entityId,
startTime,
status: "started",
source: "run-tracker",
};
this.activeRuns.set(runId, payload);
await runtime.emitEvent(EventType.RUN_STARTED, payload);
return runId;
}
async completeRun(runId: UUID, error?: string) {
const run = this.activeRuns.get(runId);
if (!run) return;
const endTime = Date.now();
const completedPayload: RunEventPayload = {
...run,
status: error ? "error" : "completed",
endTime,
duration: endTime - run.startTime,
error,
};
await runtime.emitEvent(EventType.RUN_ENDED, completedPayload);
this.activeRuns.delete(runId);
}
}
Platform-Specific Events
Discord Events
// Discord-specific event handling
const discordEvents = {
[`${PlatformPrefix.DISCORD}_MESSAGE_RECEIVED`]: [
async (payload: MessagePayload) => {
// Handle Discord-specific message processing
await processDiscordMessage(payload);
},
],
[`${PlatformPrefix.DISCORD}_REACTION_RECEIVED`]: [
async (payload: MessagePayload) => {
// Handle Discord reaction events
await processDiscordReaction(payload);
},
],
};
Custom Platform Events
// Define custom platform events
enum CustomEventType {
CUSTOM_ACTION = "CUSTOM_ACTION",
INTEGRATION_SYNC = "INTEGRATION_SYNC",
}
// Register custom event handlers
const customEvents = {
[CustomEventType.CUSTOM_ACTION]: [
async (payload: CustomPayload) => {
// Handle custom action
await handleCustomAction(payload);
},
],
};
Error Handling
Event Handler Errors
// Graceful error handling in event handlers
const safeEventHandler = async (payload: EventPayload) => {
try {
await processEvent(payload);
} catch (error) {
logger.error("Event handler error:", error);
// Emit error event
await payload.runtime.emitEvent(EventType.ERROR, {
...payload,
error: error.message,
originalEvent: EventType.MESSAGE_RECEIVED,
});
// Don't re-throw to prevent cascade failures
}
};
Event Emission Errors
// Error boundaries for event emission
export class AgentRuntime implements IAgentRuntime {
async emitEvent<T extends keyof EventPayloadMap>(
eventType: T,
payload: EventPayloadMap[T]
): Promise<void> {
const handlers = this.eventHandlers.get(eventType) || [];
const errors: Error[] = [];
for (const handler of handlers) {
try {
await handler(payload);
} catch (error) {
logger.error(`Error in event handler for ${eventType}:`, error);
errors.push(error);
}
}
// Optionally throw if all handlers failed
if (errors.length === handlers.length && handlers.length > 0) {
throw new Error(`All event handlers failed for ${eventType}`);
}
}
}
Event Monitoring
Event Metrics
class EventMetrics {
private eventCounts = new Map<string, number>();
private eventLatencies = new Map<string, number[]>();
private eventErrors = new Map<string, number>();
trackEvent(eventType: string, startTime: number, error?: Error) {
// Count events
this.eventCounts.set(eventType, (this.eventCounts.get(eventType) || 0) + 1);
// Track latency
const latency = Date.now() - startTime;
const latencies = this.eventLatencies.get(eventType) || [];
latencies.push(latency);
this.eventLatencies.set(eventType, latencies.slice(-100));
// Track errors
if (error) {
this.eventErrors.set(eventType, (this.eventErrors.get(eventType) || 0) + 1);
}
}
getMetrics() {
return {
counts: Object.fromEntries(this.eventCounts),
averageLatencies: this.calculateAverageLatencies(),
errorRates: this.calculateErrorRates(),
};
}
private calculateAverageLatencies() {
return Object.fromEntries(
Array.from(this.eventLatencies.entries()).map(([event, latencies]) => [
event,
latencies.reduce((a, b) => a + b, 0) / latencies.length,
])
);
}
private calculateErrorRates() {
return Object.fromEntries(
Array.from(this.eventErrors.entries()).map(([event, errors]) => [
event,
errors / (this.eventCounts.get(event) || 1),
])
);
}
}
Event Logging
// Enhanced event logging
const eventLogger = {
logEvent(eventType: string, payload: any, duration?: number) {
logger.info(`Event: ${eventType}`, {
eventType,
duration,
payload: this.sanitizePayload(payload),
timestamp: Date.now(),
});
},
logEventError(eventType: string, error: Error, payload: any) {
logger.error(`Event Error: ${eventType}`, {
eventType,
error: error.message,
stack: error.stack,
payload: this.sanitizePayload(payload),
timestamp: Date.now(),
});
},
sanitizePayload(payload: any) {
// Remove sensitive information
const sanitized = { ...payload };
delete sanitized.runtime;
delete sanitized.callback;
return sanitized;
},
};
Best Practices
Event Handler Design
// Use specific handler functions
const messageHandlers = {
[EventType.MESSAGE_RECEIVED]: messageReceivedHandler,
[EventType.MESSAGE_SENT]: messageSentHandler,
[EventType.MESSAGE_DELETED]: messageDeletedHandler
};
// Avoid anonymous functions in event registration
// Good
const events = {
[EventType.MESSAGE_RECEIVED]: [messageReceivedHandler]
};
// Bad - harder to debug and test
const events = {
[EventType.MESSAGE_RECEIVED]: [
async (payload) => {
// Anonymous handler logic
}
]
};
Event Payload Validation
// Validate event payloads
function validateMessagePayload(payload: MessagePayload): void {
if (!payload.runtime) {
throw new Error("Runtime is required in message payload");
}
if (!payload.message) {
throw new Error("Message is required in message payload");
}
if (!payload.message.id) {
throw new Error("Message ID is required");
}
}
// Use in handlers
const messageReceivedHandler = async (payload: MessagePayload) => {
validateMessagePayload(payload);
// Process message
};
Memory Management
// Prevent memory leaks in event handlers
class EventManager {
private handlerRefs = new Map<string, Function[]>();
registerHandler(eventType: string, handler: Function) {
const handlers = this.handlerRefs.get(eventType) || [];
handlers.push(handler);
this.handlerRefs.set(eventType, handlers);
}
cleanup() {
// Clean up all handler references
this.handlerRefs.clear();
}
}
The event system provides a robust foundation for building reactive, loosely-coupled applications in elizaOS v1.2.0, enabling complex agent behaviors through simple event handling patterns.
v1.2.0 Event System Enhancements
Domain-Based Routing
- Organized Events: Events are organized by domain (database, ai, plugin, etc.)
- Filtered Listening: Listen to specific domain events for better organization
- Namespace Isolation: Prevent event name conflicts between different domains
Enhanced Context Tracking
- Run ID Tracking: All events include run ID for better debugging
- Action Context: Events include action context when available
- Service Context: Events include service context for better monitoring
- Rich Metadata: Enhanced metadata for better observability
Improved Error Handling
- Graceful Degradation: Event handler failures don't cascade
- Error Events: Dedicated error events for better error tracking
- Retry Logic: Built-in retry mechanisms for transient failures
Performance Monitoring
- Event Metrics: Built-in metrics tracking for all events
- Performance Tracking: Latency and throughput monitoring
- Health Monitoring: Service health events for better observability
These improvements make the event system more scalable, observable, and maintainable while preserving the simplicity and flexibility that makes ElizaOS agents powerful.