From 85bb1be946b3fb514e9e0470a9a925999294803a Mon Sep 17 00:00:00 2001 From: dtzp555 Date: Tue, 31 Mar 2026 15:12:20 +1000 Subject: [PATCH] =?UTF-8?q?feat:=20memory=20decay=20=E2=80=94=20move=20old?= =?UTF-8?q?=20archives=20to=20cold=20storage=20(v3.3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- index.js | 46 ++++++++++++++++++++++++++++++++++++++++++++ openclaw.plugin.json | 11 ++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 1dfb70c..81bd710 100644 --- a/index.js +++ b/index.js @@ -589,6 +589,51 @@ function findRelevantHistory(workspaceDir, objective, maxItems = 3) { 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) { if (!messages || messages.length === 0) return null; @@ -819,6 +864,7 @@ const plugin = { // Generate daily summary for previous day if needed generateDailySummary(ws, config); generateWeeklySummary(ws, config); + decayOldArchives(ws, config); const existing = readFile(statePath); const newState = extractStateFromMessages(messages); diff --git a/openclaw.plugin.json b/openclaw.plugin.json index 3c47241..997a8fc 100644 --- a/openclaw.plugin.json +++ b/openclaw.plugin.json @@ -2,7 +2,7 @@ "id": "memory-continuity", "name": "Memory Continuity", "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", "configSchema": { "type": "object", @@ -57,6 +57,11 @@ "type": "number", "default": 3, "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": { "label": "Max relevance items", "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)" } } } \ No newline at end of file