Skip to content

feat(memory): two-stage memory system with Dream consolidation - #2717

Merged
Re-bin merged 10 commits into
mainfrom
feat/dream
Apr 4, 2026
Merged

Re-bin merged 10 commits into
mainfrom
feat/dream

Conversation

@chengyongru

@chengyongru chengyongru commented Apr 1, 2026 •

Copy link
Copy Markdown
Collaborator
af997b5c-a02a-4e79-ae29-2eb26c6c6a35

Replace the single-stage MemoryConsolidator with a two-stage memory system — Consolidator (lightweight, per-turn) + Dream (heavyweight, cron-scheduled) — backed by git version control for full change history and rollback.

Motivation

The old MemoryConsolidator had three fundamental problems:

  1. Coupled responsibilities — Compression and long-term memory updates happened in a single LLM call via tool_choice=forced on save_memory. Some providers don't support tool_choice; the LLM had to simultaneously summarize and rewrite the entire MEMORY.md, degrading quality as memory grew.

  2. No incremental updates — Every consolidate required the LLM to output the full MEMORY.md content. Token cost grew linearly with memory size, and existing facts were easily lost.

  3. No change history — Memory files were plain Markdown with no version tracking. Corrupt or unintended edits were irreversible.

What changed

Architecture

Consolidator (per-turn)              Dream (cron-scheduled)           GitStore (version control)
+----------------------------+       +----------------------------+   +---------------------------+
| token over budget → LLM    |       | Phase 1: analyze history    |   | dulwich-backed .git repo  |
| summarize evicted messages  |──────▶|   vs existing memory files  |   | auto_commit on Dream run  |
| → history.jsonl            |       | Phase 2: AgentRunner        |   | /dream-log: view changes  |
| (plain text, no tool_call)  |       |   + read_file/edit_file     |   | /dream-restore: rollback  |
+----------------------------+       |   → surgical incremental    |   +---------------------------+
                                     |     edit of memory files    |
                                     +----------------------------+

MemoryStore rewrite

  • Decoupled from LLM calls into a pure file I/O layer
  • history.jsonl replaces HISTORY.md with auto-incrementing cursor per entry
  • Cursor-based tracking prevents re-processing
  • Automatic compaction (max 1000 entries)

Consolidator simplified

  • No longer calls save_memory via forced tool_choice
  • Uses plain-text LLM summarization (tools=None) for full provider compatibility
  • Prompt adds Solutions category for capturing trial-and-error workflows
  • Priority ranking: user corrections > solutions > decisions > events > environment facts

Dream (new)

Two-phase processor, cron-scheduled (default: every 2 hours):

Phase Description
Phase 1 Plain LLM call comparing history.jsonl against MEMORY.md / SOUL.md / USER.md, outputs [FILE] atomic fact lines
Phase 2 Delegates to AgentRunner with read_file / edit_file tools for surgical edits (up to 10 tool calls)

Cursor always advances to prevent re-processing.

GitStore (new)

  • Wraps dulwich for zero-dependency git operations (no system git required)
  • Auto-commits memory file changes after each Dream run
  • .gitignore auto-generated to only track memory files
  • Supports log(), diff_commits(), revert() for browsing and rollback

New commands

Command Description
/dream Manually trigger a Dream consolidation run
/dream-log [sha] Show what the last Dream changed (diff of latest commit, or a specific commit)
/dream-restore [sha] List recent commits, or revert memory files to a previous state

DreamConfig

class DreamConfig(Base):
    cron: str = "0 */2 * * *"      # Every 2 hours
    model: str | None = None       # Optional model override
    max_batch_size: int = 20       # Max history entries per run
    max_iterations: int = 10       # Max tool calls in Phase 2

CronService.register_system_job()

Supports idempotent registration of internal system jobs that survive gateway restarts.

Breaking changes

  • HISTORY.md → history.jsonl: legacy history file is no longer read; existing content is not migrated
  • SOUL.md, USER.md, MEMORY.md are now marked as "auto-managed by Dream — do not edit directly" in the system prompt and memory skill

Files changed

Core: memory.py (+586/-178), git_store.py (+299 new), loop.py, context.py, __init__.py
Config & Commands: schema.py, commands.py, builtin.py
Cron: cron/service.py
Skills: skills/memory/SKILL.md
Tests: +test_consolidator.py, +test_dream.py, +test_memory_store.py, +test_git_store.py, -test_memory_consolidation_types.py (+533/-478)

