feat: memory decay — move old archives to cold storage (v3.3)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-31 15:12:20 +10:00
co-authored by Claude Sonnet 4.6
parent a7aeb673b7
commit 85bb1be946
2 changed files with 56 additions and 1 deletions
+46
View File
@@ -589,6 +589,51 @@ function findRelevantHistory(workspaceDir, objective, maxItems = 3) {
return lines.join("\n"); return lines.join("\n");
} }
/**
* Move archives older than `decayDays` to a cold/ subdirectory.
* Files are preserved, not deleted — they just leave the active index.
*/
function decayOldArchives(workspaceDir, config = {}) {
const decayDays = config.archiveDecayDays ?? 30;
if (decayDays <= 0) return; // Disabled
const archiveDir = path.join(workspaceDir, "memory", "session_archive");
const coldDir = path.join(archiveDir, "cold");
let files;
try { files = fs.readdirSync(archiveDir).filter(f => f.endsWith(".md")); }
catch { return; }
const cutoff = Date.now() - (decayDays * 86400000);
let movedCount = 0;
for (const f of files) {
// Parse date from filename: YYYY-MM-DD_HH-MM.md
const dateMatch = f.match(/^(\d{4})-(\d{2})-(\d{2})/);
if (!dateMatch) continue;
const fileDate = new Date(
parseInt(dateMatch[1]),
parseInt(dateMatch[2]) - 1,
parseInt(dateMatch[3])
).getTime();
if (fileDate < cutoff) {
fs.mkdirSync(coldDir, { recursive: true });
const src = path.join(archiveDir, f);
const dst = path.join(coldDir, f);
try {
// Move: copy then delete
fs.copyFileSync(src, dst);
fs.unlinkSync(src);
movedCount++;
} catch {}
}
}
return movedCount;
}
function extractStateFromMessages(messages) { function extractStateFromMessages(messages) {
if (!messages || messages.length === 0) return null; if (!messages || messages.length === 0) return null;
@@ -819,6 +864,7 @@ const plugin = {
// Generate daily summary for previous day if needed // Generate daily summary for previous day if needed
generateDailySummary(ws, config); generateDailySummary(ws, config);
generateWeeklySummary(ws, config); generateWeeklySummary(ws, config);
decayOldArchives(ws, config);
const existing = readFile(statePath); const existing = readFile(statePath);
const newState = extractStateFromMessages(messages); const newState = extractStateFromMessages(messages);
+10 -1
View File
@@ -2,7 +2,7 @@
"id": "memory-continuity", "id": "memory-continuity",
"name": "Memory Continuity", "name": "Memory Continuity",
"description": "Preserves working state across /new, reset, compaction, and gateway restarts via a simple markdown checkpoint file.", "description": "Preserves working state across /new, reset, compaction, and gateway restarts via a simple markdown checkpoint file.",
"version": "3.2.0", "version": "3.3.0",
"source": "https://github.com/dtzp555-max/memory-continuity", "source": "https://github.com/dtzp555-max/memory-continuity",
"configSchema": { "configSchema": {
"type": "object", "type": "object",
@@ -57,6 +57,11 @@
"type": "number", "type": "number",
"default": 3, "default": 3,
"description": "Maximum number of relevant history items to inject" "description": "Maximum number of relevant history items to inject"
},
"archiveDecayDays": {
"type": "number",
"default": 30,
"description": "Days before archives move to cold storage (0 = disabled)"
} }
} }
}, },
@@ -100,6 +105,10 @@
"maxRelevanceItems": { "maxRelevanceItems": {
"label": "Max relevance items", "label": "Max relevance items",
"help": "How many related history entries to inject (2-5)" "help": "How many related history entries to inject (2-5)"
},
"archiveDecayDays": {
"label": "Archive decay days",
"help": "Move archives older than N days to cold/ subfolder (0 to disable)"
} }
} }
} }