From 791c7fd505e653b87801d858980c1e116f0b6852 Mon Sep 17 00:00:00 2001 From: chengyongru Date: Mon, 13 Jul 2026 10:31:25 +0800 Subject: [PATCH] fix(memory): prune encoded Dream sessions --- nanobot/agent/memory.py | 16 +++++----- tests/agent/test_dream_session.py | 50 ++++++++++++++++++++++++------- 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/nanobot/agent/memory.py b/nanobot/agent/memory.py index 7e760269..c5785b45 100644 --- a/nanobot/agent/memory.py +++ b/nanobot/agent/memory.py @@ -16,7 +16,7 @@ from typing import TYPE_CHECKING, Any, Callable, Iterator from loguru import logger from nanobot.runtime_context import public_history_messages -from nanobot.session.manager import Session +from nanobot.session.manager import Session, SessionManager from nanobot.utils.gitstore import GitStore from nanobot.utils.helpers import ( ensure_dir, @@ -31,7 +31,6 @@ from nanobot.utils.helpers import ( from nanobot.utils.prompt_templates import render_template if TYPE_CHECKING: - from nanobot.session.manager import SessionManager from nanobot.utils.llm_runtime import LLMRuntime # --------------------------------------------------------------------------- @@ -705,12 +704,15 @@ class MemoryStore: def prune_dream_sessions(sessions_dir: Path, *, keep: int = 10) -> None: """Remove the oldest Dream session files, keeping only the N most recent. - Only files matching ``dream_*.jsonl`` are considered. Non-dream session - files are never touched. + Only current base64url-encoded Dream session keys are considered. + Non-dream session files are never touched. """ - dream_files = sorted( - sessions_dir.glob("dream_*.jsonl"), key=lambda p: p.stat().st_mtime, - ) + dream_files = [] + for path in sessions_dir.glob("*.jsonl"): + decoded_key = SessionManager._decode_storage_key(path.stem) + if decoded_key is not None and decoded_key.startswith("dream:"): + dream_files.append(path) + dream_files.sort(key=lambda p: p.stat().st_mtime) if len(dream_files) <= keep: return diff --git a/tests/agent/test_dream_session.py b/tests/agent/test_dream_session.py index 29b538f5..89351a54 100644 --- a/tests/agent/test_dream_session.py +++ b/tests/agent/test_dream_session.py @@ -1,8 +1,10 @@ """Tests for Dream session key generation and rotation.""" + from datetime import datetime, timedelta from unittest.mock import patch from nanobot.agent.memory import MemoryStore +from nanobot.session.manager import SessionManager class TestDreamSessionKey: @@ -31,11 +33,11 @@ class TestPruneDreamSessions: sessions_dir.mkdir() base_time = time.time() - 100 + dream_paths = [] for i in range(15): key = f"dream:20260528-{100000 + i:06d}" - safe_key = key.replace(":", "_") - path = sessions_dir / f"{safe_key}.jsonl" + path = sessions_dir / f"{SessionManager._storage_key(key)}.jsonl" path.write_text( f'{{"_type": "metadata", "key": "{key}", ' f'"created_at": "2026-05-28T10:00:{i:02d}", ' @@ -43,29 +45,57 @@ class TestPruneDreamSessions: encoding="utf-8", ) os.utime(path, (base_time + i, base_time + i)) + dream_paths.append(path) normal_path = sessions_dir / "telegram_123.jsonl" normal_path.write_text('{"_type": "metadata"}\n', encoding="utf-8") MemoryStore.prune_dream_sessions(sessions_dir, keep=10) - dream_files = sorted(sessions_dir.glob("dream_*.jsonl")) - assert len(dream_files) == 10 - remaining_keys = [f.stem for f in dream_files] - assert "dream_20260528-100000" not in remaining_keys - assert "dream_20260528-100014" in remaining_keys + assert [path.exists() for path in dream_paths] == [False] * 5 + [True] * 10 assert normal_path.exists() + def test_ignores_legacy_dream_filenames(self, tmp_path): + import os + import time + + sessions_dir = tmp_path / "sessions" + sessions_dir.mkdir() + base_time = time.time() - 100 + current_paths = [] + + for i in range(2): + key = f"dream:20260713-{100000 + i:06d}" + path = sessions_dir / f"{SessionManager._storage_key(key)}.jsonl" + path.write_text( + f'{{"_type": "metadata", "key": "{key}"}}\n', + encoding="utf-8", + ) + os.utime(path, (base_time + i, base_time + i)) + current_paths.append(path) + + legacy_path = sessions_dir / "dream_20260713-095959.jsonl" + legacy_path.write_text( + '{"_type": "metadata", "key": "dream:20260713-095959"}\n', + encoding="utf-8", + ) + os.utime(legacy_path, (base_time - 1, base_time - 1)) + + MemoryStore.prune_dream_sessions(sessions_dir, keep=1) + + assert [path.exists() for path in current_paths] == [False, True] + assert legacy_path.exists() + def test_noop_when_under_limit(self, tmp_path): sessions_dir = tmp_path / "sessions" sessions_dir.mkdir() for i in range(3): key = f"dream:20260528-{100000 + i:06d}" - safe_key = key.replace(":", "_") - (sessions_dir / f"{safe_key}.jsonl").write_text("{}", encoding="utf-8") + path = sessions_dir / f"{SessionManager._storage_key(key)}.jsonl" + path.write_text("{}", encoding="utf-8") MemoryStore.prune_dream_sessions(sessions_dir, keep=10) - assert len(list(sessions_dir.glob("dream_*.jsonl"))) == 3 + assert len(list(sessions_dir.glob("*.jsonl"))) == 3 def test_empty_dir_noop(self, tmp_path): sessions_dir = tmp_path / "sessions"