Repository navigation
feat(memory): two-stage memory system with Dream consolidation - #2717
Conversation
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.
cacedc0 to
ab2ecc8
Compare
- 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
|
I like this! @chengyongru |
Made-with: Cursor
Re-bin
left a comment
There was a problem hiding this comment.
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, andmemory/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.jsonlmigration path, including corner cases and partial-history preservation - fixed the migration edge case where an empty pre-created
history.jsonlcould accidentally block legacy migration - improved
/dream-logand/dream-restoreoutput so they are much more user-friendly and easier to trust - added
/dream,/dream-log, and/dream-restoreinto 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
intervalHandmodelOverride, which read much closer to product language than implementation language - renamed the internal
gitstoremodule 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.
`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.
… consolidation feat(memory): two-stage memory system with Dream consolidation
`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.
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
… consolidation feat(memory): two-stage memory system with Dream consolidation
`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.
Replace the single-stage
MemoryConsolidatorwith 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
MemoryConsolidatorhad three fundamental problems:Coupled responsibilities — Compression and long-term memory updates happened in a single LLM call via
tool_choice=forcedonsave_memory. Some providers don't supporttool_choice; the LLM had to simultaneously summarize and rewrite the entire MEMORY.md, degrading quality as memory grew.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.
No change history — Memory files were plain Markdown with no version tracking. Corrupt or unintended edits were irreversible.
What changed
Architecture
MemoryStore rewrite
history.jsonlreplacesHISTORY.mdwith auto-incrementingcursorper entryConsolidator simplified
save_memoryvia forcedtool_choicetools=None) for full provider compatibilityuser corrections > solutions > decisions > events > environment factsDream (new)
Two-phase processor, cron-scheduled (default: every 2 hours):
history.jsonlagainstMEMORY.md/SOUL.md/USER.md, outputs[FILE] atomic factlinesAgentRunnerwithread_file/edit_filetools for surgical edits (up to 10 tool calls)Cursor always advances to prevent re-processing.
GitStore (new)
dulwichfor zero-dependency git operations (no system git required).gitignoreauto-generated to only track memory fileslog(),diff_commits(),revert()for browsing and rollbackNew commands
/dream/dream-log [sha]/dream-restore [sha]DreamConfig
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 migratedSOUL.md,USER.md,MEMORY.mdare now marked as "auto-managed by Dream — do not edit directly" in the system prompt and memory skillFiles changed
Core:
memory.py(+586/-178),git_store.py(+299 new),loop.py,context.py,__init__.pyConfig & Commands:
schema.py,commands.py,builtin.pyCron:
cron/service.pySkills:
skills/memory/SKILL.mdTests: +
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, compactionpytest tests/agent/test_consolidator.py— Consolidator summarization logicpytest tests/agent/test_dream.py— Dream Phase 1/2, cursor advancementpytest tests/agent/test_git_store.py— GitStore init, commit, log, diff, revertpytest tests/agent/test_consolidate_offset.py— Session consolidation boundary selectionpytest tests/agent/test_loop_consolidation_tokens.py— Token-budget-triggered consolidationhistory.jsonlentries are appended/dreamtriggers Dream, verify MEMORY.md/USER.md/SOUL.md updates/dream-logdisplays change log with diff/dream-restorelists commits and reverts to a previous state