refactor(memory): simplify Dream config naming and rename gitstore module

This commit is contained in:
Xubin Ren 2026-04-04 10:01:45 +00:00
parent a166fe8fc2
commit 0a3a60a7a4
9 changed files with 104 additions and 23 deletions

View File

@ -149,10 +149,10 @@ Dream is configured under `agents.defaults.dream`:
"agents": {
"defaults": {
"dream": {
"cron": "0 */2 * * *",
"model": null,
"max_batch_size": 20,
"max_iterations": 10
"intervalH": 2,
"modelOverride": null,
"maxBatchSize": 20,
"maxIterations": 10
}
}
}
@ -161,10 +161,22 @@ Dream is configured under `agents.defaults.dream`:
| Field | Meaning |
|-------|---------|
| `cron` | How often Dream runs |
| `model` | Optional model override for Dream |
| `max_batch_size` | How many history entries Dream processes per run |
| `max_iterations` | The tool budget for Dream's editing phase |
| `intervalH` | How often Dream runs, in hours |
| `modelOverride` | Optional Dream-specific model override |
| `maxBatchSize` | How many history entries Dream processes per run |
| `maxIterations` | The tool budget for Dream's editing phase |
In practical terms:
- `modelOverride: null` means Dream uses the same model as the main agent. Set it only if you want Dream to run on a different model.
- `maxBatchSize` controls how many new `history.jsonl` entries Dream consumes in one run. Larger batches catch up faster; smaller batches are lighter and steadier.
- `maxIterations` limits how many read/edit steps Dream can take while updating `SOUL.md`, `USER.md`, and `MEMORY.md`. It is a safety budget, not a quality score.
- `intervalH` is the normal way to configure Dream. Internally it runs as an `every` schedule, not as a cron expression.
Legacy note:
- Older source-based configs may still contain `dream.cron`. nanobot continues to honor it for backward compatibility, but new configs should use `intervalH`.
- Older source-based configs may still contain `dream.model`. nanobot continues to honor it for backward compatibility, but new configs should use `modelOverride`.
## In Practice

View File

@ -16,7 +16,7 @@ from nanobot.utils.helpers import ensure_dir, estimate_message_tokens, estimate_
from nanobot.agent.runner import AgentRunSpec, AgentRunner
from nanobot.agent.tools.registry import ToolRegistry
from nanobot.utils.git_store import GitStore
from nanobot.utils.gitstore import GitStore
if TYPE_CHECKING:
from nanobot.providers.base import LLMProvider

View File

