fix(memory): prune encoded Dream sessions

This commit is contained in:
chengyongru
2026-07-13 12:07:29 +08:00
committed by Xubin Ren
parent 6d406d93c9
commit 791c7fd505
2 changed files with 49 additions and 17 deletions
+9 -7
View File
@@ -16,7 +16,7 @@ from typing import TYPE_CHECKING, Any, Callable, Iterator
from loguru import logger from loguru import logger
from nanobot.runtime_context import public_history_messages 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.gitstore import GitStore
from nanobot.utils.helpers import ( from nanobot.utils.helpers import (
ensure_dir, ensure_dir,
@@ -31,7 +31,6 @@ from nanobot.utils.helpers import (
from nanobot.utils.prompt_templates import render_template from nanobot.utils.prompt_templates import render_template
if TYPE_CHECKING: if TYPE_CHECKING:
from nanobot.session.manager import SessionManager
from nanobot.utils.llm_runtime import LLMRuntime from nanobot.utils.llm_runtime import LLMRuntime
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -705,12 +704,15 @@ class MemoryStore:
def prune_dream_sessions(sessions_dir: Path, *, keep: int = 10) -> None: def prune_dream_sessions(sessions_dir: Path, *, keep: int = 10) -> None:
"""Remove the oldest Dream session files, keeping only the N most recent. """Remove the oldest Dream session files, keeping only the N most recent.
Only files matching ``dream_*.jsonl`` are considered. Non-dream session Only current base64url-encoded Dream session keys are considered.
files are never touched. Non-dream session files are never touched.
""" """
dream_files = sorted( dream_files = []
sessions_dir.glob("dream_*.jsonl"), key=lambda p: p.stat().st_mtime, 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: if len(dream_files) <= keep:
return return
+40 -10
View File
@@ -1,8 +1,10 @@
"""Tests for Dream session key generation and rotation.""" """Tests for Dream session key generation and rotation."""
from datetime import datetime, timedelta from datetime import datetime, timedelta
from unittest.mock import patch from unittest.mock import patch
from nanobot.agent.memory import MemoryStore from nanobot.agent.memory import MemoryStore
from nanobot.session.manager import SessionManager
class TestDreamSessionKey: class TestDreamSessionKey:
@@ -31,11 +33,11 @@ class TestPruneDreamSessions:
sessions_dir.mkdir() sessions_dir.mkdir()
base_time = time.time() - 100 base_time = time.time() - 100
dream_paths = []
for i in range(15): for i in range(15):
key = f"dream:20260528-{100000 + i:06d}" key = f"dream:20260528-{100000 + i:06d}"
safe_key = key.replace(":", "_") path = sessions_dir / f"{SessionManager._storage_key(key)}.jsonl"
path = sessions_dir / f"{safe_key}.jsonl"
path.write_text( path.write_text(
f'{{"_type": "metadata", "key": "{key}", ' f'{{"_type": "metadata", "key": "{key}", '
f'"created_at": "2026-05-28T10:00:{i:02d}", ' f'"created_at": "2026-05-28T10:00:{i:02d}", '
@@ -43,29 +45,57 @@ class TestPruneDreamSessions:
encoding="utf-8", encoding="utf-8",
) )
os.utime(path, (base_time + i, base_time + i)) os.utime(path, (base_time + i, base_time + i))
dream_paths.append(path)
normal_path = sessions_dir / "telegram_123.jsonl" normal_path = sessions_dir / "telegram_123.jsonl"
normal_path.write_text('{"_type": "metadata"}\n', encoding="utf-8") normal_path.write_text('{"_type": "metadata"}\n', encoding="utf-8")
MemoryStore.prune_dream_sessions(sessions_dir, keep=10) MemoryStore.prune_dream_sessions(sessions_dir, keep=10)
dream_files = sorted(sessions_dir.glob("dream_*.jsonl")) assert [path.exists() for path in dream_paths] == [False] * 5 + [True] * 10
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 normal_path.exists() 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): def test_noop_when_under_limit(self, tmp_path):
sessions_dir = tmp_path / "sessions" sessions_dir = tmp_path / "sessions"
sessions_dir.mkdir() sessions_dir.mkdir()
for i in range(3): for i in range(3):
key = f"dream:20260528-{100000 + i:06d}" key = f"dream:20260528-{100000 + i:06d}"
safe_key = key.replace(":", "_") path = sessions_dir / f"{SessionManager._storage_key(key)}.jsonl"
(sessions_dir / f"{safe_key}.jsonl").write_text("{}", encoding="utf-8") path.write_text("{}", encoding="utf-8")
MemoryStore.prune_dream_sessions(sessions_dir, keep=10) 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): def test_empty_dir_noop(self, tmp_path):
sessions_dir = tmp_path / "sessions" sessions_dir = tmp_path / "sessions"