Test plan

  • pytest tests/agent/test_memory_store.py — MemoryStore I/O, cursor, compaction
  • pytest tests/agent/test_consolidator.py — Consolidator summarization logic
  • pytest tests/agent/test_dream.py — Dream Phase 1/2, cursor advancement
  • pytest tests/agent/test_git_store.py — GitStore init, commit, log, diff, revert
  • pytest tests/agent/test_consolidate_offset.py — Session consolidation boundary selection
  • pytest tests/agent/test_loop_consolidation_tokens.py — Token-budget-triggered consolidation
  • Manual: send messages, verify history.jsonl entries are appended
  • Manual: /dream triggers Dream, verify MEMORY.md/USER.md/SOUL.md updates
  • Manual: /dream-log displays change log with diff
  • Manual: /dream-restore lists commits and reverts to a previous state

@chengyongru chengyongru closed this Apr 1, 2026
@chengyongru chengyongru reopened this Apr 1, 2026
@chengyongru
chengyongru requested a review from Re-bin April 1, 2026 16:26
@chengyongru chengyongru added the enhancement New feature or request label Apr 1, 2026
Replace single-stage MemoryConsolidator with a two-stage architecture:

- Consolidator: lightweight token-budget triggered summarization,
  appends to HISTORY.md with cursor-based tracking
- Dream: cron-scheduled two-phase processor that analyzes HISTORY.md
  and updates SOUL.md, USER.md, MEMORY.md via AgentRunner with
  edit_file tools for surgical, fault-tolerant updates

New files: MemoryStore (pure file I/O), Dream class, DreamConfig,
/dream and /dream-log commands. 89 tests covering all components.
Add "Solutions" category to consolidate prompt so trial-and-error
workflows that reach a working approach are captured in history for
Dream to persist. Remove overly broad "debug steps" skip rule that
discarded these valuable findings.
@chengyongru
chengyongru force-pushed the feat/dream branch 3 times, most recently from cacedc0 to ab2ecc8 Compare April 2, 2026 16:25
- Add GitStore class wrapping dulwich for memory file versioning
- Auto-commit memory changes during Dream consolidation
- Add /dream-log and /dream-restore commands for history browsing
- Pass tracked_files as constructor param, generate .gitignore dynamically
@Re-bin

Re-bin commented Apr 3, 2026

Copy link
Copy Markdown
Collaborator

I like this! @chengyongru

@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.

This PR is in a very good place now.

I pulled it down, merged the latest main, and went through it from both the code side and the product side. What I like here is that this does not just add “more memory features” - it turns memory into a cleaner system with clearer boundaries:

  • short-term context is compressed by the Consolidator
  • long-term memory is refined by Dream
  • durable memory state lives in SOUL.md, USER.md, and memory/MEMORY.md
  • historical archive moves to memory/history.jsonl
  • memory changes become inspectable and reversible through built-in Dream commands

On top of that, I made a few follow-up improvements to keep the feature feeling natural and production-ready:

  • hardened the legacy HISTORY.md -> history.jsonl migration path, including corner cases and partial-history preservation
  • fixed the migration edge case where an empty pre-created history.jsonl could accidentally block legacy migration
  • improved /dream-log and /dream-restore output so they are much more user-friendly and easier to trust
  • added /dream, /dream-log, and /dream-restore into Telegram command registration and help output
  • protected the system-managed Dream cron job so users can inspect it but not remove it by accident
  • clarified Dream configuration semantics by moving toward intervalH and modelOverride, which read much closer to product language than implementation language
  • renamed the internal gitstore module for a cleaner shape
  • updated docs so the memory model, in-chat commands, and source-vs-stable release expectations are all easier to understand

What matters most to me is that the architecture stayed disciplined while the feature grew:

  • the main loop only wires the system in; it does not absorb the memory logic
  • migration stays in the store layer
  • cron protection stays in the cron layer
  • user-facing explanations stay in commands/docs
  • versioning stays isolated behind GitStore

So this still feels like the simplest version of the right design, not the beginning of a pile of special cases.

From a user perspective, this is also the right kind of improvement: mostly invisible when you do not need it, but very powerful when you do. Existing users do not have to learn the internals to benefit from it, and the upgrade path is now much safer.

I also ran the test suite and the branch is green on my side:

  • full test suite: 975 passed, 1 skipped
  • targeted regression coverage was added for migration, Dream UX, cron protection, Telegram command exposure, and the new config semantics

At this point, I think this PR is merge-ready.

