Skip to content

fix: advance dream cursor when Dream is disabled to prevent prompt bloat (#4242) - #4481

Merged
Re-bin merged 3 commits into
HKUDS:mainfrom
axelray-dev:fix/issue-4242-dream-cursor
Jun 25, 2026
Merged

Re-bin merged 3 commits into
HKUDS:mainfrom
axelray-dev:fix/issue-4242-dream-cursor

Conversation

@axelray-dev

Copy link
Copy Markdown
Contributor

Fixes #4242.

Problem: When dream.enabled is set to false, the Dream cron job is correctly not registered, but the dream cursor (.dream_cursor) is never advanced. The read_recent_history_for_prompt() method in context.py uses this cursor to determine which history entries are unprocessed. Since the cursor stays at its initial value (0), every history entry appears unprocessed and gets injected into the system prompt via the Recent History section. This grows without bound over time, wasting tokens in every LLM call.

Fix: Add get_latest_cursor() method to MemoryStore that returns the cursor of the most recent history entry. At gateway startup, when Dream is disabled, fast-forward the dream cursor to the latest entry so no stale entries accumulate.

Testing: 47 tests pass in test_memory_store.py, including 4 new tests for get_latest_cursor(). Ruff check passes on both changed files.

Scope: 3 files changed (memory.py, commands.py, test_memory_store.py). 25 lines added. No behavior change when Dream is enabled.

…oat (HKUDS#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.

@chengyongru chengyongru left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated PR review by nanobot. This is not a human maintainer review or approval.

Side-effect analysis:

Dream cursor fast-forward on startup when Dream is disabled (commands.py:1149)

The set_last_dream_cursor(get_latest_cursor()) call overwrites the .dream_cursor file at every gateway start when Dream is disabled. If a user later enables Dream, the cursor has already advanced past all entries created while Dream was off — those entries will never be consolidated by Dream. This is a one-way operation: once the cursor advances, there's no mechanism to rewind it. This is likely the intended tradeoff (those entries were already injected into prompts while Dream was off), but worth being aware of.

Within-session accumulation (memory.py:482)

The cursor is only advanced at gateway startup. During a single gateway uptime session, new history entries accumulate with the cursor frozen at the startup value. The existing caps (_MAX_RECENT_HISTORY=50, _MAX_HISTORY_TOKENS=8000) handle the prompt size, so the behavior matches what happens when Dream is enabled within a session. This is consistent.

get_latest_cursor() correctness

max(self._next_cursor() - 1, 0) correctly returns the cursor of the most recent entry. _next_cursor() computes the next available cursor based on both the counter file and the last JSONL entry, so subtracting 1 gives the latest committed entry's cursor. The max(..., 0) guard handles the empty-history edge case. Verified against read_recent_history_for_prompt(since_cursor=N) semantics — all entries with cursor <= N will be treated as processed.

Test coverage is thorough: empty history, populated history, empty file, and consistency with _next_cursor().

Comment thread nanobot/cli/commands.py Outdated
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())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side effect: this runs on every gateway start when Dream is disabled, overwriting the cursor each time. If Dream is later enabled, all entries created during the disabled period are permanently skipped by consolidation.

Address review: avoid overwriting cursor on every restart when Dream
is disabled. Now only advances if current cursor is behind the latest
position, so repeated restarts don't permanently skip entries.
@axelray-dev

Copy link
Copy Markdown
Contributor Author

Good catch. Fixed: the cursor now only advances when it is behind the latest position. Repeated restarts with Dream disabled no longer overwrite the cursor, so entries created during the disabled period are preserved.

@chengyongru chengyongru left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated PR review by nanobot. This is not a human maintainer review or approval.

Side effect analysis:

Adds get_latest_cursor() to MemoryStore and advances the dream cursor to latest on nanobot status when Dream is disabled, preventing unprocessed history from accumulating in system prompt.

  • max(self._next_cursor() - 1, 0) is correct — _next_cursor reads the next write position from the file length, so -1 gives the last written cursor. Guard for empty history file returns 0.
  • The cursor advance only triggers on nanobot status CLI command. At gateway startup, the existing Dream cursor fast-forward already handles the O(all history) scan. These two mechanisms are complementary.
  • No impact on Dream-enabled deployments — the if guard means nothing changes when Dream is active.
  • Good test coverage including edge cases (empty file, single entry, multiple entries).

No correctness, security, or performance concerns.

@Re-bin Re-bin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. This fixes the Dream-disabled prompt-bloat path: when Dream is off, nanobot advances the Dream cursor to the latest history cursor so disabled deployments do not keep replaying unconsolidated backlog into future Dream prompts.

I also pushed a small follow-up that keeps yongru's edge case locked down: the cursor only advances when it is behind, and never gets overwritten backwards on later gateway starts. The change stays localized to memory/gateway startup bookkeeping and does not affect the agent loop.

Validated:

  • python -m pytest tests/agent/test_memory_store.py::TestDreamCursor tests/cli/test_commands.py::test_disabled_dream_cursor_only_advances_when_behind -q
  • python -m ruff check nanobot/cli/commands.py tests/cli/test_commands.py tests/agent/test_memory_store.py
  • GitHub Actions is green across the full matrix.

@Re-bin
Re-bin merged commit 34f776b into HKUDS:main Jun 25, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Disabling dream.enabled still injects all chat history into system prompt via Recent History section

3 participants