> ## Documentation Index
> Fetch the complete documentation index at: https://docs.elizaos.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Patterns

> 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/reference#action-interface). For component basics, see [Plugin Components](/plugins/components).

### Handler Callbacks

The `HandlerCallback` provides a mechanism for actions to send immediate feedback to users before the action completes:

```typescript theme={null}
export type HandlerCallback = (response: Content, files?: any) => Promise<Memory[]>;
```

Example usage:

```typescript theme={null}
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 theme={null}
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 theme={null}
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 runtime's `processActions` method 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 theme={null}
// 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 theme={null}
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 theme={null}
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 theme={null}
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 theme={null}
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 theme={null}
// 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.targetId,
        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 theme={null}
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 theme={null}
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 theme={null}
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 theme={null}
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 theme={null}
   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 theme={null}
   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 theme={null}
   return {
     success: true,
     data: {
       resourceId: created.id,
       resourceUrl: created.url
     }
   };
   ```

4. **Handle Missing Dependencies**: Check if required previous results exist:
   ```typescript theme={null}
   const previousResult = context?.getPreviousResult?.('REQUIRED_ACTION');
   if (!previousResult?.success) {
     return {
       success: false,
       text: "Required previous action did not complete successfully"
     };
   }
   ```

5. **Maintain Backward Compatibility**: The runtime handles legacy action returns (void, boolean) but new actions should use `ActionResult`.

## Example: Multi-Step Workflow

Here's an example of a multi-step workflow using action chaining:

```typescript theme={null}
// 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 theme={null}
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
};
```

### Bootstrap Plugin Pattern

The most complex and comprehensive plugin that provides core functionality:

```typescript theme={null}
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 Plugin Pattern (Discord, Telegram)

Platform integration plugins focus on service implementation:

```typescript theme={null}
// 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 theme={null}
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 theme={null}
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 theme={null}
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 theme={null}
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](/guides/create-a-plugin) | [Publish a Plugin](/guides/publish-a-plugin)
</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/migration">
    Migrate existing plugins to new patterns
  </Card>
</CardGroup>
