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
JavaScript
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