@ -781,20 +781,20 @@ def gateway(
console.print(f"[green]✓[/green] Heartbeat: every {hb_cfg.interval_s}s")
# Register Dream cron job (always-on, idempotent on restart)
# Register Dream system job (always-on, idempotent on restart)
dream_cfg = config.agents.defaults.dream
if dream_cfg.model:
agent.dream.model = dream_cfg.model
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
from nanobot.cron.types import CronJob, CronPayload, CronSchedule
from nanobot.cron.types import CronJob, CronPayload
cron.register_system_job(CronJob(
id="dream",
name="dream",
schedule=CronSchedule(kind="cron", expr=dream_cfg.cron, tz=config.agents.defaults.timezone),
schedule=dream_cfg.build_schedule(config.agents.defaults.timezone),
payload=CronPayload(kind="system_event"),
))
console.print(f"[green]✓[/green] Dream: cron {dream_cfg.cron}")
console.print(f"[green]✓[/green] Dream: {dream_cfg.describe_schedule()}")
async def run():
try:

View File

@ -3,10 +3,12 @@
from pathlib import Path
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field
from pydantic import AliasChoices, BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
from pydantic_settings import BaseSettings
from nanobot.cron.types import CronSchedule
class Base(BaseModel):
"""Base model that accepts both camelCase and snake_case keys."""
@ -31,11 +33,30 @@ class ChannelsConfig(Base):
class DreamConfig(Base):
"""Dream memory consolidation configuration."""
cron: str = "0 */2 * * *" # Every 2 hours
model: str | None = None # Override model for Dream
_HOUR_MS = 3_600_000
interval_h: int = Field(default=2, ge=1) # Every 2 hours by default
cron: str | None = Field(default=None, exclude=True) # Legacy compatibility override
model_override: str | None = Field(
default=None,
validation_alias=AliasChoices("modelOverride", "model", "model_override"),
) # Optional Dream-specific model override
max_batch_size: int = Field(default=20, ge=1) # Max history entries per run
max_iterations: int = Field(default=10, ge=1) # Max tool calls per Phase 2
def build_schedule(self, timezone: str) -> CronSchedule:
"""Build the runtime schedule, preferring the legacy cron override if present."""
if self.cron:
return CronSchedule(kind="cron", expr=self.cron, tz=timezone)
return CronSchedule(kind="every", every_ms=self.interval_h * self._HOUR_MS)
def describe_schedule(self) -> str:
"""Return a human-readable summary for logs and startup output."""
if self.cron:
return f"cron {self.cron} (legacy)"
hours = self.interval_h
return f"every {hours}h"
class AgentDefaults(Base):
"""Default agent configuration."""

View File

@ -457,7 +457,7 @@ def sync_workspace_templates(workspace: Path, silent: bool = False) -> list[str]
# Initialize git for memory version control
try:
from nanobot.utils.git_store import GitStore
from nanobot.utils.gitstore import GitStore
gs = GitStore(workspace, tracked_files=[
"SOUL.md", "USER.md", "memory/MEMORY.md",
])

View File

@ -3,7 +3,7 @@
import pytest
from pathlib import Path
from nanobot.utils.git_store import GitStore, CommitInfo
from nanobot.utils.gitstore import GitStore, CommitInfo
TRACKED = ["SOUL.md", "USER.md", "memory/MEMORY.md"]
@ -181,7 +181,7 @@ class TestShowCommitDiff:
class TestCommitInfoFormat:
def test_format_with_diff(self):
from nanobot.utils.git_store import CommitInfo
from nanobot.utils.gitstore import CommitInfo
c = CommitInfo(sha="abcd1234", message="test commit\nsecond line", timestamp="2026-04-02 12:00")
result = c.format(diff="some diff")
assert "test commit" in result
@ -189,7 +189,7 @@ class TestCommitInfoFormat:
assert "some diff" in result
def test_format_without_diff(self):
from nanobot.utils.git_store import CommitInfo
from nanobot.utils.gitstore import CommitInfo
c = CommitInfo(sha="abcd1234", message="test", timestamp="2026-04-02 12:00")
result = c.format()
assert "(no file changes)" in result

View File

@ -7,7 +7,7 @@ import pytest
from nanobot.bus.events import InboundMessage
from nanobot.command.builtin import cmd_dream_log, cmd_dream_restore
from nanobot.command.router import CommandContext
from nanobot.utils.git_store import CommitInfo
from nanobot.utils.gitstore import CommitInfo
class _FakeStore:

View File

@ -0,0 +1,48 @@
from nanobot.config.schema import DreamConfig
def test_dream_config_defaults_to_interval_hours() -> None:
cfg = DreamConfig()
assert cfg.interval_h == 2
assert cfg.cron is None
def test_dream_config_builds_every_schedule_from_interval() -> None:
cfg = DreamConfig(interval_h=3)
schedule = cfg.build_schedule("UTC")
assert schedule.kind == "every"
assert schedule.every_ms == 3 * 3_600_000
assert schedule.expr is None
def test_dream_config_honors_legacy_cron_override() -> None:
cfg = DreamConfig.model_validate({"cron": "0 */4 * * *"})
schedule = cfg.build_schedule("UTC")
assert schedule.kind == "cron"
assert schedule.expr == "0 */4 * * *"
assert schedule.tz == "UTC"
assert cfg.describe_schedule() == "cron 0 */4 * * * (legacy)"
def test_dream_config_dump_uses_interval_h_and_hides_legacy_cron() -> None:
cfg = DreamConfig.model_validate({"intervalH": 5, "cron": "0 */4 * * *"})
dumped = cfg.model_dump(by_alias=True)
assert dumped["intervalH"] == 5
assert "cron" not in dumped
def test_dream_config_uses_model_override_name_and_accepts_legacy_model() -> None:
cfg = DreamConfig.model_validate({"model": "openrouter/sonnet"})
dumped = cfg.model_dump(by_alias=True)
assert cfg.model_override == "openrouter/sonnet"
assert dumped["modelOverride"] == "openrouter/sonnet"
assert "model" not in dumped