diff --git a/python/packages/core/agent_framework/_harness/_memory.py b/python/packages/core/agent_framework/_harness/_memory.py index 66163abed05..66957756261 100644 --- a/python/packages/core/agent_framework/_harness/_memory.py +++ b/python/packages/core/agent_framework/_harness/_memory.py @@ -506,10 +506,10 @@ def from_markdown(cls, markdown: str, *, fallback_topic: str | None = None) -> M topic = stripped[2:].strip() current_section = None continue - if stripped.startswith("Updated: "): + if current_section is None and stripped.startswith("Updated: "): updated_at = stripped.removeprefix("Updated: ").strip() continue - if stripped.startswith("Sessions: "): + if current_section is None and stripped.startswith("Sessions: "): raw_sessions = stripped.removeprefix("Sessions: ").strip() session_ids = ( [] diff --git a/python/packages/core/tests/core/test_harness_memory.py b/python/packages/core/tests/core/test_harness_memory.py index d1c7b3dac51..400c9fc6149 100644 --- a/python/packages/core/tests/core/test_harness_memory.py +++ b/python/packages/core/tests/core/test_harness_memory.py @@ -134,6 +134,60 @@ def test_memory_topic_record_round_trips_through_dict_and_markdown() -> None: assert "MemoryTopicRecord(" in repr(record) +@pytest.mark.parametrize( + "summary", + [ + "Updated: the release date changed.", + "Sessions: keep the troubleshooting context.", + "The plan was Updated: yesterday and Sessions: remain useful.", + "Prefers concise answers.", + "## Summary is part of the stored text.", + ], +) +def test_memory_file_store_preserves_metadata_like_summary_text(tmp_path: Path, summary: str) -> None: + """Summary text must not replace metadata when a topic is reloaded from disk.""" + store = MemoryFileStore(tmp_path, owner_state_key="owner_id") + session = AgentSession(session_id="session-1") + session.state["owner_id"] = "alice" + record = MemoryTopicRecord( + topic="preferences", + summary=summary, + memories=["Updated: the milestone moved.", "Sessions: retain previous notes."], + updated_at="2026-04-21T10:05:00+00:00", + session_ids=["session-1", "session-2"], + ) + + assert MemoryTopicRecord.from_markdown(record.to_markdown()) == record + store.write_topic(session, record, source_id=DEFAULT_MEMORY_SOURCE_ID) + + assert store.get_topic(session, source_id=DEFAULT_MEMORY_SOURCE_ID, topic=record.slug) == record + assert store.list_topics(session, source_id=DEFAULT_MEMORY_SOURCE_ID) == [record] + entries = store.rebuild_index(session, source_id=DEFAULT_MEMORY_SOURCE_ID, line_limit=200, line_length=150) + assert entries == [MemoryIndexEntry.from_topic_record(record)] + + +@pytest.mark.parametrize("summary_line", ["Updated: the release date changed.", "Sessions: keep previous notes."]) +def test_memory_topic_record_preserves_multiline_summary_metadata_text(summary_line: str) -> None: + """Metadata labels inside a multiline summary remain content in that section.""" + markdown = ( + "# preferences\n\n" + "Updated: 2026-04-21T10:05:00+00:00\n" + "Sessions: session-1, session-2\n\n" + "## Summary\n\n" + f"Keep the release notes.\n{summary_line}\n\n" + "## Memories\n\n" + "- Updated: the milestone moved.\n" + "- Sessions: retain previous notes.\n" + ) + + record = MemoryTopicRecord.from_markdown(markdown) + + assert record.summary == f"Keep the release notes. {summary_line}" + assert record.updated_at == "2026-04-21T10:05:00+00:00" + assert record.session_ids == ["session-1", "session-2"] + assert record.memories == ["Updated: the milestone moved.", "Sessions: retain previous notes."] + + async def test_memory_file_store_writes_topics_index_state_and_transcripts(tmp_path) -> None: """The file-backed memory store should manage topics, ``MEMORY.md``, state, and transcript search.""" store = MemoryFileStore(