UNPKG

aicf-core

Version:

Universal AI Context Format (AICF) - Enterprise-grade AI memory infrastructure with 95.5% compression and zero semantic loss

379 lines 14.4 kB
import { promises as fs } from "node:fs"; import { join } from "node:path"; /** * MemoryDropOffAgent - Implements intelligent memory decay strategy * Manages memory hierarchy to prevent infinite growth while preserving important information */ export class MemoryDropOffAgent { projectRoot; decayConfig; constructor(options = {}) { this.projectRoot = options.projectRoot ?? process.cwd(); this.decayConfig = { recent: 7, medium: 30, old: 90, archive: 365, }; } /** * Process memory decay for all stored conversations */ async processMemoryDecay() { try { const aicfPath = join(this.projectRoot, ".aicf"); const conversationsFile = join(aicfPath, "conversations.aicf"); if (!(await this.fileExists(conversationsFile))) { return { applied: false, reason: "No conversations file found", itemsProcessed: 0, }; } const rawContent = await fs.readFile(conversationsFile, "utf-8"); const conversations = this.parseConversations(rawContent); if (conversations.length === 0) { return { applied: false, reason: "No conversations to process", itemsProcessed: 0, }; } const processedConversations = this.applyMemoryDecay(conversations); const compressedContent = this.formatCompressedConversations(processedConversations); await fs.writeFile(conversationsFile, compressedContent); await this.createBackupIfNeeded(conversationsFile, rawContent); return { applied: true, itemsProcessed: conversations.length, compressionRatio: this.calculateCompressionRatio(rawContent, compressedContent), decayStatistics: this.calculateDecayStatistics(processedConversations), }; } catch (error) { const err = error; return { applied: false, error: err.message, itemsProcessed: 0, }; } } /** * Parse conversations from AICF content */ parseConversations(content) { const conversations = []; const lines = content.split("\n"); let currentConversation = null; let currentSection = null; lines.forEach((line) => { line = line.trim(); if (line.startsWith("@CONVERSATION:")) { if (currentConversation) { conversations.push(currentConversation); } currentConversation = { id: line.replace("@CONVERSATION:", ""), metadata: {}, sections: {}, originalContent: [], timestamp: null, ageInDays: 0, }; currentSection = null; } else if (currentConversation) { currentConversation.originalContent.push(line); if (line.includes("=") && !line.startsWith("@")) { const [key, ...valueParts] = line.split("="); if (key) { const value = valueParts.join("="); currentConversation.metadata[key] = value; if (key === "timestamp_end") { currentConversation.timestamp = new Date(value); currentConversation.ageInDays = this.calculateAgeInDays(currentConversation.timestamp); } } } if (line.startsWith("@")) { currentSection = line; currentConversation.sections[currentSection] = []; } else if (currentSection && line.length > 0) { const section = currentConversation.sections[currentSection]; if (section) { section.push(line); } } } }); if (currentConversation) { conversations.push(currentConversation); } return conversations; } /** * Apply memory decay strategy based on conversation age */ applyMemoryDecay(conversations) { return conversations.map((conversation) => { const age = conversation.ageInDays; if (age <= this.decayConfig.recent) { return { ...conversation, decayLevel: "RECENT", compressed: false, }; } else if (age <= this.decayConfig.medium) { return { ...conversation, decayLevel: "MEDIUM", compressed: true, compressedContent: this.compressToKeyPoints(conversation), }; } else if (age <= this.decayConfig.old) { return { ...conversation, decayLevel: "OLD", compressed: true, compressedContent: this.compressToSingleLine(conversation), }; } else { return { ...conversation, decayLevel: "ARCHIVED", compressed: true, compressedContent: this.compressToCriticalOnly(conversation), }; } }); } /** * Compress conversation to key points (medium decay) */ compressToKeyPoints(conversation) { const keyPoints = []; keyPoints.push(`@CONVERSATION:${conversation.id}`); keyPoints.push(`timestamp=${conversation.metadata["timestamp_end"] ?? "unknown"}`); keyPoints.push(`age=${conversation.ageInDays}d`); const decisionsSection = conversation.sections["@DECISIONS"]; if (decisionsSection) { const criticalDecisions = decisionsSection.filter((line) => line.includes("IMPACT:CRITICAL") || line.includes("IMPACT:HIGH")); if (criticalDecisions.length > 0) { keyPoints.push("@DECISIONS_KEY"); criticalDecisions.slice(0, 3).forEach((decision) => { const parts = decision.split("|"); if (parts.length >= 3 && parts[0] && parts[2]) { keyPoints.push(`${parts[0]}|${parts[2]}`); } }); } } const insightsSection = conversation.sections["@INSIGHTS"]; if (insightsSection) { const criticalInsights = insightsSection.filter((line) => line.includes("|CRITICAL|") || line.includes("|HIGH|")); if (criticalInsights.length > 0) { keyPoints.push("@INSIGHTS_KEY"); criticalInsights.slice(0, 2).forEach((insight) => { const parts = insight.split("|"); if (parts.length >= 2 && parts[0] && parts[1]) { keyPoints.push(`${parts[0]}|${parts[1]}`); } }); } } const stateSection = conversation.sections["@STATE"]; if (stateSection) { const workingOn = stateSection.find((line) => line.startsWith("working_on=")); if (workingOn) { keyPoints.push("@STATE_FINAL"); keyPoints.push(workingOn); } } keyPoints.push(""); return keyPoints; } /** * Compress conversation to single line (old decay) */ compressToSingleLine(conversation) { const date = conversation.timestamp ? conversation.timestamp.toISOString().split("T")[0] : "unknown"; let criticalDecision = "no_decisions"; const decisionsSection = conversation.sections["@DECISIONS"]; if (decisionsSection) { const critical = decisionsSection.find((line) => line.includes("IMPACT:CRITICAL")); if (critical) { const parts = critical.split("|"); criticalDecision = parts[0] ?? "unknown_decision"; } } let outcome = "unknown"; const stateSection = conversation.sections["@STATE"]; if (stateSection) { const workingOn = stateSection.find((line) => line.startsWith("working_on=")); if (workingOn) { const parts = workingOn.split("="); outcome = parts[1] ?? "unknown"; } } return [`${date}|${criticalDecision}|outcome:${outcome}`, ""]; } /** * Compress to critical information only (archived decay) */ compressToCriticalOnly(conversation) { const criticalInfo = []; const decisionsSection = conversation.sections["@DECISIONS"]; if (decisionsSection) { const criticalDecisions = decisionsSection.filter((line) => line.includes("IMPACT:CRITICAL")); if (criticalDecisions.length > 0) { const date = conversation.timestamp ? conversation.timestamp.toISOString().split("T")[0] : "archived"; criticalInfo.push(`@ARCHIVED:${date}`); criticalDecisions.slice(0, 1).forEach((decision) => { const parts = decision.split("|"); if (parts.length > 0 && parts[0]) { criticalInfo.push(`CRITICAL:${parts[0]}`); } }); criticalInfo.push(""); } } return criticalInfo; } /** * Calculate age in days from timestamp */ calculateAgeInDays(timestamp) { if (!timestamp) return 999; const now = new Date(); const diffTime = Math.abs(now.getTime() - timestamp.getTime()); return Math.ceil(diffTime / (1000 * 60 * 60 * 24)); } /** * Format processed conversations back to AICF format */ formatCompressedConversations(conversations) { const lines = []; conversations.forEach((conversation) => { if (conversation.compressed && conversation.compressedContent) { lines.push(...conversation.compressedContent); } else { lines.push(`@CONVERSATION:${conversation.id}`); lines.push(...conversation.originalContent); lines.push(""); } }); return lines.join("\n"); } /** * Calculate compression ratio */ calculateCompressionRatio(originalContent, compressedContent) { const originalSize = originalContent.length; const compressedSize = compressedContent.length; if (originalSize === 0) return 0; const ratio = ((originalSize - compressedSize) / originalSize) * 100; return Math.round(ratio * 100) / 100; } /** * Calculate decay statistics */ calculateDecayStatistics(conversations) { const stats = { total: conversations.length, recent: 0, medium: 0, old: 0, archived: 0, }; conversations.forEach((conv) => { const level = conv.decayLevel?.toLowerCase(); if (level && level in stats) { stats[level]++; } }); return stats; } /** * Create backup of original content if needed */ async createBackupIfNeeded(filePath, content) { const backupPath = filePath + ".backup"; if (!(await this.fileExists(backupPath))) { await fs.writeFile(backupPath, content); } } /** * Check if file exists */ async fileExists(filePath) { try { await fs.access(filePath); return true; } catch { return false; } } /** * Get memory decay statistics without applying decay */ async getMemoryStatistics() { try { const aicfPath = join(this.projectRoot, ".aicf"); const conversationsFile = join(aicfPath, "conversations.aicf"); if (!(await this.fileExists(conversationsFile))) { return { error: "No conversations file found" }; } const rawContent = await fs.readFile(conversationsFile, "utf-8"); const conversations = this.parseConversations(rawContent); const stats = { totalConversations: conversations.length, totalSize: rawContent.length, ageDistribution: {}, decayRecommendations: { candidatesForDecay: 0, estimatedCompressionRatio: "0%", recommendDecay: false, }, }; conversations.forEach((conv) => { const age = conv.ageInDays; let category; if (age <= this.decayConfig.recent) category = "recent"; else if (age <= this.decayConfig.medium) category = "medium"; else if (age <= this.decayConfig.old) category = "old"; else category = "archived"; stats.ageDistribution[category] = (stats.ageDistribution[category] ?? 0) + 1; }); const candidatesForDecay = conversations.filter((conv) => conv.ageInDays > this.decayConfig.recent).length; stats.decayRecommendations = { candidatesForDecay, estimatedCompressionRatio: candidatesForDecay > 0 ? "60-80%" : "0%", recommendDecay: candidatesForDecay > 5, }; return stats; } catch (error) { const err = error; return { error: err.message }; } } } //# sourceMappingURL=memory-dropoff.js.map