Real-World Plugin and Project Patterns in Eliza
This guide documents the actual patterns and structures used in the Eliza framework based on examination of real plugin implementations and project structures.
Plugin Structure Patterns
Basic Plugin Structure
Every plugin follows this core structure (from plugin-starter
):
import type { Plugin } from '@elizaos/core';
export const myPlugin: Plugin = {
name: 'plugin-name',
description: 'Plugin description',
// Core components
actions: [], // Actions the plugin provides
providers: [], // Data providers
services: [], // Background services
evaluators: [], // Response evaluators
// Optional components
init: async (config) => {}, // Initialization logic
models: {}, // Custom model implementations
routes: [], // HTTP routes
events: {}, // Event handlers
tests: [], // Test suites
dependencies: [], // Other required plugins
};
Real Examples
Bootstrap Plugin (plugin-bootstrap
)
The most complex and comprehensive plugin that provides core functionality:
export const bootstrapPlugin: Plugin = {
name: 'bootstrap',
description: 'Agent bootstrap with basic actions and evaluators',
actions: [
actions.replyAction,
actions.followRoomAction,
actions.ignoreAction,
actions.sendMessageAction,
actions.generateImageAction,
// ... more actions
],
providers: [
providers.timeProvider,
providers.entitiesProvider,
providers.characterProvider,
providers.recentMessagesProvider,
// ... more providers
],
services: [TaskService],
evaluators: [evaluators.reflectionEvaluator],
events: {
[EventType.MESSAGE_RECEIVED]: [messageReceivedHandler],
[EventType.POST_GENERATED]: [postGeneratedHandler],
// ... more event handlers
}
};
Service Plugins (Discord, Telegram)
Platform integration plugins focus on service implementation:
// Discord Plugin
const discordPlugin: Plugin = {
name: "discord",
description: "Discord service plugin for integration with Discord servers",
services: [DiscordService],
actions: [
chatWithAttachments,
downloadMedia,
joinVoice,
leaveVoice,
summarize,
transcribeMedia,
],
providers: [channelStateProvider, voiceStateProvider],
tests: [new DiscordTestSuite()],
init: async (config, runtime) => {
// Check for required API tokens
const token = runtime.getSetting("DISCORD_API_TOKEN");
if (!token) {
logger.warn("Discord API Token not provided");
}
},
};
// Telegram Plugin (minimal)
const telegramPlugin: Plugin = {
name: TELEGRAM_SERVICE_NAME,
description: 'Telegram client plugin',
services: [TelegramService],
tests: [new TelegramTestSuite()],
};
Action Patterns
Actions follow a consistent structure with validation and execution:
const helloWorldAction: Action = {
name: 'HELLO_WORLD',
similes: ['GREET', 'SAY_HELLO'], // Alternative names
description: 'Responds with a simple hello world message',
validate: async (runtime, message, state) => {
// Return true if action can be executed
return true;
},
handler: async (runtime, message, state, options, callback, responses) => {
try {
const responseContent: Content = {
text: 'hello world!',
actions: ['HELLO_WORLD'],
source: message.content.source,
};
if (callback) {
await callback(responseContent);
}
return responseContent;
} catch (error) {
logger.error('Error in HELLO_WORLD action:', error);
throw error;
}
},
examples: [
[
{
name: '{{name1}}',
content: { text: 'Can you say hello?' }
},
{
name: '{{name2}}',
content: {
text: 'hello world!',
actions: ['HELLO_WORLD']
}
}
]
]
};
Complex Action Example (Reply Action)
export const replyAction = {
name: 'REPLY',
similes: ['GREET', 'REPLY_TO_MESSAGE', 'SEND_REPLY', 'RESPOND'],
description: 'Replies to the current conversation',
validate: async (runtime) => true,
handler: async (runtime, message, state, options, callback, responses) => {
// Compose state with providers
state = await runtime.composeState(message, ['RECENT_MESSAGES']);
// Generate response using LLM
const prompt = composePromptFromState({ state, template: replyTemplate });
const response = await runtime.useModel(ModelType.OBJECT_LARGE, { prompt });
const responseContent = {
thought: response.thought,
text: response.message || '',
actions: ['REPLY'],
};
await callback(responseContent);
return true;
}
};
Provider Patterns
Providers supply contextual data to the agent:
export const timeProvider: Provider = {
name: 'TIME',
get: async (runtime, message) => {
const currentDate = new Date();
const options = {
timeZone: 'UTC',
dateStyle: 'full' as const,
timeStyle: 'long' as const,
};
const humanReadable = new Intl.DateTimeFormat('en-US', options).format(currentDate);
return {
data: { time: currentDate },
values: { time: humanReadable },
text: `The current date and time is ${humanReadable}.`,
};
},
};
Service Patterns
Services run in the background and handle ongoing tasks:
export class TaskService extends Service {
static serviceType = ServiceType.TASK;
capabilityDescription = 'The agent is able to schedule and execute tasks';
static async start(runtime: IAgentRuntime): Promise<Service> {
const service = new TaskService(runtime);
await service.startTimer();
return service;
}
static async stop(runtime: IAgentRuntime) {
const service = runtime.getService(ServiceType.TASK);
if (service) {
await service.stop();
}
}
private async startTimer() {
this.timer = setInterval(async () => {
await this.checkTasks();
}, this.TICK_INTERVAL);
}
}
export class DiscordService extends Service implements IDiscordService {
static serviceType: string = DISCORD_SERVICE_NAME;
capabilityDescription = "The agent is able to send and receive messages on discord";
constructor(runtime: IAgentRuntime) {
super(runtime);
// Parse environment configuration
const token = runtime.getSetting("DISCORD_API_TOKEN");
if (!token) {
this.client = null;
return;
}
// Initialize Discord client
this.client = new DiscordJsClient({
intents: [/* Discord intents */],
partials: [/* Discord partials */]
});
// Set up event handlers
this.setupEventHandlers();
}
}
Project Structure Patterns
Single Agent Project
// packages/project-starter/src/index.ts
import { type IAgentRuntime, type Project, type ProjectAgent } from '@elizaos/core';
import { character } from './character.ts';
const initCharacter = ({ runtime }: { runtime: IAgentRuntime }) => {
logger.info('Initializing character');
logger.info('Name: ', character.name);
};
export const projectAgent: ProjectAgent = {
character,
init: async (runtime: IAgentRuntime) => await initCharacter({ runtime }),
// plugins: [starterPlugin], // Custom plugins here
};
const project: Project = {
agents: [projectAgent],
};
export default project;
Character Configuration
Characters define personality and plugin configuration:
export const character: Character = {
name: 'Eliza',
plugins: [
// Core plugins first
'@elizaos/plugin-sql',
// Conditional plugins based on environment
...(process.env.ANTHROPIC_API_KEY ? ['@elizaos/plugin-anthropic'] : []),
...(process.env.OPENAI_API_KEY ? ['@elizaos/plugin-openai'] : []),
...(process.env.DISCORD_API_TOKEN ? ['@elizaos/plugin-discord'] : []),
...(process.env.TELEGRAM_BOT_TOKEN ? ['@elizaos/plugin-telegram'] : []),
// Bootstrap plugin (unless explicitly disabled)
...(!process.env.IGNORE_BOOTSTRAP ? ['@elizaos/plugin-bootstrap'] : []),
],
settings: {
secrets: {},
avatar: 'https://elizaos.github.io/eliza-avatars/Eliza/portrait.png',
},
system: 'Respond to all messages in a helpful, conversational manner...',
bio: [
'Engages with all types of questions and conversations',
'Provides helpful, concise responses',
// ...
],
topics: ['general knowledge', 'problem solving', 'technology'],
messageExamples: [/* conversation examples */],
style: {
all: ['Keep responses concise', 'Use clear language'],
chat: ['Be conversational', 'Show personality'],
},
};
Plugin Registration and Initialization
Environment-Based Plugin Loading
Plugins are conditionally loaded based on environment variables:
const plugins = [
// Always loaded
'@elizaos/plugin-sql',
// Conditionally loaded based on API keys
...(process.env.ANTHROPIC_API_KEY ? ['@elizaos/plugin-anthropic'] : []),
...(process.env.OPENAI_API_KEY ? ['@elizaos/plugin-openai'] : []),
// Platform plugins
...(process.env.DISCORD_API_TOKEN ? ['@elizaos/plugin-discord'] : []),
...(process.env.TELEGRAM_BOT_TOKEN ? ['@elizaos/plugin-telegram'] : []),
];
Plugin Initialization
Plugins can have initialization logic:
const myPlugin: Plugin = {
name: 'my-plugin',
config: {
EXAMPLE_VARIABLE: process.env.EXAMPLE_VARIABLE,
},
async init(config: Record<string, string>) {
// Validate configuration
const validatedConfig = await configSchema.parseAsync(config);
// Set environment variables
for (const [key, value] of Object.entries(validatedConfig)) {
if (value) process.env[key] = value;
}
},
};
Event Handling Patterns
Plugins can handle various system events:
const events = {
[EventType.MESSAGE_RECEIVED]: [
async (payload: MessagePayload) => {
await messageReceivedHandler({
runtime: payload.runtime,
message: payload.message,
callback: payload.callback,
});
},
],
[EventType.WORLD_JOINED]: [
async (payload: WorldPayload) => {
await handleServerSync(payload);
},
],
[EventType.ENTITY_JOINED]: [
async (payload: EntityPayload) => {
await syncSingleUser(/* params */);
},
],
};
Multi-Agent Projects
While the examples show single-agent projects, the structure supports multiple agents:
const project: Project = {
agents: [
{
character: elizaCharacter,
init: async (runtime) => { /* Eliza init */ },
plugins: [/* Eliza plugins */],
},
{
character: assistantCharacter,
init: async (runtime) => { /* Assistant init */ },
plugins: [/* Assistant plugins */],
},
],
};
Environment Variable Usage
Common environment variables used by plugins:
# AI Model Providers
OPENAI_API_KEY=
ANTHROPIC_API_KEY=
GOOGLE_GENERATIVE_AI_API_KEY=
OLLAMA_API_ENDPOINT=
# Platform Integrations
DISCORD_API_TOKEN=
TELEGRAM_BOT_TOKEN=
TWITTER_API_KEY=
TWITTER_API_SECRET_KEY=
TWITTER_ACCESS_TOKEN=
TWITTER_ACCESS_TOKEN_SECRET=
# Plugin Control
IGNORE_BOOTSTRAP= # Skip bootstrap plugin
CHANNEL_IDS= # Restrict Discord to specific channels
# Database
POSTGRES_URL=
PGLITE_DATA_DIR=
Best Practices
- Plugin Dependencies: Use the
dependencies
array to specify required plugins
- Conditional Loading: Check environment variables before loading platform-specific plugins
- Service Initialization: Handle missing API tokens gracefully in service constructors
- Event Handlers: Keep event handlers focused and delegate to specialized functions
- Provider Data: Return structured data with
data
, values
, and text
fields
- Action Validation: Always implement validation logic before execution
- Error Handling: Use try-catch blocks and log errors appropriately
- Type Safety: Use TypeScript types from
@elizaos/core
for all plugin components