diff --git a/nanobot/agent/memory.py b/nanobot/agent/memory.py index bf875282..9a5aa573 100644 --- a/nanobot/agent/memory.py +++ b/nanobot/agent/memory.py @@ -433,13 +433,33 @@ class MemoryStore: ] def compact_history(self) -> None: - """Drop oldest entries if the file exceeds *max_history_entries*.""" + """Drop oldest processed entries without discarding pending Dream input.""" if self.max_history_entries <= 0: return entries = self._read_entries() if len(entries) <= self.max_history_entries: return - kept = entries[-self.max_history_entries:] + last_dream_cursor = self.get_last_dream_cursor() + first_unprocessed = next( + ( + index + for index, entry in enumerate(entries) + if ( + (cursor := self._valid_cursor(entry.get("cursor"))) is not None + and cursor > last_dream_cursor + ) + ), + len(entries), + ) + keep_from = min(len(entries) - self.max_history_entries, first_unprocessed) + kept = entries[keep_from:] + if len(kept) > self.max_history_entries: + logger.warning( + "History compaction retained {} unprocessed entries beyond the configured " + "limit of {}", + len(kept), + self.max_history_entries, + ) self._write_entries(kept) # -- JSONL helpers ------------------------------------------------------- diff --git a/tests/agent/test_memory_store.py b/tests/agent/test_memory_store.py index 8de77208..bf8f4c72 100644 --- a/tests/agent/test_memory_store.py +++ b/tests/agent/test_memory_store.py @@ -235,11 +235,23 @@ class TestHistoryWithCursor: store.append_history("event 3") store.append_history("event 4") store.append_history("event 5") + store.set_last_dream_cursor(5) store.compact_history() entries = store.read_unprocessed_history(since_cursor=0) assert len(entries) == 2 assert entries[0]["cursor"] in {4, 5} + def test_compact_history_preserves_entries_after_dream_cursor(self, tmp_path): + store = MemoryStore(tmp_path, max_history_entries=50) + for index in range(1, 101): + store.append_history(f"event {index}") + store.set_last_dream_cursor(20) + + store.compact_history() + + entries = store.read_unprocessed_history(since_cursor=0) + assert [entry["cursor"] for entry in entries] == list(range(21, 101)) + def test_write_entries_uses_atomic_write(self, tmp_path): """_write_entries uses temp file + os.replace for atomicity.""" store = MemoryStore(tmp_path)