Skip to main content
Glama
hybrid-memory-manager.js13 kB
import path from "path"; import { MemoryOptimizer } from "../memory-optimizer.js"; import { BackupOperations } from "./backup-operations.js"; import { JSONOperations } from "./json-operations.js"; import { logger } from "./logger.js"; import { MigrationUtils } from "./migration-utils.js"; import { ModularSQLiteOperations } from "./sqlite/index.js"; /** * Hybrid Memory Manager - Lightweight Orchestrator * Coordinates SQLite, JSON, optimization, and backup operations * Much smaller and focused on delegation rather than implementation */ export class HybridMemoryManager { sqliteOps; jsonOps; migrationUtils; backupOps; optimizer; useSQLite = true; migrationInProgress = false; constructor(basePath) { const memoryPath = basePath || process.env.MEMORY_PATH || path.join(process.cwd(), "memory"); this.sqliteOps = new ModularSQLiteOperations(memoryPath); this.jsonOps = new JSONOperations(memoryPath); this.migrationUtils = new MigrationUtils(memoryPath); this.backupOps = new BackupOperations(memoryPath); this.optimizer = new MemoryOptimizer({ compressionLevel: "aggressive", // Use aggressive for maximum compression extractKeywords: true, extractEntities: true, }); } async initialize() { // Initialize backup system await this.backupOps.initialize(); if (this.useSQLite) { // Initialize SQLite await this.sqliteOps.initialize(); logger.info("Enhanced Memory Manager initialized with SQLite, text optimization, and keyword extraction"); // Perform migration if needed await this.performMigration(); } else { // Initialize JSON await this.jsonOps.initialize(); logger.info("Enhanced Memory Manager initialized with JSON, text optimization, and keyword extraction"); } } async performMigration() { this.migrationInProgress = true; try { const memoryFiles = await this.migrationUtils.discoverMemoryFiles(); if (memoryFiles.length === 0) { logger.info("No existing memory files found - starting fresh"); this.migrationInProgress = false; return; } logger.info(`Found ${memoryFiles.length} memory files to migrate`); for (const { path: filePath, branch } of memoryFiles) { logger.info(`Migrating ${branch} branch from ${filePath}...`); // Parse JSON file const graph = await this.migrationUtils.parseJsonMemoryFile(filePath); if (graph.entities.length > 0 || graph.relations.length > 0) { // Import to SQLite await this.sqliteOps.importData(graph, branch); logger.info(`Migrated ${graph.entities.length} entities and ${graph.relations.length} relations to SQLite`); // Create backup before cleanup await this.migrationUtils.createMigrationBackup(filePath, branch); } } logger.info("Migration completed successfully"); } catch (error) { logger.error("Migration failed:", error); } finally { this.migrationInProgress = false; } } // Delegate to appropriate storage backend async createEntities(entities, branchName) { // Don't use SQLite during migration to avoid recursion if (this.useSQLite && !this.migrationInProgress) { logger.debug(`Using SQLite for entity creation`); // Apply optimization before storing const optimizedEntities = entities.map((entity) => { // Optimize each observation individually for better results const optimizedObservations = (entity.observations || []).map((obs) => { const optimization = this.optimizer.optimize(obs); return optimization.optimized; }); // Also optimize the overall entity content for keyword extraction const content = JSON.stringify({ name: entity.name, entityType: entity.entityType, observations: entity.observations, }); const optimization = this.optimizer.optimize(content); logger.debug(`Entity "${entity.name}" optimized: ${optimization.originalTokenCount} → ${optimization.tokenCount} tokens (${Math.round(optimization.compressionRatio * 100)}%)`); logger.debug(`Keywords: ${optimization.keywords.join(", ")}`); return { ...entity, observations: optimizedObservations, // Use optimized observations status: entity.status || "active", lastUpdated: new Date().toISOString(), _keywordData: { keywords: optimization.keywords, entities: optimization.entities, compressionRatio: optimization.compressionRatio, }, }; }); const result = await this.sqliteOps.createEntities(optimizedEntities, branchName); // Create backup const graph = await this.sqliteOps.exportBranch(branchName); await this.backupOps.createBackup(graph, branchName); return result; } else { logger.warn(`Using JSON fallback (migration: ${this.migrationInProgress})`); return await this.jsonOps.createEntities(entities, branchName); } } async updateEntity(entity, branchName) { if (this.useSQLite && !this.migrationInProgress) { return await this.sqliteOps.updateEntity(entity, branchName); } else { return await this.jsonOps.updateEntity(entity, branchName); } } async deleteEntities(entityNames, branchName) { if (this.useSQLite && !this.migrationInProgress) { return await this.sqliteOps.deleteEntities(entityNames, branchName); } else { return await this.jsonOps.deleteEntities(entityNames, branchName); } } async createRelations(relations, branchName) { if (this.useSQLite && !this.migrationInProgress) { return await this.sqliteOps.createRelations(relations, branchName); } else { return await this.jsonOps.createRelations(relations, branchName); } } async deleteRelations(relations, branchName) { if (this.useSQLite && !this.migrationInProgress) { return await this.sqliteOps.deleteRelations(relations, branchName); } else { return await this.jsonOps.deleteRelations(relations, branchName); } } async searchEntities(query, branchName, includeStatuses) { if (this.useSQLite && !this.migrationInProgress) { return await this.sqliteOps.searchEntities(query, branchName, includeStatuses); } else { return await this.jsonOps.searchEntities(query, branchName, includeStatuses); } } async findEntityByName(name, branchName) { if (this.useSQLite && !this.migrationInProgress) { return await this.sqliteOps.findEntityByName(name, branchName); } else { return await this.jsonOps.findEntityByName(name, branchName); } } async createBranch(branchName, purpose) { if (this.useSQLite && !this.migrationInProgress) { return await this.sqliteOps.createBranch(branchName, purpose); } else { return await this.jsonOps.createBranch(branchName, purpose); } } async deleteBranch(branchName) { if (this.useSQLite && !this.migrationInProgress) { return await this.sqliteOps.deleteBranch(branchName); } else { return await this.jsonOps.deleteBranch(branchName); } } async listBranches() { if (this.useSQLite && !this.migrationInProgress) { return await this.sqliteOps.listBranches(); } else { return await this.jsonOps.listBranches(); } } async exportBranch(branchName) { if (this.useSQLite && !this.migrationInProgress) { return await this.sqliteOps.exportBranch(branchName); } else { return await this.jsonOps.exportBranch(branchName); } } async importData(data, branchName) { if (this.useSQLite && !this.migrationInProgress) { return await this.sqliteOps.importData(data, branchName); } else { return await this.jsonOps.importData(data, branchName); } } async close() { // Clean up old backups before closing await this.backupOps.cleanupBackups(5); if (this.useSQLite) { await this.sqliteOps.close(); } await this.jsonOps.close(); logger.info("Hybrid Memory Manager closed"); } // Utility methods async suggestBranch(entityType, content) { return await this.sqliteOps.suggestBranch(entityType, content); } // Legacy compatibility methods for existing API async readGraph(branchName, includeStatuses, autoCrossContext = true) { return await this.exportBranch(branchName); } async searchNodes(query, branchName, includeStatuses, autoCrossContext = true) { return await this.searchEntities(query, branchName, includeStatuses); } async openNodes(names, branchName, includeStatuses, autoCrossContext = true) { // Use direct entity lookup instead of search const foundEntities = []; const allRelations = []; for (const name of names) { // Use findEntityByName for exact lookup const entity = await this.findEntityByName(name, branchName); if (entity) { // Check status filter if provided if (!includeStatuses || includeStatuses.includes(entity.status)) { foundEntities.push(entity); // Get all relations involving this entity from the full branch const branchGraph = await this.exportBranch(branchName); const entityRelations = branchGraph.relations.filter((r) => r.from === name || r.to === name); allRelations.push(...entityRelations); } } } // Remove duplicate relations const uniqueRelations = allRelations.filter((relation, index, arr) => arr.findIndex((r) => r.from === relation.from && r.to === relation.to && r.relationType === relation.relationType) === index); return { entities: foundEntities, relations: uniqueRelations }; } // Additional compatibility methods async addObservations(observations, branchName) { if (this.useSQLite && !this.migrationInProgress) { return await this.sqliteOps.addObservations(observations, branchName); } else { return await this.jsonOps.addObservations(observations, branchName); } } async updateEntityStatus(entityName, newStatus, statusReason, branchName) { const entity = await this.findEntityByName(entityName, branchName); if (!entity) { throw new Error(`Entity "${entityName}" not found`); } const updatedEntity = { ...entity, status: newStatus, statusReason, lastUpdated: new Date().toISOString(), }; await this.updateEntity(updatedEntity, branchName); } async deleteObservations(deletions, branchName) { if (this.useSQLite && !this.migrationInProgress) { return await this.sqliteOps.deleteObservations(deletions, branchName); } else { return await this.jsonOps.deleteObservations(deletions, branchName); } } async createCrossReference(entityName, targetBranch, targetEntityNames, sourceBranch) { if (this.useSQLite && !this.migrationInProgress) { return await this.sqliteOps.createCrossReference(entityName, targetBranch, targetEntityNames, sourceBranch); } else { return await this.jsonOps.createCrossReference(entityName, targetBranch, targetEntityNames, sourceBranch); } } async getCrossContext(entityNames, sourceBranch) { if (entityNames.length > 0) { return await this.searchEntities(entityNames.join(" "), sourceBranch); } else { return await this.exportBranch(sourceBranch); } } }

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/PrismAero/agentic-memory-server'

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