This guide provides comprehensive documentation on the composeState
method in ElizaOS and how it interacts with different types of providers to build contextual state for agent decision-making.
Introduction
The composeState
method is a core function in ElizaOS that aggregates data from multiple providers to create a comprehensive state object. This state represents the agent’s understanding of the current context and is used for decision-making, action selection, and response generation.
What is State?
State in ElizaOS is a structured object containing:
- values: Key-value pairs for direct access (used in templates)
- data: Structured data from providers
- text: Concatenated textual context from all providers
Quick Reference
Provider Summary Table
Provider Name | Dynamic | Position | Default Included | Purpose |
---|
ACTIONS | No | -1 | Yes | Lists available actions |
ACTION_STATE | No | 150 | Yes | Action execution state |
ANXIETY | No | Default | Yes | Response style guidelines |
ATTACHMENTS | Yes | Default | No | File/media attachments |
CAPABILITIES | No | Default | Yes | Service capabilities |
CHARACTER | No | Default | Yes | Agent personality |
CHOICE | No | Default | Yes | Pending user choices |
ENTITIES | Yes | Default | No | Conversation participants |
EVALUATORS | No | Default | No (private) | Post-processing options |
FACTS | Yes | Default | No | Stored knowledge |
PROVIDERS | No | Default | Yes | Available providers list |
RECENT_MESSAGES | No | 100 | Yes | Conversation history |
RELATIONSHIPS | Yes | Default | No | Social connections |
ROLES | No | Default | Yes | Server roles (groups only) |
SETTINGS | No | Default | Yes | Configuration state |
TIME | No | Default | Yes | Current UTC time |
WORLD | Yes | Default | No | Server/world context |
Common Usage Patterns
// Default state (all non-dynamic, non-private providers)
const state = await runtime.composeState(message);
// Include specific dynamic providers
const state = await runtime.composeState(message, ['FACTS', 'ENTITIES']);
// Only specific providers
const state = await runtime.composeState(message, ['CHARACTER'], true);
// Force fresh data (skip cache)
const state = await runtime.composeState(message, null, false, true);
Understanding composeState
Method Signature
async composeState(
message: Memory,
includeList: string[] | null = null,
onlyInclude = false,
skipCache = false
): Promise<State>
Parameters
- message: The current message/memory object being processed
- includeList: Array of provider names to include (optional)
- onlyInclude: If true, ONLY include providers from includeList
- skipCache: If true, bypass cache and fetch fresh data
How It Works
- Provider Selection: Determines which providers to run based on filters
- Parallel Execution: Runs all selected providers concurrently
- Result Aggregation: Combines results from all providers
- Caching: Stores the composed state for reuse
Provider Types
ElizaOS includes various built-in providers, each serving a specific purpose:
Core Providers
1. Character Provider
Provides character information, personality, and behavior guidelines.
// Included data:
{
values: {
agentName: "Alice",
bio: "AI assistant focused on...",
topics: "technology, science, education",
adjective: "helpful"
},
data: { character: {...} },
text: "# About Alice\n..."
}
2. Recent Messages Provider
Provides conversation history and context.
// Included data:
{
values: {
recentMessages: "User: Hello\nAlice: Hi there!",
recentInteractions: "..."
},
data: {
recentMessages: [...],
actionResults: [...]
},
text: "# Conversation Messages\n..."
}
3. Actions Provider
Lists available actions the agent can take.
// Included data:
{
values: {
actionNames: "Possible response actions: 'SEND_MESSAGE', 'SEARCH', 'CALCULATE'",
actionExamples: "..."
},
data: { actionsData: [...] },
text: "# Available Actions\n..."
}
Dynamic Providers
Dynamic providers are only executed when explicitly requested:
- Facts Provider: Retrieves relevant facts from memory
- Relationships Provider: Gets relationship information
- Settings Provider: Fetches configuration settings
- Roles Provider: Server role hierarchy
Private Providers
Private providers are internal and not included in default state composition:
- Evaluators Provider: Post-interaction processing options
Detailed Provider Reference
Built-in Providers
Actions Provider (ACTIONS
)
- Purpose: Lists all available actions the agent can execute
- Position: -1 (runs early)
- Dynamic: No (included by default)
- Data Provided:
actionNames
: Comma-separated list of action names
actionsWithDescriptions
: Formatted action details
actionExamples
: Example usage for each action
actionsData
: Raw action objects
Action State Provider (ACTION_STATE
)
- Purpose: Shares execution state between chained actions
- Position: 150
- Dynamic: No (included by default)
- Data Provided:
actionResults
: Previous action execution results
actionPlan
: Multi-step action execution plan
workingMemory
: Temporary data shared between actions
recentActionMemories
: Historical action executions
Anxiety Provider (ANXIETY
)
- Purpose: Provides behavioral guidelines based on channel type
- Position: Default
- Dynamic: No (included by default)
- Behavior: Adjusts response style for DMs, groups, and voice channels
Attachments Provider (ATTACHMENTS
)
- Purpose: Lists files and media in the conversation
- Position: Default
- Dynamic: Yes (must be explicitly included)
- Data Provided:
- File names, URLs, descriptions
- Text content from attachments
- Media metadata
Capabilities Provider (CAPABILITIES
)
- Purpose: Lists agent’s available services and capabilities
- Position: Default
- Dynamic: No (included by default)
- Data Provided: Service descriptions and available functions
Character Provider (CHARACTER
)
- Purpose: Core personality and behavior definition
- Position: Default
- Dynamic: No (included by default)
- Data Provided:
agentName
: Character name
bio
: Character background
topics
: Current interests
adjective
: Current mood/state
directions
: Style guidelines
examples
: Example conversations/posts
Choice Provider (CHOICE
)
- Purpose: Lists pending decisions awaiting user input
- Position: Default
- Dynamic: No (included by default)
- Use Case: Interactive workflows requiring user selection
Entities Provider (ENTITIES
)
- Purpose: Information about conversation participants
- Position: Default
- Dynamic: Yes (must be explicitly included)
- Data Provided:
- User names and aliases
- Entity metadata
- Sender identification
Facts Provider (FACTS
)
- Purpose: Retrieves relevant stored facts
- Position: Default
- Dynamic: Yes (must be explicitly included)
- Behavior: Uses embedding search to find contextually relevant facts
Providers Provider (PROVIDERS
)
- Purpose: Meta-provider listing all available providers
- Position: Default
- Dynamic: No (included by default)
- Use Case: Dynamic provider discovery
Recent Messages Provider (RECENT_MESSAGES
)
- Purpose: Conversation history and context
- Position: 100 (runs later to access other data)
- Dynamic: No (included by default)
- Data Provided:
- Recent dialogue messages
- Action execution history
- Formatted conversation context
Relationships Provider (RELATIONSHIPS
)
- Purpose: Social graph and interaction history
- Position: Default
- Dynamic: Yes (must be explicitly included)
- Data Provided:
- Known entities and their relationships
- Interaction frequency
- Relationship metadata
Roles Provider (ROLES
)
- Purpose: Server/group role hierarchy
- Position: Default
- Dynamic: No (included by default)
- Restrictions: Only available in group channels
Settings Provider (SETTINGS
)
- Purpose: Configuration and onboarding state
- Position: Default
- Dynamic: No (included by default)
- Behavior: Different output for DMs (onboarding) vs groups
Time Provider (TIME
)
- Purpose: Current time and timezone information
- Position: Default
- Dynamic: No (included by default)
- Data Provided: Formatted timestamps and timezone data
World Provider (WORLD
)
- Purpose: Virtual world/server context
- Position: Default
- Dynamic: No (included by default)
- Data Provided: World metadata and configuration
Cache Management
How Caching Works
The composeState
method uses an in-memory cache (stateCache
) to store composed states:
// Cache is stored by message ID
this.stateCache.set(message.id, newState);
Getting Cached Data
// Inside a runtime context (this.stateCache is a Map<UUID, State>)
// The cache is internal to the runtime and accessed via composeState with skipCache parameter
// To get fresh data, skip the cache:
const freshState = await runtime.composeState(message, null, false, true); // skipCache = true
// To use cached data (default behavior):
const cachedState = await runtime.composeState(message); // uses cache if available
Clearing Cache
import { AgentRuntime } from '@elizaos/core';
// The stateCache is internal to the runtime instance
// In a custom runtime extension or plugin initialization:
class ExtendedRuntime extends AgentRuntime {
clearOldStateCache() {
const oneHourAgo = Date.now() - 60 * 60 * 1000;
for (const [messageId, state] of this.stateCache.entries()) {
// Check if we have the message in memory
this.getMemoryById(messageId).then((memory) => {
if (memory && memory.createdAt < oneHourAgo) {
this.stateCache.delete(messageId);
}
});
}
}
// Clear entire cache
clearAllStateCache() {
this.stateCache.clear();
}
}
Usage Scenarios
Scenario 1: Default State Composition
Most common usage - compose state with all non-private, non-dynamic providers:
// Default usage - includes all standard providers
const state = await runtime.composeState(message);
// Access composed data
console.log(state.values.agentName); // Character name
console.log(state.values.recentMessages); // Recent conversation
console.log(state.values.actionNames); // Available actions
Scenario 2: Including Dynamic Providers
Include specific dynamic providers for additional context:
// Include facts and relationships providers
const state = await runtime.composeState(
message,
['FACTS', 'RELATIONSHIPS'], // Include these dynamic providers
false, // Don't exclude other providers
false // Use cache if available
);
// Access additional data
console.log(state.values.facts); // Relevant facts
console.log(state.values.relationships); // Relationship data
Scenario 3: Selective Provider Inclusion
Only include specific providers for focused processing:
// Only get character and recent messages
const state = await runtime.composeState(
message,
['CHARACTER', 'RECENT_MESSAGES'], // Only these providers
true, // onlyInclude = true
false // Use cache
);
// State will only contain data from specified providers
Scenario 4: Force Fresh Data
Skip cache for real-time data requirements:
// Force fresh data fetch
const state = await runtime.composeState(
message,
null, // Include default providers
false, // Don't limit to includeList
true // skipCache = true
);
Examples
Example 1: Action Processing with State
import type { IAgentRuntime, Memory, State } from '@elizaos/core';
// In an action handler
async function handleAction(runtime: IAgentRuntime, message: Memory) {
// Compose state with action-specific providers
const state = await runtime.composeState(
message,
['CHARACTER', 'RECENT_MESSAGES', 'ACTION_STATE'],
false,
false
);
// Use state for decision making
if (state.values.hasActionResults) {
console.log('Previous actions completed:', state.values.completedActions);
}
// Process action based on state
// Actions are typically processed through runtime.processActions
// but can be executed directly if needed
const action = runtime.actions.find((a) => a.name === 'SEND_MESSAGE');
if (action && action.handler) {
const result = await action.handler(runtime, message, state);
return result;
}
}
Example 2: Settings-Based Configuration
// Check settings in DM for configuration
async function checkConfiguration(runtime: IAgentRuntime, message: Memory) {
// Include settings provider for DM configuration
const state = await runtime.composeState(
message,
['SETTINGS', 'CHARACTER'],
false,
true // Skip cache for fresh settings
);
// Helper function to check if all required settings are configured
const hasRequiredSettings = (settings: any) => {
if (!settings) return false;
// Check if all required settings have values
return Object.entries(settings).every(([key, setting]: [string, any]) => {
if (setting.required) {
return setting.value !== null && setting.value !== undefined;
}
return true;
});
};
// Check if configuration is needed
const settings = state.data.providers?.SETTINGS?.data?.settings;
if (settings && hasRequiredSettings(settings)) {
return 'Configuration complete!';
} else {
return "Let's configure your settings...";
}
}
Example 3: Context-Aware Response Generation
import { ModelType } from '@elizaos/core';
// Generate response with full context
async function generateContextualResponse(runtime: IAgentRuntime, message: Memory) {
// Get comprehensive state
const state = await runtime.composeState(
message,
['CHARACTER', 'RECENT_MESSAGES', 'FACTS', 'ENTITIES', 'ATTACHMENTS'],
false,
false
);
// Build prompt from state
const prompt = `
${state.values.bio}
${state.values.recentMessages}
Known facts:
${state.values.facts || 'No relevant facts'}
People in conversation:
${state.values.entities || 'Just you and me'}
Respond to the last message appropriately.
`;
// Generate response using composed context
const response = await runtime.useModel(ModelType.TEXT_LARGE, {
prompt,
maxTokens: 500,
});
return response;
}
Example 4: Custom Provider Integration
import type { Provider } from '@elizaos/core';
// Define a custom provider
const customDataProvider: Provider = {
name: 'CUSTOM_DATA',
description: 'Custom data from external source',
dynamic: true,
get: async (runtime, message) => {
try {
// Example: fetch data from a service or database
const customData = await runtime.getService('customService')?.getData();
if (!customData) {
return { values: {}, data: {}, text: '' };
}
return {
values: { customData: customData.summary },
data: { customData },
text: `Custom data: ${customData.summary}`,
};
} catch (error) {
runtime.logger.error('Error in custom provider:', error);
return { values: {}, data: {}, text: '' };
}
},
};
// Register the provider
runtime.registerProvider(customDataProvider);
// Use in state composition
const state = await runtime.composeState(
message,
['CHARACTER', 'CUSTOM_DATA'], // Include custom provider
false,
true
);
console.log(state.values.customData); // Access custom data value
// Monitor provider performance
async function composeStateWithMetrics(runtime: IAgentRuntime, message: Memory) {
const startTime = Date.now();
// Compose state
const state = await runtime.composeState(message);
// Check individual provider timings
const providers = state.data.providers;
for (const [name, result] of Object.entries(providers)) {
console.log(`Provider ${name} data size:`, JSON.stringify(result).length);
}
const totalTime = Date.now() - startTime;
console.log(`Total composition time: ${totalTime}ms`);
return state;
}
Advanced Examples
Multi-Step Action Coordination
import type { IAgentRuntime, Memory, State } from '@elizaos/core';
// Example ActionPlan type
interface ActionPlan {
thought: string;
steps: Array<{
actionName: string;
params?: any;
}>;
totalSteps: number;
}
// Coordinate multiple actions with shared state
async function executeMultiStepPlan(runtime: IAgentRuntime, message: Memory, plan: ActionPlan) {
const workingMemory: { [key: string]: any } = {};
for (let i = 0; i < plan.steps.length; i++) {
// Update state with working memory
const state = await runtime.composeState(
message,
['CHARACTER', 'ACTION_STATE', 'RECENT_MESSAGES'],
false,
true // Fresh state for each step
);
// Inject working memory into state
state.data.workingMemory = workingMemory;
state.data.actionPlan = {
...plan,
currentStep: i + 1,
};
// Execute action through runtime's processActions
const action = runtime.actions.find((a) => a.name === plan.steps[i].actionName);
if (action && action.handler) {
const result = await action.handler(runtime, message, state);
// Store result in working memory
workingMemory[`step_${i + 1}_result`] = result;
}
}
return workingMemory;
}
Provider Dependency Management
// Provider that depends on other providers
const enhancedFactsProvider: Provider = {
name: 'ENHANCED_FACTS',
description: 'Facts with relationship context',
dynamic: true,
position: 200, // Run after other providers
get: async (runtime, message, state) => {
// First ensure we have entities and relationships
const enhancedState = await runtime.composeState(
message,
['ENTITIES', 'RELATIONSHIPS', 'FACTS'],
false,
false
);
// Enhance facts with relationship context
const facts = enhancedState.data.providers?.FACTS?.data?.facts || [];
const relationships = enhancedState.data.providers?.RELATIONSHIPS?.data?.relationships || [];
// Helper function to find related entities
const findRelatedEntities = (fact: any, relationships: any[]) => {
// Simple implementation - match entities mentioned in fact text
return relationships.filter((rel) => fact.content?.text?.includes(rel.targetEntityId));
};
const formatEnhancedFacts = (facts: any[]) => {
return facts
.map(
(fact) => `${fact.content?.text} [Related: ${fact.relatedEntities?.length || 0} entities]`
)
.join('\n');
};
const enhancedFacts = facts.map((fact) => ({
...fact,
relatedEntities: findRelatedEntities(fact, relationships),
}));
return {
values: { enhancedFacts: formatEnhancedFacts(enhancedFacts) },
data: { enhancedFacts },
text: `Enhanced facts with relationships:\n${formatEnhancedFacts(enhancedFacts)}`,
};
},
};
Dynamic Provider Loading
import { ChannelType, type IAgentRuntime, type Memory } from '@elizaos/core';
// Conditionally load providers based on context
async function adaptiveStateComposition(runtime: IAgentRuntime, message: Memory) {
// Determine context
const room = await runtime.getRoom(message.roomId);
const isDM = room?.type === ChannelType.DM;
const isVoice = room?.type === ChannelType.VOICE_GROUP || room?.type === ChannelType.VOICE_DM;
// Build provider list based on context
const providers: string[] = ['CHARACTER', 'RECENT_MESSAGES'];
if (isDM) {
providers.push('SETTINGS'); // Configuration in DMs
} else {
providers.push('ROLES', 'ENTITIES'); // Group context
}
if (isVoice) {
// Voice channels might need different providers
// For example, you might want to limit providers for performance
providers = ['CHARACTER', 'ANXIETY']; // Minimal providers for voice
}
// Check if user mentioned facts or history
if (message.content.text.match(/remember|fact|history/i)) {
providers.push('FACTS', 'RELATIONSHIPS');
}
// Compose adaptive state
return runtime.composeState(message, providers, false, false);
}
// Transform state through multiple stages
async function stateTransformationPipeline(runtime: IAgentRuntime, message: Memory) {
// Stage 1: Base state
const baseState = await runtime.composeState(
message,
['CHARACTER', 'RECENT_MESSAGES'],
true,
false
);
// Helper function to determine if facts are needed
const needsFactEnrichment = (state: State) => {
// Check if the message mentions facts, history, or memory
const messageText = state.values.recentMessages || '';
return /remember|fact|history|memory/i.test(messageText);
};
// Stage 2: Enrich with facts if needed
let enrichedState = baseState;
if (needsFactEnrichment(baseState)) {
const factsState = await runtime.composeState(message, ['FACTS'], false, false);
enrichedState = mergeStates(baseState, factsState);
}
// Stage 3: Add action context
const actionState = await runtime.composeState(
message,
['ACTIONS', 'ACTION_STATE'],
false,
false
);
// Final merged state
return mergeStates(enrichedState, actionState);
}
function mergeStates(state1: State, state2: State): State {
return {
values: { ...state1.values, ...state2.values },
data: {
...state1.data,
...state2.data,
providers: {
...state1.data.providers,
...state2.data.providers,
},
},
text: `${state1.text}\n\n${state2.text}`,
};
}
Troubleshooting
Common Issues and Solutions
1. Stale Cache Data
Problem: State contains outdated information
// Symptom: Old messages or incorrect data
const state = await runtime.composeState(message);
console.log(state.values.recentMessages); // Shows old messages
Solution: Force fresh data or implement cache TTL
// Option 1: Skip cache
const freshState = await runtime.composeState(message, null, false, true);
// Option 2: Implement cache TTL
class CacheWithTTL extends Map {
private ttl: number;
private timestamps = new Map<string, number>();
constructor(ttl: number = 5 * 60 * 1000) {
// 5 minutes default
super();
this.ttl = ttl;
}
set(key: string, value: any) {
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);
}
}
2. Provider Timeout Issues
Problem: Slow providers blocking state composition
// Provider that might hang
const slowProvider: Provider = {
name: 'SLOW_API',
get: async (runtime, message) => {
// This could take forever without proper timeout handling
try {
// Simulate a slow external API call
const response = await fetch('https://slow-api.example.com/data');
const data = await response.json();
return {
data: { apiResponse: data },
values: { slowApiStatus: 'success' },
text: `Fetched data from slow API`,
};
} catch (error) {
// This might never complete if the API hangs
return { data: {}, values: {}, text: '' };
}
},
};
Solution: Implement timeouts
const timeoutProvider: Provider = {
name: 'TIMEOUT_SAFE',
get: async (runtime, message) => {
try {
// Example of a provider that might take too long
const fetchData = async () => {
// Simulate an API call or expensive operation
const service = runtime.getService('externalAPI');
if (!service) {
return { values: {}, data: {}, text: '' };
}
const data = await service.fetchData();
return {
values: { apiData: data.summary },
data: { apiData: data },
text: `API data: ${data.summary}`,
};
};
const result = await Promise.race([
fetchData(),
new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 5000)),
]);
return result;
} catch (error) {
runtime.logger.warn(`Provider TIMEOUT_SAFE timed out`);
return { values: {}, data: {}, text: '' };
}
},
};
3. Memory Leaks from Cache
Problem: Cache grows indefinitely
// This will grow forever
for (const message of messages) {
await runtime.composeState(message);
}
Solution: Implement cache size limits
class BoundedCache extends Map {
private maxSize: number;
constructor(maxSize: number = 1000) {
super();
this.maxSize = maxSize;
}
set(key: string, value: any) {
// 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);
}
}
4. Circular Provider Dependencies
Problem: Providers depending on each other
// DON'T DO THIS
const providerA: Provider = {
name: 'A',
get: async (runtime, message) => {
const state = await runtime.composeState(message, ['B']);
// Uses B's data
},
};
const providerB: Provider = {
name: 'B',
get: async (runtime, message) => {
const state = await runtime.composeState(message, ['A']);
// Uses A's data - CIRCULAR!
},
};
Solution: Use provider positioning and cached state
const providerA: Provider = {
name: 'A',
position: 100, // Runs first
get: async (runtime, message) => {
// Generate data independently
return { data: { aData: 'value' } };
},
};
const providerB: Provider = {
name: 'B',
position: 200, // Runs after A
get: async (runtime, message, cachedState) => {
// Access A's data from cached state
const aData = cachedState.data?.providers?.A?.data?.aData;
// Process the data from provider A
const processedData = aData
? {
enhanced: true,
originalValue: aData,
processedAt: Date.now(),
}
: null;
return {
data: { bData: processedData },
values: { bProcessed: processedData ? 'success' : 'no data' },
text: processedData ? `Processed data from A: ${aData}` : '',
};
},
};
Debugging State Composition
// Debug helper to trace provider execution
async function debugComposeState(runtime: IAgentRuntime, message: Memory, includeList?: string[]) {
console.log('=== State Composition Debug ===');
console.log('Message ID:', message.id);
console.log('Include List:', includeList || 'default');
// Monkey patch provider execution
const originalProviders = runtime.providers;
runtime.providers = runtime.providers.map((provider) => ({
...provider,
get: async (...args) => {
const start = Date.now();
console.log(`[${provider.name}] Starting...`);
try {
const result = await provider.get(...args);
const duration = Date.now() - start;
console.log(`[${provider.name}] Completed in ${duration}ms`);
console.log(`[${provider.name}] Data size:`, JSON.stringify(result).length);
return result;
} catch (error) {
console.error(`[${provider.name}] Error:`, error);
throw error;
}
},
}));
const state = await runtime.composeState(message, includeList);
// Restore original providers
runtime.providers = originalProviders;
console.log('=== Final State Summary ===');
console.log('Total providers run:', Object.keys(state.data.providers || {}).length);
console.log('State text length:', state.text.length);
console.log('===============================');
return state;
}
Best Practices
1. Provider Selection
- Use default providers for general conversation
- Include dynamic providers only when needed
- Use
onlyInclude
for performance-critical paths
2. Cache Management
// Good: Use cache for repeated operations
const state1 = await runtime.composeState(message); // First call
const state2 = await runtime.composeState(message); // Uses cache
// Good: Skip cache for real-time data
const freshState = await runtime.composeState(message, null, false, true);
// Bad: Always skipping cache
// This defeats the purpose of caching and hurts performance
3. Error Handling
try {
const state = await runtime.composeState(message, ['CUSTOM_PROVIDER']);
// Use state
} catch (error) {
console.error('State composition failed:', error);
// Fallback to minimal state
const minimalState = await runtime.composeState(
message,
['CHARACTER'], // Just character data
true,
true
);
}
4. Memory Optimization
// Clean up old cache entries periodically
setInterval(() => {
const fiveMinutesAgo = Date.now() - 5 * 60 * 1000;
for (const [messageId, _] of runtime.stateCache.entries()) {
runtime.getMemoryById(messageId).then((memory) => {
if (memory && memory.createdAt < fiveMinutesAgo) {
runtime.stateCache.delete(messageId);
}
});
}
}, 60000); // Run every minute
5. Custom Provider Guidelines
When creating custom providers:
const customProvider: Provider = {
name: 'CUSTOM_DATA',
description: 'Provides custom data',
dynamic: true, // Set to true if not always needed
position: 150, // Higher numbers run later
private: false, // Set to true for internal-only providers
get: async (runtime, message, state) => {
// Best practices:
// 1. Return quickly - use timeouts
// 2. Handle errors gracefully
// 3. Return empty result on failure
// 4. Keep data size reasonable
try {
// Example: Fetch data with timeout
const fetchDataWithTimeout = async (timeout: number) => {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch('https://api.example.com/data', {
signal: controller.signal,
});
clearTimeout(timeoutId);
return await response.json();
} catch (error) {
clearTimeout(timeoutId);
throw error;
}
};
const data = await fetchDataWithTimeout(5000);
return {
values: { customValue: data.summary || 'No summary' },
data: { fullData: data },
text: `Custom data: ${data.summary || 'No data available'}`,
};
} catch (error) {
runtime.logger.error('Error in CUSTOM_DATA provider:', error);
// Return empty result on error
return {
values: {},
data: {},
text: '',
};
}
},
};
Summary
The composeState
method is central to ElizaOS’s context management system. It provides a flexible way to aggregate data from multiple sources, manage caching for performance, and create rich contextual state for agent decision-making. By understanding how to effectively use providers and manage state composition, you can build more intelligent and context-aware agents.
Key takeaways:
- Use default composition for most scenarios
- Include dynamic providers when specific data is needed
- Leverage caching for performance
- Clear cache when data freshness is critical
- Monitor provider performance in production
- Handle errors gracefully