@Re-bin
Re-bin merged commit b9ee236 into main Apr 4, 2026
3 checks passed
@Re-bin
Re-bin deleted the feat/dream branch April 4, 2026 10:18
Re-bin pushed a commit that referenced this pull request Apr 22, 2026
`resolve_config_env_vars` unconditionally dumped the config via
`model_dump(mode="json")` and revalidated it, which silently dropped
any field declared with `exclude=True` (e.g. `DreamConfig.cron` —
introduced by the Dream rename refactor in #2717). Result:
`agents.defaults.dream.cron` was never honored at runtime — the gateway
always fell back to the default `every 2h` schedule even when `cron`
was set in config.json.

Fix: skip the roundtrip entirely when the config has no `${VAR}`
references. Env-var interpolation still works unchanged when refs
exist; the legacy `cron` override now survives the common case of
fully-resolved config.

Regression test covers the bug path.
MaoChen1980 pushed a commit to MaoChen1980/nanobot-mg that referenced this pull request May 18, 2026
… consolidation

feat(memory): two-stage memory system with Dream consolidation
MaoChen1980 pushed a commit to MaoChen1980/nanobot-mg that referenced this pull request May 18, 2026
`resolve_config_env_vars` unconditionally dumped the config via
`model_dump(mode="json")` and revalidated it, which silently dropped
any field declared with `exclude=True` (e.g. `DreamConfig.cron` —
introduced by the Dream rename refactor in HKUDS#2717). Result:
`agents.defaults.dream.cron` was never honored at runtime — the gateway
always fell back to the default `every 2h` schedule even when `cron`
was set in config.json.

Fix: skip the roundtrip entirely when the config has no `${VAR}`
references. Env-var interpolation still works unchanged when refs
exist; the legacy `cron` override now survives the common case of
fully-resolved config.

Regression test covers the bug path.
sammyne added a commit to sammyne/nanobot-rs that referenced this pull request May 29, 2026
Task 0: Add docs/dreaming.md covering the two-stage memory system
design — Consolidator (per-turn text summary) + Dream (cron-scheduled
LLM-driven incremental memory editing with git versioning).

Task 1: Add DreamConfig to AgentDefaults with fields: cron (default
every 2 hours), model (optional override), max_batch_size (20),
max_iterations (10). Currently a no-op config consumed by future
Dream implementation.

Upstream: HKUDS/nanobot#2717 (phase 1 of 4)
sammyne added a commit to sammyne/nanobot-rs that referenced this pull request May 29, 2026
Add History struct for structured JSONL history storage:
- HistoryEntry with cursor, timestamp, content fields
- append() with auto-incrementing cursor
- read_all() and read_since(cursor) for retrieval
- Automatic compaction when entries exceed 1000

This is the foundation for the MemoryStore rewrite (Task 2).
Does not yet replace HISTORY.md — that happens in the next commit.

Upstream: HKUDS/nanobot#2717 (phase 2 prep)
sammyne added a commit to sammyne/nanobot-rs that referenced this pull request May 29, 2026
Task 2+3: MemoryStore refactor and Consolidator simplification.

- Remove provider/options fields from MemoryStore (pure I/O layer now)
- Replace HISTORY.md with history.jsonl via History module (cursor tracking)
- Move consolidation to standalone consolidate_memory() function
- Simplify consolidation: plain-text LLM summary instead of
  tool_choice=Required on save_memory tool
- Remove SaveMemoryArgs, create_save_memory_tool, NoToolCall/ToolParse errors
- Update ContextBuilder::new() to no longer require Options param
- Update agent loop try_consolidate() and NewCmd to use new API
- Update all tests and AGENTS.md docs

Upstream: HKUDS/nanobot#2717 (phase 2)
sammyne added a commit to sammyne/nanobot-rs that referenced this pull request May 29, 2026
Task 4: Implement GitStore using git CLI (std::process::Command).

- init: check git availability (error if missing), git init, .gitignore,
  local user config for commits
- commit: git add -A + git commit (no-op if no changes)
- log: parse git log --format into Vec<CommitInfo>
- diff: git diff-tree for any commit including root
- revert: checkout files from target commit, commit the revert

Zero new dependencies — uses system git command only.
6 unit tests covering init, idempotency, commit, log, diff, revert.

Upstream: HKUDS/nanobot#2717 (phase 3)
sammyne added a commit to sammyne/nanobot-rs that referenced this pull request May 29, 2026
Task 5: Implement Dream struct with Phase 1 (analyze) + Phase 2 (edit).

- Phase 1: LLM compares new history entries against MEMORY.md/SOUL.md/
  USER.md, outputs [FILE] fact instructions
- Phase 2: simplified append-only editing (direct file append, future
  upgrade to re_act-based surgical edits)
- Dream cursor (.dream_cursor file) tracks processed history entries
- Git commit after each Dream run via GitStore
- DreamResult reports entries_processed and files_changed

3 unit tests: cursor read/write, prompt format, instruction parsing.

Upstream: HKUDS/nanobot#2717 (phase 4a)
sammyne added a commit to sammyne/nanobot-rs that referenced this pull request May 29, 2026
Task 6: CronService.register_system_job() for idempotent internal
job registration. Gateway registers Dream system job on startup
when DreamConfig is present.

Task 7: /dream, /dream-log, /dream-restore commands.
- /dream: triggers Dream::run() and reports results
- /dream-log: shows recent 10 memory commits via GitStore
- /dream-restore <sha>: reverts memory to specified commit

Updated help text and command dispatch in try_handle_cmd().

Upstream: HKUDS/nanobot#2717 (phase 4b)
sammyne added a commit to sammyne/nanobot-rs that referenced this pull request May 29, 2026
Task 0: Add docs/dreaming.md covering the two-stage memory system
design — Consolidator (per-turn text summary) + Dream (cron-scheduled
LLM-driven incremental memory editing with git versioning).

Task 1: Add DreamConfig to AgentDefaults with fields: cron (default
every 2 hours), model (optional override), max_batch_size (20),
max_iterations (10). Currently a no-op config consumed by future
Dream implementation.

Upstream: HKUDS/nanobot#2717 (phase 1 of 4)
sammyne added a commit to sammyne/nanobot-rs that referenced this pull request May 29, 2026
Add History struct for structured JSONL history storage:
- HistoryEntry with cursor, timestamp, content fields
- append() with auto-incrementing cursor
- read_all() and read_since(cursor) for retrieval
- Automatic compaction when entries exceed 1000

This is the foundation for the MemoryStore rewrite (Task 2).
Does not yet replace HISTORY.md — that happens in the next commit.

Upstream: HKUDS/nanobot#2717 (phase 2 prep)
sammyne added a commit to sammyne/nanobot-rs that referenced this pull request May 29, 2026
Task 2+3: MemoryStore refactor and Consolidator simplification.

- Remove provider/options fields from MemoryStore (pure I/O layer now)
- Replace HISTORY.md with history.jsonl via History module (cursor tracking)
- Move consolidation to standalone consolidate_memory() function
- Simplify consolidation: plain-text LLM summary instead of
  tool_choice=Required on save_memory tool
- Remove SaveMemoryArgs, create_save_memory_tool, NoToolCall/ToolParse errors
- Update ContextBuilder::new() to no longer require Options param
- Update agent loop try_consolidate() and NewCmd to use new API
- Update all tests and AGENTS.md docs

Upstream: HKUDS/nanobot#2717 (phase 2)
sammyne added a commit to sammyne/nanobot-rs that referenced this pull request May 29, 2026
Task 4: Implement GitStore using git CLI (std::process::Command).

- init: check git availability (error if missing), git init, .gitignore,
  local user config for commits
- commit: git add -A + git commit (no-op if no changes)
- log: parse git log --format into Vec<CommitInfo>
- diff: git diff-tree for any commit including root
- revert: checkout files from target commit, commit the revert

Zero new dependencies — uses system git command only.
6 unit tests covering init, idempotency, commit, log, diff, revert.

Upstream: HKUDS/nanobot#2717 (phase 3)
sammyne added a commit to sammyne/nanobot-rs that referenced this pull request May 29, 2026
Task 5: Implement Dream struct with Phase 1 (analyze) + Phase 2 (edit).

- Phase 1: LLM compares new history entries against MEMORY.md/SOUL.md/
  USER.md, outputs [FILE] fact instructions
- Phase 2: simplified append-only editing (direct file append, future
  upgrade to re_act-based surgical edits)
- Dream cursor (.dream_cursor file) tracks processed history entries
- Git commit after each Dream run via GitStore
- DreamResult reports entries_processed and files_changed

3 unit tests: cursor read/write, prompt format, instruction parsing.

Upstream: HKUDS/nanobot#2717 (phase 4a)
sammyne added a commit to sammyne/nanobot-rs that referenced this pull request May 29, 2026
Task 6: CronService.register_system_job() for idempotent internal
job registration. Gateway registers Dream system job on startup
when DreamConfig is present.

Task 7: /dream, /dream-log, /dream-restore commands.
- /dream: triggers Dream::run() and reports results
- /dream-log: shows recent 10 memory commits via GitStore
- /dream-restore <sha>: reverts memory to specified commit

Updated help text and command dispatch in try_handle_cmd().

Upstream: HKUDS/nanobot#2717 (phase 4b)
StefanoParadisi pushed a commit to StefanoHernandez/nanobot that referenced this pull request Oct 16, 2026
… consolidation

feat(memory): two-stage memory system with Dream consolidation
StefanoParadisi pushed a commit to StefanoHernandez/nanobot that referenced this pull request Oct 16, 2026
`resolve_config_env_vars` unconditionally dumped the config via
`model_dump(mode="json")` and revalidated it, which silently dropped
any field declared with `exclude=True` (e.g. `DreamConfig.cron` —
introduced by the Dream rename refactor in HKUDS#2717). Result:
`agents.defaults.dream.cron` was never honored at runtime — the gateway
always fell back to the default `every 2h` schedule even when `cron`
was set in config.json.

Fix: skip the roundtrip entirely when the config has no `${VAR}`
references. Env-var interpolation still works unchanged when refs
exist; the legacy `cron` override now survives the common case of
fully-resolved config.

Regression test covers the bug path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request valid

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants