Understanding agent memory, context, and state management in elizaOS
AgentRuntime
. The system provides a unified API for creating, storing, retrieving, and searching memories, enabling agents to maintain context and learn from interactions. For runtime details, see Runtime and Lifecycle and Runtime Core.
interface Memory {
id?: UUID; // Unique identifier
entityId: UUID; // Who created this memory (user/agent)
roomId: UUID; // Conversation context
worldId?: UUID; // Broader context (e.g., server)
content: Content; // The actual content
embedding?: number[]; // Vector representation
createdAt?: number; // Timestamp
metadata?: MemoryMetadata; // Additional data
}
interface Content {
text?: string; // Text content
actions?: string[]; // Associated actions
inReplyTo?: UUID; // Reference to previous memory
metadata?: any; // Custom metadata
}
// Creating a memory through the runtime
async function createMemory(runtime: IAgentRuntime, message: string) {
const memory: CreateMemory = {
agentId: runtime.agentId,
entityId: userId,
roomId: currentRoom,
content: {
text: message,
metadata: {
source: 'chat',
processed: Date.now()
}
}
};
// Runtime automatically generates embeddings if not provided
const memoryId = await runtime.createMemory(memory);
return memoryId;
}
IDatabaseAdapter
:
// The runtime handles storage automatically
// Memories are stored with:
// - Full text for retrieval
// - Embeddings for semantic search
// - Metadata for filtering
// - Relationships for context
// Recent memories from a conversation
const recentMemories = await runtime.getMemories({
roomId: roomId,
count: 10,
unique: true // Deduplicate similar memories
});
// Memories from a specific user
const userMemories = await runtime.getMemories({
entityId: userId,
count: 20
});
// Time-bounded memories
const todaysMemories = await runtime.getMemories({
roomId: roomId,
start: startOfDay,
end: endOfDay
});
// Context window configuration
export class AgentRuntime {
readonly #conversationLength = 32; // Default messages to consider
// Dynamically adjust based on token limits
async buildContext(roomId: UUID): Promise<State> {
const memories = await this.getMemories({
roomId,
count: this.#conversationLength
});
// Token counting and pruning
let tokenCount = 0;
const maxTokens = 4000; // Leave room for response
const prunedMemories = [];
for (const memory of memories) {
const tokens = estimateTokens(memory.content.text);
if (tokenCount + tokens > maxTokens) break;
tokenCount += tokens;
prunedMemories.push(memory);
}
return this.composeState(prunedMemories);
}
}
const recentContext = await runtime.getMemories({
roomId: roomId,
count: 20,
orderBy: 'createdAt',
direction: 'DESC'
});
// Importance scoring based on:
// - User reactions
// - Agent actions taken
// - Explicit markers
const importantMemories = await runtime.searchMemories({
roomId: roomId,
filter: {
importance: { $gte: 0.8 }
},
count: 10
});
async function getHybridContext(runtime: IAgentRuntime, roomId: UUID) {
// Get recent messages for immediate context
const recent = await runtime.getMemories({
roomId,
count: 10
});
// Get important historical context
const important = await runtime.searchMemories({
roomId,
query: "important decisions, key information, user preferences",
match_threshold: 0.7,
count: 5
});
// Combine and deduplicate
const combined = [...recent, ...important];
return deduplicateMemories(combined);
}
// The runtime's state composition pipeline
interface State {
messages: Memory[]; // Conversation history
facts: string[]; // Known facts
providers: ProviderData[]; // Provider contributions
context: string; // Formatted context
}
// Provider contribution to state
export const userContextProvider: Provider = {
name: 'userContext',
get: async (runtime, message, state) => {
const userProfile = await runtime.getEntity(message.entityId);
return {
text: `User: ${userProfile.name}`,
data: {
preferences: userProfile.metadata?.preferences,
history: userProfile.metadata?.interactionCount
}
};
}
};
// Short-term memory is typically the current conversation
class WorkingMemory {
private buffer: Memory[] = [];
private maxSize = 50;
add(memory: Memory) {
this.buffer.push(memory);
if (this.buffer.length > this.maxSize) {
this.buffer.shift(); // Remove oldest
}
}
getRecent(count: number): Memory[] {
return this.buffer.slice(-count);
}
clear() {
this.buffer = [];
}
}
// Long-term memories are marked and preserved
interface LongTermMemory extends Memory {
metadata: {
type: 'long_term';
importance: number;
lastAccessed: number;
accessCount: number;
};
}
// Consolidation process
async function consolidateToLongTerm(
runtime: IAgentRuntime,
memory: Memory
): Promise<void> {
if (shouldConsolidate(memory)) {
await runtime.updateMemory({
...memory,
metadata: {
...memory.metadata,
type: 'long_term',
importance: calculateImportance(memory),
consolidatedAt: Date.now()
}
});
}
}
// Knowledge loaded from character configuration
const staticKnowledge = character.knowledge || [];
// Dynamic knowledge learned during interactions
async function learnFact(runtime: IAgentRuntime, fact: string) {
await runtime.createMemory({
content: {
text: fact,
metadata: {
type: 'knowledge',
learned: true,
confidence: 0.9
}
},
roomId: 'knowledge-base',
entityId: runtime.agentId
});
}
// Retrieving knowledge
async function getKnowledge(runtime: IAgentRuntime, topic: string) {
return await runtime.searchMemories({
query: topic,
filter: {
'metadata.type': 'knowledge'
},
match_threshold: 0.7
});
}
// Complete memory creation with all metadata
async function createRichMemory(
runtime: IAgentRuntime,
content: string,
context: any
): Promise<UUID> {
const memory: CreateMemory = {
agentId: runtime.agentId,
entityId: context.userId,
roomId: context.roomId,
content: {
text: content,
actions: context.actions || [],
inReplyTo: context.replyTo,
metadata: {
source: context.source,
platform: context.platform,
sentiment: analyzeSentiment(content),
topics: extractTopics(content),
entities: extractEntities(content)
}
},
// Pre-compute embedding for better performance
embedding: await runtime.embed(content)
};
return await runtime.createMemory(memory);
}
// Paginated retrieval for large conversations
async function getPaginatedMemories(
runtime: IAgentRuntime,
roomId: UUID,
page: number = 1,
pageSize: number = 20
) {
const offset = (page - 1) * pageSize;
return await runtime.getMemories({
roomId,
count: pageSize,
offset
});
}
// Filtered retrieval
async function getFilteredMemories(
runtime: IAgentRuntime,
filters: MemoryFilters
) {
return await runtime.getMemories({
roomId: filters.roomId,
entityId: filters.entityId,
start: filters.startDate,
end: filters.endDate,
filter: {
'content.actions': { $contains: filters.action },
'metadata.sentiment': filters.sentiment
}
});
}
// Semantic search with embeddings
async function semanticSearch(
runtime: IAgentRuntime,
query: string,
options: SearchOptions = {}
): Promise<Memory[]> {
const embedding = await runtime.embed(query);
return await runtime.searchMemoriesByEmbedding(embedding, {
match_threshold: options.threshold || 0.75,
count: options.limit || 10,
roomId: options.roomId,
filter: options.filter
});
}
// Hybrid search combining semantic and keyword
async function hybridSearch(
runtime: IAgentRuntime,
query: string
): Promise<Memory[]> {
// Semantic search
const semantic = await semanticSearch(runtime, query);
// Keyword search
const keywords = extractKeywords(query);
const keyword = await runtime.searchMemories({
text: keywords.join(' OR '),
count: 10
});
// Combine and rank
return rankSearchResults([...semantic, ...keyword]);
}
// Automatic embedding generation
class EmbeddingManager {
private model: EmbeddingModel;
private cache = new Map<string, number[]>();
async generateEmbedding(text: string): Promise<number[]> {
// Check cache first
const cached = this.cache.get(text);
if (cached) return cached;
// Generate new embedding
const embedding = await this.model.embed(text);
// Cache for reuse
this.cache.set(text, embedding);
return embedding;
}
// Batch processing for efficiency
async generateBatch(texts: string[]): Promise<number[][]> {
const uncached = texts.filter(t => !this.cache.has(t));
if (uncached.length > 0) {
const embeddings = await this.model.embedBatch(uncached);
uncached.forEach((text, i) => {
this.cache.set(text, embeddings[i]);
});
}
return texts.map(t => this.cache.get(t)!);
}
}
// Vector similarity calculation
function cosineSimilarity(a: number[], b: number[]): number {
let dotProduct = 0;
let normA = 0;
let normB = 0;
for (let i = 0; i < a.length; i++) {
dotProduct += a[i] * b[i];
normA += a[i] * a[i];
normB += b[i] * b[i];
}
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}
// Optimized vector search with indexing
class VectorIndex {
private index: AnnoyIndex; // Approximate nearest neighbor
async search(
query: number[],
k: number = 10
): Promise<SearchResult[]> {
const neighbors = await this.index.getNearestNeighbors(query, k);
return neighbors.map(n => ({
id: n.id,
similarity: n.distance,
memory: this.getMemory(n.id)
}));
}
// Periodic index rebuilding for new memories
async rebuild() {
const memories = await this.getAllMemories();
this.index = new AnnoyIndex(memories);
await this.index.build();
}
}
interface State {
// Core conversation context
messages: Memory[];
recentMessages: string;
// Agent knowledge
facts: string[];
knowledge: string;
// Provider contributions
providers: {
[key: string]: {
text: string;
data: any;
};
};
// Composed context
context: string;
// Metadata
metadata: {
roomId: UUID;
entityId: UUID;
timestamp: number;
tokenCount: number;
};
}
class StateManager {
private currentState: State;
private stateHistory: State[] = [];
private maxHistory = 10;
async updateState(runtime: IAgentRuntime, trigger: Memory) {
// Save current state to history
this.stateHistory.push(this.currentState);
if (this.stateHistory.length > this.maxHistory) {
this.stateHistory.shift();
}
// Build new state
this.currentState = await this.buildState(runtime, trigger);
// Notify listeners
this.emitStateChange(this.currentState);
return this.currentState;
}
private async buildState(
runtime: IAgentRuntime,
trigger: Memory
): Promise<State> {
// Get relevant memories
const memories = await runtime.getMemories({
roomId: trigger.roomId,
count: 20
});
// Get provider data
const providers = await this.gatherProviderData(runtime, trigger);
// Compose final state
return runtime.composeState({
messages: memories,
providers,
trigger
});
}
}
// Time-based pruning
async function pruneOldMemories(
runtime: IAgentRuntime,
maxAge: number = 30 * 24 * 60 * 60 * 1000 // 30 days
) {
const cutoff = Date.now() - maxAge;
await runtime.deleteMemories({
filter: {
createdAt: { $lt: cutoff },
'metadata.type': { $ne: 'long_term' } // Preserve long-term
}
});
}
// Importance-based pruning
async function pruneByImportance(
runtime: IAgentRuntime,
maxMemories: number = 10000
) {
const memories = await runtime.getAllMemories();
if (memories.length <= maxMemories) return;
// Score and sort memories
const scored = memories.map(m => ({
memory: m,
score: calculateImportanceScore(m)
}));
scored.sort((a, b) => b.score - a.score);
// Keep top memories, delete rest
const toDelete = scored.slice(maxMemories);
for (const item of toDelete) {
await runtime.deleteMemory(item.memory.id);
}
}
class MemoryCache {
private l1Cache = new Map<UUID, Memory>(); // Hot cache (in-memory)
private l2Cache = new LRUCache<UUID, Memory>({ // Warm cache
max: 1000,
ttl: 5 * 60 * 1000 // 5 minutes
});
async get(id: UUID): Promise<Memory | null> {
// Check L1
if (this.l1Cache.has(id)) {
return this.l1Cache.get(id);
}
// Check L2
const l2Result = this.l2Cache.get(id);
if (l2Result) {
this.l1Cache.set(id, l2Result); // Promote to L1
return l2Result;
}
// Fetch from database
const memory = await this.fetchFromDB(id);
if (memory) {
this.cache(memory);
}
return memory;
}
private cache(memory: Memory) {
this.l1Cache.set(memory.id, memory);
this.l2Cache.set(memory.id, memory);
// Manage L1 size
if (this.l1Cache.size > 100) {
const oldest = this.l1Cache.keys().next().value;
this.l1Cache.delete(oldest);
}
}
}
// Indexed queries
interface MemoryIndexes {
roomId: BTreeIndex;
entityId: BTreeIndex;
createdAt: BTreeIndex;
embedding: IVFIndex; // Inverted file index for vectors
}
// Batch operations
async function batchCreateMemories(
runtime: IAgentRuntime,
memories: CreateMemory[]
): Promise<UUID[]> {
// Generate embeddings in batch
const texts = memories.map(m => m.content.text);
const embeddings = await runtime.embedBatch(texts);
// Prepare batch insert
const enriched = memories.map((m, i) => ({
...m,
embedding: embeddings[i]
}));
// Single database transaction
return await runtime.batchCreateMemories(enriched);
}
// Memory graph structure
interface MemoryNode {
memory: Memory;
connections: {
causes: UUID[]; // Memories that led to this
effects: UUID[]; // Memories caused by this
related: UUID[]; // Thematically related
references: UUID[]; // Explicit references
};
}
// Building memory graphs
async function buildMemoryGraph(
runtime: IAgentRuntime,
rootMemoryId: UUID
): Promise<MemoryGraph> {
const visited = new Set<UUID>();
const graph = new Map<UUID, MemoryNode>();
async function traverse(memoryId: UUID, depth: number = 0) {
if (visited.has(memoryId) || depth > 3) return;
visited.add(memoryId);
const memory = await runtime.getMemory(memoryId);
const connections = await findConnections(runtime, memory);
graph.set(memoryId, {
memory,
connections
});
// Recursively traverse connections
for (const connectedId of Object.values(connections).flat()) {
await traverse(connectedId, depth + 1);
}
}
await traverse(rootMemoryId);
return graph;
}
// Temporal memory windows
async function getTemporalContext(
runtime: IAgentRuntime,
timestamp: number,
windowSize: number = 60 * 60 * 1000 // 1 hour
) {
return await runtime.getMemories({
start: timestamp - windowSize / 2,
end: timestamp + windowSize / 2,
orderBy: 'createdAt'
});
}
// Memory decay modeling
function calculateMemoryRelevance(
memory: Memory,
currentTime: number
): number {
const age = currentTime - memory.createdAt;
const halfLife = 7 * 24 * 60 * 60 * 1000; // 1 week
// Exponential decay with importance modifier
const decay = Math.exp(-age / halfLife);
const importance = memory.metadata?.importance || 0.5;
return decay * importance;
}
// Shared memory pool
interface SharedMemorySpace {
id: UUID;
agents: UUID[];
visibility: 'public' | 'private' | 'selective';
permissions: {
[agentId: string]: {
read: boolean;
write: boolean;
delete: boolean;
};
};
}
// Accessing shared memories
async function getSharedMemories(
runtime: IAgentRuntime,
spaceId: UUID
): Promise<Memory[]> {
// Check permissions
const space = await runtime.getSharedSpace(spaceId);
const permissions = space.permissions[runtime.agentId];
if (!permissions?.read) {
throw new Error('No read access to shared space');
}
return await runtime.getMemories({
spaceId,
visibility: ['public', runtime.agentId]
});
}
// Memory synchronization
async function syncMemories(
runtime: IAgentRuntime,
otherAgentId: UUID
) {
const sharedSpace = await runtime.getSharedSpace(otherAgentId);
const updates = await runtime.getMemoryUpdates(sharedSpace.lastSync);
for (const update of updates) {
await runtime.applyMemoryUpdate(update);
}
sharedSpace.lastSync = Date.now();
}
// Debug search issues
async function debugSearch(runtime: IAgentRuntime, query: string) {
// Check embedding generation
const embedding = await runtime.embed(query);
console.log('Query embedding:', embedding.slice(0, 5));
// Test with different thresholds
const thresholds = [0.9, 0.8, 0.7, 0.6, 0.5];
for (const threshold of thresholds) {
const results = await runtime.searchMemoriesByEmbedding(embedding, {
match_threshold: threshold,
count: 5
});
console.log(`Threshold ${threshold}: ${results.length} results`);
}
// Check if memories exist at all
const allMemories = await runtime.getMemories({ count: 100 });
console.log(`Total memories: ${allMemories.length}`);
}
// Monitor memory usage
class MemoryMonitor {
private metrics = {
totalMemories: 0,
averageSize: 0,
growthRate: 0
};
async monitor(runtime: IAgentRuntime) {
setInterval(async () => {
const stats = await runtime.getMemoryStats();
this.metrics = {
totalMemories: stats.count,
averageSize: stats.totalSize / stats.count,
growthRate: (stats.count - this.metrics.totalMemories) / this.metrics.totalMemories
};
if (this.metrics.growthRate > 0.1) { // 10% growth
console.warn('High memory growth detected:', this.metrics);
}
}, 60000); // Check every minute
}
}
Was this page helpful?