thoughtmcp
Version:
AI that thinks more like humans do - MCP server with human-like cognitive architecture for enhanced reasoning, memory, and self-monitoring
391 lines • 16 kB
JavaScript
/**
* 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.
*/
export class ConsolidationEngine {
config;
initialized = false;
lastActivity = 0;
consolidationHistory = [];
constructor(config) {
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) {
if (config) {
this.config = { ...this.config, ...config };
}
this.consolidationHistory = [];
this.initialized = true;
this.lastActivity = Date.now();
}
async process(input) {
// Generic process method for CognitiveComponent interface
if (Array.isArray(input)) {
return this.consolidate(input);
}
throw new Error('Invalid input for ConsolidationEngine.process()');
}
reset() {
this.consolidationHistory = [];
this.lastActivity = Date.now();
}
getStatus() {
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) {
this.lastActivity = Date.now();
if (episodes.length < this.config.minimum_episode_count) {
return [];
}
const result = {
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) {
const patterns = [];
// 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 = {
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) {
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 = this.config.pruning_threshold) {
// 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() {
return [...this.consolidationHistory];
}
/**
* Get the most recent consolidation result
*/
getLastConsolidationResult() {
return this.consolidationHistory.length > 0
? this.consolidationHistory[this.consolidationHistory.length - 1]
: null;
}
// Private helper methods
groupSimilarEpisodes(episodes) {
const groups = [];
const processed = new Set();
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;
}
computeEpisodeSimilarity(episodeA, episodeB) {
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);
}
computeEmotionalOverlap(tagsA, tagsB) {
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;
}
extractCommonPattern(episodes) {
if (episodes.length < 2)
return null;
// Extract common elements across episodes
const commonElements = [];
const elementCounts = new Map();
// 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)
};
}
extractElementsFromEpisode(episode) {
const elements = [];
// 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;
}
getTotalUniqueElements(episodes) {
const allElements = new Set();
for (const episode of episodes) {
const elements = this.extractElementsFromEpisode(episode);
elements.forEach(element => allElements.add(element));
}
return allElements.size;
}
isSignificantPattern(pattern, episodes) {
// 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);
}
computeRecencyScore(episodes) {
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
}
classifyPattern(pattern) {
// 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';
}
computePatternConfidence(pattern, episodes) {
// 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;
}
computePatternSalience(_pattern, episodes) {
// 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;
}
patternsToConceptsConversion(patterns, episodes) {
const concepts = [];
for (const pattern of patterns) {
// Create a concept from the pattern
const 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;
}
generateConceptId(pattern) {
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)}`;
}
generateConceptEmbedding(pattern) {
// 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;
}
computeRelationshipStrength(conceptA, conceptB) {
// 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);
}
computeCosineSimilarity(a, b) {
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);
}
}
//# sourceMappingURL=ConsolidationEngine.js.map