packages/docs/runtime/providers.mdx
Your agent needs to understand the world around it. Without context, it's flying blind:
Providers are the senses of your agent. They gather data from memory, services, and external sources, then feed it into the LLM prompt.
<Tip> **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. </Tip>Providers supply contextual information that forms the agent's understanding of the current situation. They gather data from various sources to build comprehensive state.
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, unknown>; // Key-value pairs for templates
data: Record<string, unknown>; // Structured data
text: string; // Textual context
}
| 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 |
ACTIONS)Lists all available actions the agent can execute.
actionNames: Comma-separated list of action namesactionsWithDescriptions: Formatted action detailsactionExamples: Example usage for each actionactionsData: Raw action objects{
values: {
actionNames: "Possible response actions: 'SEND_MESSAGE', 'SEARCH', 'CALCULATE'",
actionExamples: "..."
},
data: { actionsData: [...] },
text: "# Available Actions\n..."
}
ACTION_STATE)Shares execution state between chained actions.
actionResults: Previous action execution resultsactionPlan: Multi-step action execution planworkingMemory: Temporary data shared between actionsrecentActionMemories: Historical action executionsCHARACTER)Core personality and behavior definition.
agentName: Character namebio: Character backgroundtopics: Current interestsadjective: Current mood/statedirections: Style guidelinesexamples: 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)Provides conversation history and context.
recentMessages: Formatted conversation historyrecentInteractions: Previous interactionsactionResults: Results from recent actions{
values: {
recentMessages: "User: Hello\nAlice: Hi there!",
recentInteractions: "..."
},
data: {
recentMessages: [...],
actionResults: [...]
},
text: "# Conversation Messages\n..."
}
FACTS)Retrieves contextually relevant stored facts.
RELATIONSHIPS)Social graph and interaction history.
The composeState method aggregates data from multiple providers to create comprehensive state.
async composeState(
message: Memory,
includeList: string[] | null = null,
onlyInclude = false,
skipCache = false
): Promise<State>
// 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);
runtime.registerProvider(provider);
Providers are registered during plugin initialization:
const myPlugin: Plugin = {
name: "my-plugin",
providers: [customProvider],
init: async (config, runtime) => {
// Providers auto-registered
},
};
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 () => {...}
};
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 = 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: "" };
}
},
};
dynamic for optional providersProviders 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}`,
};
},
};
The runtime maintains an in-memory cache of composed states:
// Cache is stored by message ID
this.stateCache.set(message.id, newState);
// Use cached data (default behavior)
const cachedState = await runtime.composeState(message);
// Force fresh data
const freshState = await runtime.composeState(message, null, false, true);
// 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
flowchart TD
Start[composeState called] --> Select[Select Providers]
Select --> Check{Check Cache}
Check -->|Cache Hit| Return[Return Cached State]
Check -->|Cache Miss| Sort[Sort by Position]
Sort --> Execute[Execute in Parallel]
Execute --> Aggregate[Aggregate Results]
Aggregate --> Cache[Store in Cache]
Cache --> Return
classDef process fill:#2196f3,color:#fff
classDef decision fill:#ff9800,color:#fff
classDef execution fill:#4caf50,color:#fff
classDef result fill:#9c27b0,color:#fff
class Start,Select,Sort process
class Check decision
class Execute,Aggregate execution
class Return,Cache result
Providers run concurrently for optimal performance:
const results = await Promise.all(
providers.map((provider) => provider.get(runtime, message, partialState)),
);
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: "" };
});
},
};
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) } };
},
};
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: unknown) {
if (this.size >= this.maxSize) {
const firstKey = this.keys().next().value;
this.delete(firstKey);
}
return super.set(key, value);
}
}
// 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;
}