Files
wehub-resource-sync 426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

992 lines
25 KiB
Plaintext

---
title: "Patterns"
description: "Battle-tested patterns for building robust plugins"
---
## Real Patterns from Real Plugins
These aren't theoretical - they're extracted from production plugins. Each pattern solves a specific problem you'll encounter when building agents that actually work.
<CardGroup cols={2}>
<Card title="Action Chaining" icon="link">
Multi-step workflows where actions build on each other
</Card>
<Card title="Callbacks" icon="reply">
Send progress updates before actions complete
</Card>
<Card title="State Composition" icon="layer-group">
Combine multiple providers elegantly
</Card>
<Card title="Error Recovery" icon="rotate-right">
Graceful degradation and retry strategies
</Card>
</CardGroup>
## Action Chaining and Callbacks
Action chaining allows multiple actions to execute sequentially, with each action accessing previous results. This enables complex workflows where actions build upon each other's outputs.
### The ActionResult Interface
Actions return an `ActionResult` object that standardizes how actions communicate their outcomes. This interface includes:
- **success** (required): Boolean indicating whether the action completed successfully
- **text**: Optional human-readable description of the result
- **values**: Key-value pairs to merge into the state for subsequent actions
- **data**: Raw data payload with action-specific results
- **error**: Error information if the action failed
The `success` field is the only required field, making it easy to create simple results while supporting complex data passing for action chaining.
For interface definitions, see [Plugin Reference](/plugins/patterns#the-actionresult-interface). For component basics, see [Plugin Components](/plugins/architecture).
### Handler Callbacks
The `HandlerCallback` provides a mechanism for actions to send immediate feedback to users before the action completes:
```typescript
export type HandlerCallback = (
response: Content,
files?: Attachment[],
) => Promise<Memory[]>;
```
Example usage:
```typescript
async handler(
runtime: IAgentRuntime,
message: Memory,
_state?: State,
_options?: Record<string, unknown>,
callback?: HandlerCallback
): Promise<ActionResult> {
try {
// Send immediate feedback
await callback?.({
text: `Starting to process your request...`,
source: message.content.source
});
// Perform action logic
const result = await performComplexOperation();
// Send success message to user via callback
await callback?.({
text: `Created issue: ${result.title} (${result.identifier})\n\nView it at: ${result.url}`,
source: message.content.source
});
// Return structured result for potential chaining
return {
success: true,
text: `Created issue: ${result.title}`,
data: {
issueId: result.id,
identifier: result.identifier,
url: result.url
}
};
} catch (error) {
// Send error message to user
await callback?.({
text: `Failed to create issue: ${error.message}`,
source: message.content.source
});
return {
success: false,
text: `Failed to create issue: ${error.message}`,
error: error instanceof Error ? error : new Error(String(error))
};
}
}
```
### Action Context and Previous Results
When multiple actions are executed in sequence, each action receives an `ActionContext` that provides access to previous action results:
```typescript
export interface ActionContext {
/** Results from previously executed actions in this run */
previousResults: ActionResult[];
/** Get a specific previous result by action name */
getPreviousResult?: (actionName: string) => ActionResult | undefined;
}
```
The runtime automatically provides this context in the `options` parameter:
```typescript
async handler(
runtime: IAgentRuntime,
message: Memory,
state?: State,
options?: Record<string, unknown>,
callback?: HandlerCallback
): Promise<ActionResult> {
// Access the action context
const context = options?.context as ActionContext;
// Get results from a specific previous action
const previousResult = context?.getPreviousResult?.('CREATE_LINEAR_ISSUE');
if (previousResult?.data?.issueId) {
// Use data from previous action
const issueId = previousResult.data.issueId;
// ... continue with logic using previous result ...
}
}
```
### Action Execution Flow
The planned-tool executor manages the execution flow:
1. **Action Planning**: When multiple actions are detected, the runtime creates an execution plan
2. **Sequential Execution**: Actions execute in the order specified by the agent
3. **State Accumulation**: Each action's results are merged into the accumulated state
4. **Working Memory**: Results are stored in working memory for access during execution
5. **Error Handling**: Failed actions don't stop the chain unless marked as critical
### Working Memory Management
The runtime maintains a working memory that stores recent action results:
```typescript
// Results are automatically stored in state.data.workingMemory
const memoryEntry: WorkingMemoryEntry = {
actionName: action.name,
result: actionResult,
timestamp: Date.now(),
};
```
The system keeps the most recent 50 entries (configurable) to prevent memory bloat.
## Action Patterns
### Decision-Making Actions
Actions can use the LLM to make intelligent decisions based on context:
```typescript
export const muteRoomAction: Action = {
name: "MUTE_ROOM",
similes: ["SHUT_UP", "BE_QUIET", "STOP_TALKING", "SILENCE"],
description: "Mutes a room if asked to or if the agent is being annoying",
validate: async (runtime, message) => {
// Check if already muted
const roomState = await runtime.getParticipantUserState(
message.roomId,
runtime.agentId,
);
return roomState !== "MUTED";
},
handler: async (runtime, message, state) => {
// Create a decision prompt
const shouldMuteTemplate = `# Task: Should {{agentName}} mute this room?
{{recentMessages}}
Should {{agentName}} mute and stop responding unless mentioned?
Respond YES if:
- User asked to stop/be quiet
- Agent responses are annoying users
- Conversation is hostile
Otherwise NO.`;
const prompt = composePromptFromState({
state,
template: shouldMuteTemplate,
});
const decision = await runtime.useModel(ModelType.TEXT_SMALL, {
prompt,
runtime,
});
if (decision.toLowerCase().includes("yes")) {
await runtime.setParticipantUserState(
message.roomId,
runtime.agentId,
"MUTED",
);
return {
success: true,
text: "Going silent in this room",
values: { roomMuted: true },
};
}
return {
success: false,
text: "Continuing to participate",
};
},
};
```
### Multi-Step Actions
Actions that need to perform multiple steps with intermediate feedback:
```typescript
export const deployContractAction: Action = {
name: "DEPLOY_CONTRACT",
description: "Deploy a smart contract with multiple steps",
handler: async (runtime, message, state, options, callback) => {
try {
// Step 1: Compile
await callback?.({
text: "📝 Compiling contract...",
actions: ["DEPLOY_CONTRACT"],
});
const compiled = await compileContract(state.contractCode);
// Step 2: Estimate gas
await callback?.({
text: "⛽ Estimating gas costs...",
actions: ["DEPLOY_CONTRACT"],
});
const gasEstimate = await estimateGas(compiled);
// Step 3: Deploy
await callback?.({
text: `🚀 Deploying with gas: ${gasEstimate}...`,
actions: ["DEPLOY_CONTRACT"],
});
const deployed = await deploy(compiled, gasEstimate);
// Step 4: Verify
await callback?.({
text: "✅ Verifying deployment...",
actions: ["DEPLOY_CONTRACT"],
});
await verifyContract(deployed.address);
return {
success: true,
text: `Contract deployed at ${deployed.address}`,
values: {
contractAddress: deployed.address,
transactionHash: deployed.txHash,
gasUsed: deployed.gasUsed,
},
data: {
deployment: deployed,
verification: true,
},
};
} catch (error) {
return {
success: false,
text: `Deployment failed: ${error.message}`,
error,
};
}
},
};
```
### API Integration Actions
Pattern for external API calls with retries and error handling:
```typescript
export const apiAction: Action = {
name: "API_CALL",
handler: async (runtime, message, state, options, callback) => {
const maxRetries = 3;
let lastError: Error | null = null;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
await callback?.({
text: `Attempt ${attempt}/${maxRetries}...`,
});
const result = await callExternalAPI({
endpoint: state.endpoint,
data: state.data,
timeout: 5000,
});
return {
success: true,
text: "API call successful",
data: result,
};
} catch (error) {
lastError = error as Error;
if (attempt < maxRetries) {
await callback?.({
text: `Attempt ${attempt} failed, retrying...`,
});
await new Promise((r) => setTimeout(r, 1000 * attempt)); // Exponential backoff
}
}
}
return {
success: false,
text: `API call failed after ${maxRetries} attempts`,
error: lastError,
};
},
};
```
### Context-Aware Actions
Actions that adapt based on conversation context:
```typescript
export const contextAwareAction: Action = {
name: "CONTEXT_RESPONSE",
handler: async (runtime, message, state, options, callback) => {
// Analyze conversation sentiment
const sentiment = await analyzeSentiment(state.recentMessages);
// Adjust response based on context
let responseStrategy: string;
if (sentiment.score < -0.5) {
responseStrategy = "empathetic";
} else if (sentiment.score > 0.5) {
responseStrategy = "enthusiastic";
} else {
responseStrategy = "neutral";
}
// Generate context-appropriate response
const response = await generateResponse(state, responseStrategy, runtime);
return {
success: true,
text: response.text,
values: {
sentiment: sentiment.score,
strategy: responseStrategy,
},
};
},
};
```
## Action Composition
### Compose Multiple Actions
```typescript
// Compose multiple actions into higher-level operations
export const compositeAction: Action = {
name: "SEND_AND_TRACK",
description: "Send a message and track its delivery",
handler: async (runtime, message, state, options, callback) => {
// Execute sub-actions
const sendResult = await sendMessageAction.handler(
runtime,
message,
state,
options,
callback,
);
if (!sendResult.success) {
return sendResult; // Propagate failure
}
// Track the sent message
const trackingId = generateTrackingId();
await runtime.createMemory(
{
id: trackingId,
entityId: message.entityId,
roomId: message.roomId,
content: {
type: "message_tracking",
sentTo: sendResult.data.target,
sentAt: Date.now(),
messageContent: sendResult.data.messageContent,
},
},
"tracking",
);
return {
success: true,
text: `Message sent and tracked (${trackingId})`,
values: {
...sendResult.values,
trackingId,
tracked: true,
},
data: {
sendResult,
trackingId,
},
};
},
};
```
### Workflow Orchestration
```typescript
export const workflowAction: Action = {
name: "COMPLEX_WORKFLOW",
handler: async (runtime, message, state, options, callback) => {
const workflow = [
{ action: "VALIDATE_INPUT", required: true },
{ action: "FETCH_DATA", required: true },
{ action: "PROCESS_DATA", required: false },
{ action: "STORE_RESULTS", required: true },
{ action: "NOTIFY_USER", required: false },
];
const results: ActionResult[] = [];
for (const step of workflow) {
const action = runtime.getAction(step.action);
if (!action) {
if (step.required) {
return {
success: false,
text: `Required action ${step.action} not found`,
};
}
continue;
}
const result = await action.handler(
runtime,
message,
state,
{ context: { previousResults: results } },
callback,
);
results.push(result);
if (!result.success && step.required) {
return {
success: false,
text: `Workflow failed at ${step.action}`,
data: { failedStep: step.action, results },
};
}
// Merge values into state for next action
state = {
...state,
values: {
...state.values,
...result.values,
},
};
}
return {
success: true,
text: "Workflow completed successfully",
data: { workflowResults: results },
};
},
};
```
## Self-Modifying Actions
Actions that learn and adapt their behavior:
```typescript
export const learningAction: Action = {
name: "ADAPTIVE_RESPONSE",
handler: async (runtime, message, state) => {
// Retrieve past performance
const history = await runtime.getMemories({
tableName: "action_feedback",
roomId: message.roomId,
count: 100,
});
// Analyze what worked well
const analysis = await runtime.useModel(ModelType.TEXT_LARGE, {
prompt: `Analyze these past interactions and identify patterns:
${JSON.stringify(history)}
What response strategies were most effective?`,
});
// Adapt behavior based on learning
const strategy = determineStrategy(analysis);
const response = await generateResponse(state, strategy);
// Store for future learning
await runtime.createMemory(
{
id: generateId(),
content: {
type: "action_feedback",
strategy: strategy.name,
context: state.text,
response: response.text,
},
},
"action_feedback",
);
return {
success: true,
text: response.text,
values: {
strategyUsed: strategy.name,
confidence: strategy.confidence,
},
};
},
};
```
## Provider Patterns
### Conditional Providers
Providers that only provide data under certain conditions:
```typescript
export const conditionalProvider: Provider = {
name: "PREMIUM_DATA",
private: true,
get: async (runtime, message, state) => {
// Check if user has premium access
const user = await runtime.getUser(message.entityId);
if (!user.isPremium) {
return {
text: "",
values: {},
data: { available: false },
};
}
// Provide premium data
const premiumData = await fetchPremiumData(user);
return {
text: formatPremiumData(premiumData),
values: premiumData,
data: { available: true, ...premiumData },
};
},
};
```
### Aggregating Providers
Providers that combine data from multiple sources:
```typescript
export const aggregateProvider: Provider = {
name: "MARKET_OVERVIEW",
position: 50, // Run after individual data providers
get: async (runtime, message, state) => {
// Aggregate from multiple sources
const [stocks, crypto, forex] = await Promise.all([
fetchStockData(),
fetchCryptoData(),
fetchForexData(),
]);
const overview = {
stocksUp: stocks.filter((s) => s.change > 0).length,
stocksDown: stocks.filter((s) => s.change < 0).length,
cryptoMarketCap: crypto.reduce((sum, c) => sum + c.marketCap, 0),
forexVolatility: calculateVolatility(forex),
};
return {
text: `Market Overview:
- Stocks: ${overview.stocksUp} up, ${overview.stocksDown} down
- Crypto Market Cap: $${overview.cryptoMarketCap.toLocaleString()}
- Forex Volatility: ${overview.forexVolatility}`,
values: overview,
data: { stocks, crypto, forex },
};
},
};
```
## Best Practices for Action Chaining
1. **Always Return ActionResult**: Even for simple actions, return a proper `ActionResult` object:
```typescript
return {
success: true,
text: "Action completed",
data: {
/* any data for next actions */
},
};
```
2. **Use Callbacks for User Feedback**: Send immediate feedback via callbacks rather than waiting for the action to complete:
```typescript
await callback?.({
text: "Processing your request...",
source: message.content.source,
});
```
3. **Store Identifiers in Data**: When creating resources, store identifiers that subsequent actions might need:
```typescript
return {
success: true,
data: {
resourceId: created.id,
resourceUrl: created.url,
},
};
```
4. **Handle Missing Dependencies**: Check if required previous results exist:
```typescript
const previousResult = context?.getPreviousResult?.("REQUIRED_ACTION");
if (!previousResult?.success) {
return {
success: false,
text: "Required previous action did not complete successfully",
};
}
```
5. **Use ActionResult**: All actions must return `ActionResult` with a `success` field.
## Example: Multi-Step Workflow
Here's an example of a multi-step workflow using action chaining:
```typescript
// User: "Create a bug report for the login issue and assign it to John"
// Agent executes: REPLY, CREATE_LINEAR_ISSUE, UPDATE_LINEAR_ISSUE
// Action 1: CREATE_LINEAR_ISSUE
{
success: true,
data: {
issueId: "abc-123",
identifier: "BUG-456"
}
}
// Action 2: UPDATE_LINEAR_ISSUE (can access previous result)
async handler(runtime, message, state, options, callback) {
const context = options?.context as ActionContext;
const createResult = context?.getPreviousResult?.('CREATE_LINEAR_ISSUE');
if (createResult?.data?.issueId) {
// Use the issue ID from previous action
await updateIssue(createResult.data.issueId, { assignee: "John" });
return {
success: true,
text: "Issue assigned to John"
};
}
}
```
## Common Patterns
1. **Create and Configure**: Create a resource, then configure it
2. **Search and Update**: Find resources, then modify them
3. **Validate and Execute**: Check conditions, then perform actions
4. **Aggregate and Report**: Collect data from multiple sources, then summarize
The action chaining system provides a powerful way to build complex, multi-step workflows while maintaining clean separation between individual actions.
## Real-World Implementation Patterns
This section documents actual patterns and structures used in production elizaOS plugins based on examination of real implementations.
### Basic Plugin Structure Pattern
Every plugin follows this core structure pattern (from `plugin-starter`):
```typescript
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
};
```
### BasicCapabilities Plugin Pattern
The basic-capabilities plugin is built into `@elizaos/core` and automatically registered during runtime initialization. It provides core functionality through:
```typescript
// The basic-capabilities plugin is created internally using createBasicCapabilitiesPlugin()
// It provides:
const basicCapabilities = {
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.factMemoryEvaluator,
evaluators.relationshipEvaluator,
evaluators.identityEvaluator,
evaluators.successEvaluator,
],
events: {
[EventType.MESSAGE_RECEIVED]: [messageReceivedHandler],
[EventType.POST_GENERATED]: [postGeneratedHandler],
// ... more event handlers
},
};
```
<Note>
You don't need to import or configure the basic-capabilities plugin - it's automatically included when you create an AgentRuntime.
</Note>
### Service Plugin Pattern (Discord, Telegram)
Platform integration plugins focus on service implementation:
```typescript
// 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 Implementation Pattern
Actions follow a consistent structure with validation and execution:
```typescript
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 Pattern (Reply Action)
```typescript
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 Implementation Pattern
Providers supply contextual data to the agent:
```typescript
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}.`,
};
},
};
```
### Plugin Initialization Pattern
Plugins can have initialization logic:
```typescript
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;
}
},
};
```
<Tip>
**Guides**: [Create a Plugin](/plugins/create-a-plugin) | [Publish a
Plugin](/plugins/publish)
</Tip>
## See Also
<CardGroup cols={2}>
<Card title="Plugin Architecture" icon="sitemap" href="/plugins/architecture">
Understand the overall plugin system design
</Card>
<Card title="Development Guide" icon="code" href="/plugins/development">
Build your first plugin with step-by-step guidance
</Card>
<Card title="Plugin Schemas" icon="list" href="/plugins/schemas">
Learn about configuration and validation schemas
</Card>
<Card title="Plugin Migration" icon="arrow-right" href="/plugins/plugin-eject">
Migrate existing plugins to new patterns
</Card>
</CardGroup>