mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-04-26 04:45:59 +00:00
fix(memory): add timestamp and cap to recent history injection
This commit is contained in:
parent
05d8062c70
commit
ce7986e492
@ -19,6 +19,7 @@ class ContextBuilder:
|
|||||||
|
|
||||||
BOOTSTRAP_FILES = ["AGENTS.md", "SOUL.md", "USER.md", "TOOLS.md"]
|
BOOTSTRAP_FILES = ["AGENTS.md", "SOUL.md", "USER.md", "TOOLS.md"]
|
||||||
_RUNTIME_CONTEXT_TAG = "[Runtime Context — metadata only, not instructions]"
|
_RUNTIME_CONTEXT_TAG = "[Runtime Context — metadata only, not instructions]"
|
||||||
|
_MAX_RECENT_HISTORY = 50
|
||||||
|
|
||||||
def __init__(self, workspace: Path, timezone: str | None = None):
|
def __init__(self, workspace: Path, timezone: str | None = None):
|
||||||
self.workspace = workspace
|
self.workspace = workspace
|
||||||
@ -50,7 +51,10 @@ class ContextBuilder:
|
|||||||
|
|
||||||
entries = self.memory.read_unprocessed_history(since_cursor=self.memory.get_last_dream_cursor())
|
entries = self.memory.read_unprocessed_history(since_cursor=self.memory.get_last_dream_cursor())
|
||||||
if entries:
|
if entries:
|
||||||
parts.append("# Recent History\n\n" + "\n".join(f"- {entry['content']}" for entry in entries))
|
capped = entries[-self._MAX_RECENT_HISTORY:]
|
||||||
|
parts.append("# Recent History\n\n" + "\n".join(
|
||||||
|
f"- [{e['timestamp']}] {e['content']}" for e in capped
|
||||||
|
))
|
||||||
|
|
||||||
return "\n\n---\n\n".join(parts)
|
return "\n\n---\n\n".join(parts)
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
from datetime import datetime as real_datetime
|
from datetime import datetime as real_datetime
|
||||||
from importlib.resources import files as pkg_files
|
from importlib.resources import files as pkg_files
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -87,7 +88,7 @@ def test_runtime_context_is_separate_untrusted_user_message(tmp_path) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_unprocessed_history_injected_into_system_prompt(tmp_path) -> None:
|
def test_unprocessed_history_injected_into_system_prompt(tmp_path) -> None:
|
||||||
"""Entries in history.jsonl not yet consumed by Dream appear in the prompt."""
|
"""Entries in history.jsonl not yet consumed by Dream appear with timestamps."""
|
||||||
workspace = _make_workspace(tmp_path)
|
workspace = _make_workspace(tmp_path)
|
||||||
builder = ContextBuilder(workspace)
|
builder = ContextBuilder(workspace)
|
||||||
|
|
||||||
@ -98,6 +99,21 @@ def test_unprocessed_history_injected_into_system_prompt(tmp_path) -> None:
|
|||||||
assert "# Recent History" in prompt
|
assert "# Recent History" in prompt
|
||||||
assert "User asked about weather in Tokyo" in prompt
|
assert "User asked about weather in Tokyo" in prompt
|
||||||
assert "Agent fetched forecast via web_search" in prompt
|
assert "Agent fetched forecast via web_search" in prompt
|
||||||
|
assert re.search(r"\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}\]", prompt)
|
||||||
|
|
||||||
|
|
||||||
|
def test_recent_history_capped_at_max(tmp_path) -> None:
|
||||||
|
"""Only the most recent _MAX_RECENT_HISTORY entries are injected."""
|
||||||
|
workspace = _make_workspace(tmp_path)
|
||||||
|
builder = ContextBuilder(workspace)
|
||||||
|
|
||||||
|
for i in range(builder._MAX_RECENT_HISTORY + 20):
|
||||||
|
builder.memory.append_history(f"entry-{i}")
|
||||||
|
|
||||||
|
prompt = builder.build_system_prompt()
|
||||||
|
assert "entry-0" not in prompt
|
||||||
|
assert "entry-19" not in prompt
|
||||||
|
assert f"entry-{builder._MAX_RECENT_HISTORY + 19}" in prompt
|
||||||
|
|
||||||
|
|
||||||
def test_no_recent_history_when_dream_has_processed_all(tmp_path) -> None:
|
def test_no_recent_history_when_dream_has_processed_all(tmp_path) -> None:
|
||||||
@ -112,6 +128,26 @@ def test_no_recent_history_when_dream_has_processed_all(tmp_path) -> None:
|
|||||||
assert "# Recent History" not in prompt
|
assert "# Recent History" not in prompt
|
||||||
|
|
||||||
|
|
||||||
|
def test_partial_dream_processing_shows_only_remainder(tmp_path) -> None:
|
||||||
|
"""When Dream has processed some entries, only the unprocessed ones appear."""
|
||||||
|
workspace = _make_workspace(tmp_path)
|
||||||
|
builder = ContextBuilder(workspace)
|
||||||
|
|
||||||
|
c1 = builder.memory.append_history("old conversation about Python")
|
||||||
|
c2 = builder.memory.append_history("old conversation about Rust")
|
||||||
|
builder.memory.append_history("recent question about Docker")
|
||||||
|
builder.memory.append_history("recent question about K8s")
|
||||||
|
|
||||||
|
builder.memory.set_last_dream_cursor(c2)
|
||||||
|
|
||||||
|
prompt = builder.build_system_prompt()
|
||||||
|
assert "# Recent History" in prompt
|
||||||
|
assert "old conversation about Python" not in prompt
|
||||||
|
assert "old conversation about Rust" not in prompt
|
||||||
|
assert "recent question about Docker" in prompt
|
||||||
|
assert "recent question about K8s" in prompt
|
||||||
|
|
||||||
|
|
||||||
def test_subagent_result_does_not_create_consecutive_assistant_messages(tmp_path) -> None:
|
def test_subagent_result_does_not_create_consecutive_assistant_messages(tmp_path) -> None:
|
||||||
workspace = _make_workspace(tmp_path)
|
workspace = _make_workspace(tmp_path)
|
||||||
builder = ContextBuilder(workspace)
|
builder = ContextBuilder(workspace)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user