Skip to main content
Glama
ConsolidationEngine.ts16.5 kB
/** * Consolidation Engine * * Implements pattern transfer and memory pruning between episodic and semantic memory. * Handles the consolidation process that transfers important patterns from episodic * to semantic memory, similar to sleep-based memory consolidation in humans. */ import { ComponentStatus, IConsolidationEngine, Pattern, } from "../interfaces/cognitive.js"; import { Concept, Episode } from "../types/core.js"; export interface ConsolidationConfig { consolidation_threshold: number; pattern_similarity_threshold: number; minimum_episode_count: number; importance_weight: number; recency_weight: number; frequency_weight: number; max_patterns_per_cycle: number; pruning_threshold: number; } export interface ConsolidationResult { patterns_extracted: number; concepts_created: number; relations_strengthened: number; episodes_processed: number; pruned_memories: number; } export class ConsolidationEngine implements IConsolidationEngine { private config: ConsolidationConfig; private initialized: boolean = false; private lastActivity: number = 0; private consolidationHistory: ConsolidationResult[] = []; constructor(config?: Partial<ConsolidationConfig>) { this.config = { consolidation_threshold: 0.7, pattern_similarity_threshold: 0.6, minimum_episode_count: 3, importance_weight: 0.4, recency_weight: 0.3, frequency_weight: 0.3, max_patterns_per_cycle: 50, pruning_threshold: 0.1, ...config, }; } async initialize(config?: Partial<ConsolidationConfig>): Promise<void> { if (config) { this.config = { ...this.config, ...config }; } this.consolidationHistory = []; this.initialized = true; this.lastActivity = Date.now(); } async process(input: Episode[]): Promise<Concept[]> { // Generic process method for CognitiveComponent interface if (Array.isArray(input)) { return this.consolidate(input); } throw new Error("Invalid input for ConsolidationEngine.process()"); } reset(): void { this.consolidationHistory = []; this.lastActivity = Date.now(); } getStatus(): ComponentStatus { return { name: "ConsolidationEngine", initialized: this.initialized, active: this.consolidationHistory.length > 0, last_activity: this.lastActivity, }; } /** * Main consolidation process - extract patterns and create concepts */ consolidate(episodes: Episode[]): Concept[] { this.lastActivity = Date.now(); if (episodes.length < this.config.minimum_episode_count) { return []; } const result: ConsolidationResult = { patterns_extracted: 0, concepts_created: 0, relations_strengthened: 0, episodes_processed: episodes.length, pruned_memories: 0, }; // Step 1: Extract patterns from episodes const patterns = this.extractPatterns(episodes); result.patterns_extracted = patterns.length; // Step 2: Convert significant patterns to concepts const concepts = this.patternsToConceptsConversion(patterns, episodes); result.concepts_created = concepts.length; // Step 3: Strengthen connections between related concepts result.relations_strengthened = this.strengthenConnections(concepts); // Step 4: Prune weak memories (handled externally but tracked) // This would be called by the memory systems themselves this.consolidationHistory.push(result); return concepts; } /** * Extract patterns from a collection of episodes */ extractPatterns(episodes: Episode[]): Pattern[] { const patterns: Pattern[] = []; // Group episodes by similarity const episodeGroups = this.groupSimilarEpisodes(episodes); for (const group of episodeGroups) { if (group.length < this.config.minimum_episode_count) continue; // Extract common elements from the group const commonPattern = this.extractCommonPattern(group); if (commonPattern && this.isSignificantPattern(commonPattern, group)) { const pattern: Pattern = { type: this.classifyPattern(commonPattern), content: commonPattern.elements, confidence: this.computePatternConfidence(commonPattern, group), salience: this.computePatternSalience(commonPattern, group), }; patterns.push(pattern); } } // Sort patterns by significance and limit to max per cycle return patterns .sort((a, b) => b.confidence * b.salience - a.confidence * a.salience) .slice(0, this.config.max_patterns_per_cycle); } /** * Strengthen connections between related concepts */ strengthenConnections(concepts: Concept[]): number { let strengthenedCount = 0; // Find relationships between concepts based on co-occurrence and similarity for (let i = 0; i < concepts.length; i++) { for (let j = i + 1; j < concepts.length; j++) { const conceptA = concepts[i]; const conceptB = concepts[j]; const relationshipStrength = this.computeRelationshipStrength( conceptA, conceptB ); if (relationshipStrength > this.config.consolidation_threshold) { // This would typically update the semantic memory system // For now, we just count the potential strengthening strengthenedCount++; } } } return strengthenedCount; } /** * Prune weak memories based on threshold */ pruneWeakMemories(_threshold: number = this.config.pruning_threshold): void { // This method would be called by memory systems to identify // memories that should be pruned based on consolidation analysis this.lastActivity = Date.now(); } /** * Get consolidation statistics */ getConsolidationStats(): ConsolidationResult[] { return [...this.consolidationHistory]; } /** * Get the most recent consolidation result */ getLastConsolidationResult(): ConsolidationResult | null { return this.consolidationHistory.length > 0 ? this.consolidationHistory[this.consolidationHistory.length - 1] : null; } // Private helper methods private groupSimilarEpisodes(episodes: Episode[]): Episode[][] { const groups: Episode[][] = []; const processed = new Set<number>(); for (let i = 0; i < episodes.length; i++) { if (processed.has(i)) continue; const group = [episodes[i]]; processed.add(i); for (let j = i + 1; j < episodes.length; j++) { if (processed.has(j)) continue; const similarity = this.computeEpisodeSimilarity( episodes[i], episodes[j] ); if (similarity >= this.config.pattern_similarity_threshold) { group.push(episodes[j]); processed.add(j); } } groups.push(group); } return groups; } private computeEpisodeSimilarity( episodeA: Episode, episodeB: Episode ): number { let similarity = 0; // Content similarity (simple string matching) const contentA = JSON.stringify(episodeA.content).toLowerCase(); const contentB = JSON.stringify(episodeB.content).toLowerCase(); const wordsA = new Set(contentA.split(/\s+/)); const wordsB = new Set(contentB.split(/\s+/)); const intersection = new Set( [...wordsA].filter((word) => wordsB.has(word)) ); const union = new Set([...wordsA, ...wordsB]); similarity += (intersection.size / union.size) * 0.4; // Context similarity if (episodeA.context && episodeB.context) { if (episodeA.context.session_id === episodeB.context.session_id) { similarity += 0.2; } if (episodeA.context.domain === episodeB.context.domain) { similarity += 0.2; } } // Emotional similarity const emotionalOverlap = this.computeEmotionalOverlap( episodeA.emotional_tags, episodeB.emotional_tags ); similarity += emotionalOverlap * 0.2; return Math.min(similarity, 1.0); } private computeEmotionalOverlap(tagsA: string[], tagsB: string[]): number { if (!tagsA || !tagsB || tagsA.length === 0 || tagsB.length === 0) { return 0; } const setA = new Set(tagsA); const setB = new Set(tagsB); const intersection = new Set([...setA].filter((tag) => setB.has(tag))); const union = new Set([...setA, ...setB]); return intersection.size / union.size; } private extractCommonPattern(episodes: Episode[]): CommonPattern | null { if (episodes.length < 2) return null; // Extract common elements across episodes const commonElements: string[] = []; const elementCounts = new Map<string, number>(); // Count element occurrences for (const episode of episodes) { const elements = this.extractElementsFromEpisode(episode); for (const element of elements) { elementCounts.set(element, (elementCounts.get(element) ?? 0) + 1); } } // Find elements that appear in most episodes const threshold = Math.max(2, Math.ceil(episodes.length * 0.5)); // 50% threshold, minimum 2 for (const [element, count] of elementCounts) { if (count >= threshold) { commonElements.push(element); } } if (commonElements.length === 0) return null; return { elements: commonElements, frequency: commonElements.length, episodes: episodes.length, confidence: commonElements.length / this.getTotalUniqueElements(episodes), }; } private extractElementsFromEpisode(episode: Episode): string[] { const elements: string[] = []; // Extract from content const contentStr = JSON.stringify(episode.content).toLowerCase(); const words = contentStr.match(/\b\w+\b/g) ?? []; elements.push(...words); // Extract from context if (episode.context) { if (episode.context.domain) elements.push(`domain:${episode.context.domain}`); if (episode.context.session_id) elements.push(`session:${episode.context.session_id}`); } // Extract from emotional tags if (episode.emotional_tags) { elements.push(...episode.emotional_tags.map((tag) => `emotion:${tag}`)); } return elements; } private getTotalUniqueElements(episodes: Episode[]): number { const allElements = new Set<string>(); for (const episode of episodes) { const elements = this.extractElementsFromEpisode(episode); elements.forEach((element) => allElements.add(element)); } return allElements.size; } private isSignificantPattern( pattern: CommonPattern, episodes: Episode[] ): boolean { // Check if pattern meets significance criteria const avgImportance = episodes.reduce((sum, ep) => sum + ep.importance, 0) / episodes.length; const recencyScore = this.computeRecencyScore(episodes); const frequencyScore = pattern.frequency / this.getTotalUniqueElements(episodes); const significance = avgImportance * this.config.importance_weight + recencyScore * this.config.recency_weight + frequencyScore * this.config.frequency_weight; // Lower threshold for testing - patterns with reasonable frequency should be significant return significance >= Math.min(this.config.consolidation_threshold, 0.4); } private computeRecencyScore(episodes: Episode[]): number { const currentTime = Date.now(); const avgAge = episodes.reduce((sum, ep) => { const age = (currentTime - ep.timestamp) / (1000 * 60 * 60 * 24); // days return sum + age; }, 0) / episodes.length; // Recency score decreases with age return Math.exp(-avgAge / 7); // 7-day half-life } private classifyPattern(pattern: CommonPattern): string { // Simple pattern classification based on content const elements = pattern.elements; if (elements.some((e) => e.startsWith("emotion:"))) { return "emotional_pattern"; } if (elements.some((e) => e.startsWith("domain:"))) { return "domain_pattern"; } if (elements.some((e) => e.startsWith("session:"))) { return "session_pattern"; } return "content_pattern"; } private computePatternConfidence( pattern: CommonPattern, episodes: Episode[] ): number { // Confidence based on consistency across episodes const consistencyScore = pattern.frequency / this.getTotalUniqueElements(episodes); const episodeCountScore = Math.min(episodes.length / 10, 1); // Normalize to 10 episodes return (consistencyScore + episodeCountScore) / 2; } private computePatternSalience( _pattern: CommonPattern, episodes: Episode[] ): number { // Salience based on importance and recency of episodes const avgImportance = episodes.reduce((sum, ep) => sum + ep.importance, 0) / episodes.length; const recencyScore = this.computeRecencyScore(episodes); return (avgImportance + recencyScore) / 2; } private patternsToConceptsConversion( patterns: Pattern[], episodes: Episode[] ): Concept[] { const concepts: Concept[] = []; for (const pattern of patterns) { // Create a concept from the pattern const concept: Concept = { id: this.generateConceptId(pattern), content: { pattern_type: pattern.type, elements: pattern.content, source_episodes: episodes.length, confidence: pattern.confidence, }, embedding: this.generateConceptEmbedding(pattern), relations: [], activation: pattern.salience, last_accessed: Date.now(), }; concepts.push(concept); } return concepts; } private generateConceptId(pattern: Pattern): string { const patternStr = `${pattern.type}_${pattern.content.join("_")}`; let hash = 0; for (let i = 0; i < patternStr.length; i++) { const char = patternStr.charCodeAt(i); hash = (hash << 5) - hash + char; hash = hash & hash; } return `consolidated_${Math.abs(hash).toString(16)}`; } private generateConceptEmbedding(pattern: Pattern): number[] { // Simple embedding generation for consolidated concepts const embedding = new Array(768).fill(0); // Standard embedding dimension const patternStr = pattern.content.join(" ").toLowerCase(); for (let i = 0; i < patternStr.length; i++) { const char = patternStr.charCodeAt(i); const index = char % embedding.length; embedding[index] += Math.sin(char * 0.1) * pattern.confidence; } // Normalize const magnitude = Math.sqrt( embedding.reduce((sum, val) => sum + val * val, 0) ); if (magnitude > 0) { for (let i = 0; i < embedding.length; i++) { embedding[i] /= magnitude; } } return embedding; } private computeRelationshipStrength( conceptA: Concept, conceptB: Concept ): number { // Compute relationship strength based on concept similarity and co-occurrence let strength = 0; // Content similarity if (conceptA.embedding && conceptB.embedding) { strength += this.computeCosineSimilarity(conceptA.embedding, conceptB.embedding) * 0.5; } // Activation correlation const activationSimilarity = 1 - Math.abs(conceptA.activation - conceptB.activation); strength += activationSimilarity * 0.3; // Temporal proximity (if both were accessed recently) const timeDiff = Math.abs(conceptA.last_accessed - conceptB.last_accessed); const temporalProximity = Math.exp(-timeDiff / (1000 * 60 * 60)); // 1-hour decay strength += temporalProximity * 0.2; return Math.min(strength, 1.0); } private computeCosineSimilarity(a: number[], b: number[]): number { if (a.length !== b.length) return 0; let dotProduct = 0; let magnitudeA = 0; let magnitudeB = 0; for (let i = 0; i < a.length; i++) { dotProduct += a[i] * b[i]; magnitudeA += a[i] * a[i]; magnitudeB += b[i] * b[i]; } magnitudeA = Math.sqrt(magnitudeA); magnitudeB = Math.sqrt(magnitudeB); if (magnitudeA === 0 || magnitudeB === 0) return 0; return dotProduct / (magnitudeA * magnitudeB); } } // Helper interfaces // Note: PatternCandidate interface available for future pattern extraction enhancements interface CommonPattern { elements: string[]; frequency: number; episodes: number; confidence: number; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/keyurgolani/ThoughtMcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server