Why Providers?
Your agent needs to understand the world around it. Without context, it’s flying blind:
- Who is the user? What’s their history?
- What actions are available right now?
- What does the agent know about this topic?
Providers are the senses of your agent. They gather data from memory, services, and external sources, then feed it into the LLM prompt.
Think of providers as context injectors. Each provider contributes a piece of the puzzle - conversation history, user facts, available actions - assembled into the prompt at runtime.
Provider Interface
Providers supply contextual information that forms the agent’s understanding of the current situation. They gather data from various sources to build comprehensive state.
Core Interface
interface Provider {
name: string;
description: string;
dynamic?: boolean; // Only executed when explicitly requested
private?: boolean; // Internal-only, not included in default state
position?: number; // Execution order (lower runs first)
get: (
runtime: IAgentRuntime,
message: Memory,
state?: State
) => Promise<ProviderResult>;
}
interface ProviderResult {
values: Record<string, any>; // Key-value pairs for templates
data: Record<string, any>; // Structured data
text: string; // Textual context
}
Provider Types
- Standard Providers: Included by default in state composition
- Dynamic Providers: Only executed when explicitly requested
- Private Providers: Internal use only, not exposed in default state
Built-in Providers
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 |
Provider Details
Actions Provider (ACTIONS)
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
{
values: {
actionNames: "Possible response actions: 'SEND_MESSAGE', 'SEARCH', 'CALCULATE'",
actionExamples: "..."
},
data: { actionsData: [...] },
text: "# Available Actions\n..."
}
Action State Provider (ACTION_STATE)
Shares execution state between chained actions.
- Position: 150 (runs later)
- 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
Character Provider (CHARACTER)
Core personality and behavior definition.
- 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
{
values: {
agentName: "Alice",
bio: "AI assistant focused on...",
topics: "technology, science, education",
adjective: "helpful"
},
data: { character: {...} },
text: "# About Alice\n..."
}
Recent Messages Provider (RECENT_MESSAGES)
Provides conversation history and context.
- Position: 100 (runs later to access other data)
- Dynamic: No (included by default)
- Data Provided:
recentMessages: Formatted conversation history
recentInteractions: Previous interactions
actionResults: Results from recent actions
{
values: {
recentMessages: "User: Hello\nAlice: Hi there!",
recentInteractions: "..."
},
data: {
recentMessages: [...],
actionResults: [...]
},
text: "# Conversation Messages\n..."
}
Facts Provider (FACTS)
Retrieves contextually relevant stored facts.
- Dynamic: Yes (must be explicitly included)
- Behavior: Uses embedding search to find relevant facts
- Data Provided:
- Relevant facts based on context
- Fact metadata and sources
Relationships Provider (RELATIONSHIPS)
Social graph and interaction history.
- Dynamic: Yes (must be explicitly included)
- Data Provided:
- Known entities and their relationships
- Interaction frequency
- Relationship metadata
State Composition
The composeState method aggregates data from multiple providers to create comprehensive state.
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
Composition Process
- 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
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);
Provider Registration
Registering a Provider
runtime.registerProvider(provider);
Providers are registered during plugin initialization:
const myPlugin: Plugin = {
name: 'my-plugin',
providers: [customProvider],
init: async (config, runtime) => {
// Providers auto-registered
}
};
Provider Position
Position determines execution order:
const earlyProvider: Provider = {
name: 'EARLY',
position: -100, // Runs very early
get: async () => {...}
};
const lateProvider: Provider = {
name: 'LATE',
position: 200, // Runs late
get: async () => {...}
};
Custom Providers
Creating a Custom Provider
const customDataProvider: Provider = {
name: 'CUSTOM_DATA',
description: 'Custom data from external source',
dynamic: true,
position: 150,
get: async (runtime, message, state) => {
try {
// Fetch data from 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: '' };
}
},
};
Provider Best Practices
- Return quickly: Use timeouts for external calls
- Handle errors gracefully: Return empty result on failure
- Keep data size reasonable: Don’t return excessive data
- Use appropriate flags: Set
dynamic for optional providers
- Consider position: Order matters for dependent providers
Provider Dependencies
Providers can access data from previously executed providers through the state parameter:
const dependentProvider: Provider = {
name: 'DEPENDENT',
position: 200, // Runs after other providers
get: async (runtime, message, state) => {
// Access data from earlier providers
const characterData = state?.data?.providers?.CHARACTER?.data;
if (!characterData) {
return { values: {}, data: {}, text: '' };
}
// Process based on character data
const processed = processCharacterData(characterData);
return {
values: { processed: processed.summary },
data: { processed },
text: `Processed: ${processed.summary}`
};
}
};
State Cache Management
Cache Architecture
The runtime maintains an in-memory cache of composed states:
// Cache is stored by message ID
this.stateCache.set(message.id, newState);
Cache Usage
// Use cached data (default behavior)
const cachedState = await runtime.composeState(message);
// Force fresh data
const freshState = await runtime.composeState(message, null, false, true);
Cache Optimization
// Clear 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
Provider Execution Flow
Parallel Execution
Providers run concurrently for optimal performance:
const results = await Promise.all(
providers.map(provider =>
provider.get(runtime, message, partialState)
)
);
Timeout Handling
Implement timeouts to prevent slow providers from blocking:
const timeoutProvider: Provider = {
name: 'TIMEOUT_SAFE',
get: async (runtime, message) => {
const fetchData = async () => {
// Potentially slow operation
const data = await externalAPI.fetch();
return formatProviderResult(data);
};
return Promise.race([
fetchData(),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), 5000)
)
]).catch(error => {
runtime.logger.warn(`Provider timeout: ${error.message}`);
return { values: {}, data: {}, text: '' };
});
}
};
Common Issues and Solutions
Circular Dependencies
Avoid providers that depend on each other circularly:
// BAD: Circular dependency
const providerA: Provider = {
get: async (runtime, message) => {
const state = await runtime.composeState(message, ['B']);
// Uses B's data
}
};
const providerB: Provider = {
get: async (runtime, message) => {
const state = await runtime.composeState(message, ['A']);
// Uses A's data - CIRCULAR!
}
};
// GOOD: Use position and state parameter
const providerA: Provider = {
position: 100,
get: async (runtime, message) => {
// Generate data independently
return { data: { aData: 'value' } };
}
};
const providerB: Provider = {
position: 200,
get: async (runtime, message, state) => {
// Access A's data from state
const aData = state?.data?.providers?.A?.data;
return { data: { bData: processData(aData) } };
}
};
Memory Leaks
Prevent memory leaks with proper cache management:
class BoundedCache extends Map {
private maxSize: number;
constructor(maxSize: number = 1000) {
super();
this.maxSize = maxSize;
}
set(key: string, value: any) {
if (this.size >= this.maxSize) {
const firstKey = this.keys().next().value;
this.delete(firstKey);
}
return super.set(key, value);
}
}
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;
}
See Also