From 6c880a6691016fbcb434ca9c3829a48fd1181850 Mon Sep 17 00:00:00 2001 From: axelray-dev <110029405+axelray-dev@users.noreply.github.com> Date: Wed, 24 Jun 2026 01:29:53 +0800 Subject: [PATCH] fix: advance dream cursor when Dream is disabled to prevent prompt bloat (#4242) When dream.enabled is false, the Dream cron job never runs, so the dream cursor (.dream_cursor) stays at its initial value (0). This causes read_recent_history_for_prompt() to treat every history entry as unprocessed, injecting the full chat history into every system prompt and growing without bound. Fix: fast-forward the dream cursor to the latest history entry at gateway startup when Dream is disabled. --- nanobot/agent/memory.py | 3 +++ nanobot/cli/commands.py | 1 + tests/agent/test_memory_store.py | 21 +++++++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/nanobot/agent/memory.py b/nanobot/agent/memory.py index d1d49ef5..d2d32a2b 100644 --- a/nanobot/agent/memory.py +++ b/nanobot/agent/memory.py @@ -479,6 +479,9 @@ class MemoryStore: def set_last_dream_cursor(self, cursor: int) -> None: self._dream_cursor_file.write_text(str(cursor), encoding="utf-8") + def get_latest_cursor(self) -> int: + return max(self._next_cursor() - 1, 0) + def build_dream_prompt(self, *, max_entries: int = 20) -> tuple[str, int] | None: """Build the Dream prompt with unprocessed history context. diff --git a/nanobot/cli/commands.py b/nanobot/cli/commands.py index 2332fe2e..13ef6f2a 100644 --- a/nanobot/cli/commands.py +++ b/nanobot/cli/commands.py @@ -1165,6 +1165,7 @@ def _run_gateway( console.print(f"[green]✓[/green] Dream: {dream_cfg.describe_schedule()}") else: console.print("[yellow]○[/yellow] Dream: disabled") + agent.context.memory.set_last_dream_cursor(agent.context.memory.get_latest_cursor()) # Register Heartbeat system job (idempotent on restart) if hb_cfg.enabled: diff --git a/tests/agent/test_memory_store.py b/tests/agent/test_memory_store.py index 239c62a8..e99ea869 100644 --- a/tests/agent/test_memory_store.py +++ b/tests/agent/test_memory_store.py @@ -330,6 +330,27 @@ class TestDreamCursor: def test_initial_cursor_is_zero(self, store): assert store.get_last_dream_cursor() == 0 + def test_returns_zero_when_empty(self, store): + assert store.get_latest_cursor() == 0 + + def test_returns_cursor_of_last_entry(self, store): + store.append_history("event 1") + store.append_history("event 2") + store.append_history("event 3") + + assert store.get_latest_cursor() == 3 + + def test_returns_zero_when_no_entries(self, store): + store.history_file.write_text("", encoding="utf-8") + + assert store.get_latest_cursor() == 0 + + def test_matches_next_cursor_minus_one(self, store): + store.append_history("event 1") + store.append_history("event 2") + + assert store.get_latest_cursor() == max(store._next_cursor() - 1, 0) + def test_set_and_get_cursor(self, store): store.set_last_dream_cursor(5) assert store.get_last_dream_cursor() == 5