mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-04-05 10:52:36 +00:00
refactor(memory): simplify Dream config naming and rename gitstore module
This commit is contained in:
parent
a166fe8fc2
commit
0a3a60a7a4
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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."""
|
||||
|
||||
@ -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",
|
||||
])
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
48
tests/config/test_dream_config.py
Normal file
48
tests/config/test_dream_config.py
Normal 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
|
||||
Loading…
x
Reference in New Issue
Block a user