Repository navigation
Python: [Bug]: Consolidated memory continuation lines disappear on reload #9228
Description
Observed Behavior
consolidate_memories accepts a valid JSON response whose memories array contains a string with an ordinary line break and returns {"consolidated_topics": 1}. A fresh MemoryFileStore then returns only the first line of that memory.
For example, the supplied memory is Deploy staging.\nThen deploy production., but a fresh read returns ['Deploy staging.']. The continuation is still present in the raw Markdown file immediately after consolidation; it is omitted by the record parser. This report concerns that parsed-memory discrepancy, not a model-quality result or a general storage-loss claim.
The same string through the public write_memory tool retains both sentences by normalizing whitespace. A single-line consolidation result round-trips normally. The default consolidation prompt asks for strings in a memories array and does not prohibit line breaks.
Expected Behavior
A successful consolidation should not silently omit part of an accepted memory when the file is read again. Either preserve all of the memory text, normalize it to the existing single-line representation, or reject unsupported formatting while preserving the previous record.
Steps to Reproduce
- Create a
MemoryFileStoreandMemoryContextProviderwith the deterministic offline client below. - Write a normal memory using the provider's public tool.
- Consolidate with one valid JSON memory containing LF or CRLF.
- Open a fresh store and compare the memory with the supplied text. The single-line case is a normal control.
Minimal Reproduction
The client returns an actual SDK ChatResponse; there are no private provider calls, network requests or model calls.
import asyncio
import json
import tempfile
from agent_framework import (
AgentSession, ChatResponse, MemoryContextProvider, MemoryFileStore,
Message, SessionContext,
)
class OfflineClient:
def __init__(self, memory):
self.memory = memory
async def get_response(self, messages, **kwargs):
result = {"summary": "Deployment", "memories": [self.memory]}
return ChatResponse(messages=[Message(role="assistant", contents=[json.dumps(result)])])
async def reproduce(memory):
with tempfile.TemporaryDirectory() as directory:
session = AgentSession(session_id="example")
session.state["owner"] = "example-owner"
store = MemoryFileStore(directory, owner_state_key="owner")
provider = MemoryContextProvider(
store=store, recent_turns=0, max_extractions=0,
consolidation_client=OfflineClient(memory),
)
context = SessionContext(session_id=session.session_id, input_messages=[])
await provider.before_run(agent=None, session=session, context=context, state={})
tools = {tool.name: tool for tool in context.tools}
await tools["write_memory"].invoke(
arguments={"topic": "Deployment", "memory": "Deploy staging then production."},
skip_parsing=True,
)
response = await tools["consolidate_memories"].invoke(arguments={}, skip_parsing=True)
fresh = MemoryFileStore(directory, owner_state_key="owner").get_topic(
session, source_id="memory", topic="Deployment"
)
print({"input": memory, "result": response, "reloaded": fresh.memories})
async def main():
await reproduce("Deploy staging then production.")
await reproduce("Deploy staging.\nThen deploy production.")
await reproduce("Deploy staging.\r\nThen deploy production.")
asyncio.run(main())Observed output, with the experimental warning retained:
single-line: result={"consolidated_topics": 1}; reloaded=['Deploy staging then production.']
LF: result={"consolidated_topics": 1}; reloaded=['Deploy staging.']
CRLF: result={"consolidated_topics": 1}; reloaded=['Deploy staging.']
Package Versions
agent-framework-core source at current main 2d9cc3f8ae465baf1aed3022f89b6848db4ffa7b (pyproject.toml version 1.21.0). All 84 checked package Python files match that commit's Git blobs. The cached dependency environment reports Core 1.20.0 metadata; the imported executable source is the verified main source. No fresh workspace-lock installation or full core-suite claim.
Python Version
Python 3.14.3.
Operating System
Windows.
Regression
Unknown.
Additional Context
The original memory harness and Markdown format belong to #5613. Its review already called for heading-marker round-trip preservation; this observation uses an ordinary continuation line and the accepted consolidation response path. #9078/#9079 concern summary text resembling metadata; #9153 and #9205/#9227 concern topic identity; #9218 concerns consolidation scheduling markers. Their existing scope and authorship are preserved.
Public provider/store/tool controls covered single lines, ordinary LF/CRLF continuations, list continuations and heading controls. A separate independent check confirms LF/CRLF omissions and normal single-line, two-item, list and Unicode controls. Matrices overlap and are not added. No product patch, live model, remote store, other operating system or performance validation.
Please confirm the desired single-line normalization or unsupported-format handling before implementation. This report does not propose a new file format or attempt to reconstruct already omitted content.
AI Assistance
AI-assisted analysis, reproduction, and writing.
Acknowledgements
- I searched existing issues and did not find a duplicate.
- I personally verified this behavior and the reproduction details are authentic.
- I will wait for explicit maintainer agreement before starting implementation of a non-trivial change.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNo status
Activity
github-actions commentedon Oct 9, 2026
🤖 Automated triage reproduction notes (agent-authored — trust but verify)
Agent analysis
Repro:
python/packages/core/agent_framework/_harness/_memory.py::MemoryTopicRecord.to_markdown/from_markdownloses continuation lines whenMemoryContextProvider._consolidate_topicaccepts a memory string containing LF or CRLF. Minimal repro: consolidate an existing topic withmemories=["Deploy staging.\nThen deploy production."], confirm{"consolidated_topics": 1}, then read it through a freshMemoryFileStore; onlyDeploy staging.is returned.python/packages/core/tests/core/test_harness_memory.py::test_memory_consolidation_round_trips_multiline_memories1.21.01.21.0he-yufeng commentedon Oct 9, 2026
Reproduced on current main (65a12f9, core 1.21.0). The write path already normalizes:
_merge_memoryruns every memory through_normalize_memory_text, sowrite_memorynever persists a line break. The consolidation path skips that step:_consolidate_topicpasses the LLM'smemoriesstrings straight intoMemoryTopicRecord,to_markdownwrites the break verbatim as a non-bullet line, andfrom_markdownonly reads-bullets, so the continuation vanishes on the next load while the raw file still shows it.I'd like to take this. Proposed fix: normalize memories in
MemoryTopicRecord.__init__with the same_normalize_memory_textthe write path uses (keeping the current drop-empty tolerance), so single-line memories become a record invariant and the markdown round-trip is stable by construction. That covers consolidation, extraction, and any future JSON-sourced path at once, matching thewrite_memorybehavior you noted as the reference. Regression coverage: LF and CRLF consolidation memories read back intact (normalized) through a fresh store, plus a record-level markdown round-trip.he-yufeng commentedon Oct 10, 2026
This is exactly what #9240 fixes:
to_markdownwrote the line break verbatim as a non-bullet line, and the reload path only collected bullet items, so everything after the first break was dropped. The PR makes multi-line memory strings survive the round trip; verified with the same fresh-store reload scenario.