This commit is contained in:
Adarsh 2026-10-28 12:20:59 +08:00 • committed by GitHub
commit af7dd1c541
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 54 additions and 6 deletions

View file

@ -320,10 +320,13 @@ class AutoMemoryStep(BaseStep):
by_id[msg.id] = msg
for msg in messages:
by_id[msg.id] = msg
merged = sorted(by_id.values(), key=lambda m: m.created_at)
merged = [_sanitize_msg_for_save(msg) for msg in sorted(by_id.values(), key=lambda m: m.created_at)]
# An unchanged ID does not imply unchanged persisted content. Compare
# sanitized records so identical retries with omitted image/tool data
# retain the append fast path, while corrections force a rewrite.
can_append = 0 < len(existing) <= len(merged) and all(
merged[i].id == existing[i].id for i in range(len(existing))
merged[i].model_dump() == existing[i].model_dump() for i in range(len(existing))
)
path.parent.mkdir(parents=True, exist_ok=True)
@ -333,7 +336,7 @@ class AutoMemoryStep(BaseStep):
if new_msgs:
async with aiofiles.open(path, "a", encoding="utf-8") as f:
for msg in new_msgs:
await f.write(_sanitize_msg_for_save(msg).model_dump_json() + "\n")
await f.write(msg.model_dump_json() + "\n")
self.logger.info(
f"[{self.name}] save session appended session_id={session_id!r} "
f"existing={len(existing)} appended={len(new_msgs)} total={len(merged)}",
@ -346,7 +349,7 @@ class AutoMemoryStep(BaseStep):
else:
async with aiofiles.open(path, "w", encoding="utf-8") as f:
for msg in merged:
await f.write(_sanitize_msg_for_save(msg).model_dump_json() + "\n")
await f.write(msg.model_dump_json() + "\n")
self.logger.info(
f"[{self.name}] save session rewrote session_id={session_id!r} "
f"existing={len(existing)} total={len(merged)}",

View file

@ -414,7 +414,7 @@ async def test_history_merge_keeps_main_source_contract(setup, enabled, history)
elif history == "backfill":
messages, expected = [older, original], _saved_line(older) + initial
elif history == "same-id":
messages, expected = [replacement], initial
messages, expected = [replacement], _saved_line(replacement)
else:
messages, expected = [older, replacement], _saved_line(older) + _saved_line(replacement)
before = [message.model_dump() for message in messages]

View file

@ -5,7 +5,7 @@
import asyncio
from types import SimpleNamespace
from agentscope.message import Msg, TextBlock
from agentscope.message import Base64Source, DataBlock, Msg, TextBlock
import pytest
from reme.steps.evolve import auto_memory
@ -197,3 +197,48 @@ async def test_concurrent_append_survives_rewrite_race(step, monkeypatch):
_assert_transcript(step._session_path(_SESSION), ["m0", "m_mid", "m1", "m2"])
_assert_serialized(interleaving)
@pytest.mark.asyncio
@pytest.mark.parametrize("append_new", [False, True])
@pytest.mark.parametrize("field", ["content", "metadata", "created_at"])
async def test_existing_message_updates_are_persisted(step, append_new, field):
original = _msg("m0", "2026-10-01T10:00:00")
await step._save_session_messages(_SESSION, [original])
updates = {
"content": [TextBlock(text="corrected observation")],
"metadata": {"corrected": True},
"created_at": "2026-10-01T10:00:30",
}
corrected = original.model_copy(update={field: updates[field]})
messages = [corrected]
if append_new:
messages.append(_msg("m1", "2026-10-01T10:01:00"))
await step._save_session_messages(_SESSION, messages)
saved = [Msg.model_validate_json(line) for line in step._session_path(_SESSION).read_text().splitlines()]
assert [message.model_dump() for message in saved] == [message.model_dump() for message in messages]
@pytest.mark.asyncio
async def test_identical_retry_with_sanitized_content_does_not_rewrite(step, monkeypatch):
message = _msg("m0", "2026-10-01T10:00:00")
message.content.append(DataBlock(source=Base64Source(media_type="image/png", data="aW1hZ2U=")))
await step._save_session_messages(_SESSION, [message])
original = step._session_path(_SESSION).read_bytes()
real_open = auto_memory.aiofiles.open
write_modes = []
def record_open(path, mode="r", **kwargs):
if mode in ("a", "w"):
write_modes.append(mode)
return real_open(path, mode, **kwargs)
monkeypatch.setattr(auto_memory.aiofiles, "open", record_open)
await step._save_session_messages(_SESSION, [message])
assert not write_modes
assert step._session_path(_SESSION).read_bytes() == original
await step._save_session_messages(_SESSION, [message, _msg("m1", "2026-10-01T10:01:00")])
assert write_modes == ["a"]
assert len(message.content) == 2