refactor(dream): replace two-phase Dream class with simple cron + process_direct (#3990)
* refactor(dream): replace two-phase Dream class with simple cron + process_direct - Remove the heavyweight Dream class (AgentRunner-based two-phase system) from nanobot/agent/memory.py - Delete dream_phase1.md and dream_phase2.md templates - New dream.md template serves as the consolidation prompt - Cron callback uses agent.process_direct(prompt, session_key=\"dream\") instead of agent.dream.run() - Always performs git auto_commit after execution - /dream command updated to use process_direct + git commit - DreamConfig kept for backward compatibility; deprecated fields (model_override, max_batch_size, max_iterations, annotate_line_ages) are ignored but accepted in config - interval_h remains configurable via agents.defaults.dream.interval_h - Update tests and webui settings to match new architecture * feat(loop): add ephemeral mode to process_direct, skip history writes for Dream When ephemeral=True, _state_save skips enforce_file_cap (which calls raw_archive -> append_history) and consolidator.maybe_consolidate_by_tokens. This prevents Dream sessions from creating a positive feedback loop where they process their own output. The session IS still saved to disk. * fix(loop): skip extra hooks for ephemeral sessions (Dream) * feat(dream): per-run timestamped sessions with rotation for WebUI * test(config): restore DreamConfig schedule and alias tests * fix(dream): include LLM response summary in git auto-commit message The old two-phase Dream class included the Phase 1 analysis in the git commit message body. The new single-phase version lost this. Restore it by extracting resp.content from the process_direct return value and appending it to the commit message in both the cron handler and the /dream command. * fix(test): accept ephemeral kwarg in test_openai_api fake_process * refactor(dream): merge dream_session.py into MemoryStore The standalone dream_session.py module only contained three small helpers that all revolve around MemoryStore concerns (session keys, commit messages, file pruning). Fold them into MemoryStore as @staticmethod to reduce indirection and avoid a 35-line module with no independent reason to exist. * fix(test): address code review — patch correct instance, use actual function - Fix test_ephemeral_skips_raw_archive to patch loop.context.memory instead of the fixture's separate MemoryStore instance - Fix TestDreamCommitMessage to call MemoryStore.build_dream_commit_message instead of reimplementing the logic inline - Move Dream helpers in memory.py above the Consolidator section comment to avoid misleading visual boundary * fix(dream): gate cursor advancement and restrict tools maintainer edit: Dream now processes backlog from the oldest unprocessed entries, only advances the cursor after a completed ephemeral run, and uses a restricted file-only tool registry for background consolidation. * fix(dream): skip idle compact for dream sessions Dream runs use internal dream:* sessions that are pruned by Dream retention. Exclude them from AutoCompact scheduling, archive execution, and summary injection so idle-session compaction cannot truncate Dream transcripts. * fix(dream): keep batched history isolated * feat(dream): tag archived memory for single-phase Dream --------- Co-authored-by: Xubin Ren <52506698+Re-bin@users.noreply.github.com>
This commit is contained in:
+40
-8
@@ -984,11 +984,48 @@ def _run_gateway(
|
||||
|
||||
# Dream is an internal job — run directly, not through the agent loop.
|
||||
if job.name == "dream":
|
||||
from nanobot.agent.memory import MemoryStore
|
||||
|
||||
dream_session_key = MemoryStore.dream_session_key
|
||||
build_dream_commit_message = MemoryStore.build_dream_commit_message
|
||||
prune_dream_sessions = MemoryStore.prune_dream_sessions
|
||||
|
||||
store = agent.context.memory
|
||||
resp = None
|
||||
try:
|
||||
await agent.dream.run()
|
||||
logger.info("Dream cron job completed")
|
||||
result = store.build_dream_prompt()
|
||||
if result is None:
|
||||
logger.info("Dream: nothing to process")
|
||||
return None
|
||||
prompt, last_cursor = result
|
||||
key = dream_session_key()
|
||||
resp = await agent.process_direct(
|
||||
prompt,
|
||||
session_key=key,
|
||||
ephemeral=True,
|
||||
tools=store.build_dream_tools(),
|
||||
on_progress=_silent,
|
||||
)
|
||||
if MemoryStore.dream_run_completed(resp):
|
||||
store.set_last_dream_cursor(last_cursor)
|
||||
logger.info("Dream cron job completed, cursor advanced to {}", last_cursor)
|
||||
else:
|
||||
logger.warning(
|
||||
"Dream cron job did not complete; cursor remains at {}",
|
||||
store.get_last_dream_cursor(),
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("Dream cron job failed")
|
||||
finally:
|
||||
if store.git.is_initialized():
|
||||
msg = build_dream_commit_message(
|
||||
"dream: periodic memory consolidation", resp,
|
||||
)
|
||||
sha = store.git.auto_commit(msg)
|
||||
if sha:
|
||||
logger.info("Dream commit: {}", sha)
|
||||
store.compact_history()
|
||||
prune_dream_sessions(agent.sessions.sessions_dir)
|
||||
return None
|
||||
|
||||
# Heartbeat is a system job that checks HEARTBEAT.md for active tasks.
|
||||
@@ -1199,13 +1236,8 @@ def _run_gateway(
|
||||
async with server:
|
||||
await server.serve_forever()
|
||||
# Register Dream system job (idempotent on restart)
|
||||
dream_cfg = config.agents.defaults.dream
|
||||
if dream_cfg.model_override:
|
||||
agent.dream.model = dream_cfg.model_override
|
||||
agent.dream.max_batch_size = dream_cfg.max_batch_size
|
||||
agent.dream.max_iterations = dream_cfg.max_iterations
|
||||
agent.dream.annotate_line_ages = dream_cfg.annotate_line_ages
|
||||
from nanobot.cron.types import CronJob, CronPayload, CronSchedule
|
||||
dream_cfg = config.agents.defaults.dream
|
||||
if dream_cfg.enabled:
|
||||
cron.register_system_job(CronJob(
|
||||
id="dream",
|
||||
|
||||
Reference in New Issue
Block a user