Repository navigation
Python: [Bug]: Distinct non-English memory topics silently share a file #9205
Description
Observed Behavior
Writing two distinct non-English topic names through MemoryContextProvider's write_memory tool silently merges them into one file. For example, 旅行计划 (travel plans) and 饮食偏好 (food preferences) both resolve to topics/memory-topic.md. The second write returns the first topic's name with both facts in its memories. Deleting the second topic through delete_memory_topic then removes the shared record, so the first topic can no longer be read.
This occurs with one ordinary owner and session, using the public tools and real local Markdown files. It also reproduces with two Japanese topics, two Cyrillic topics, and café / cafè. Ordinary distinct ASCII topics remain separate. A single non-English topic writes and reloads successfully, and writing the exact same topic twice correctly updates one record.
Expected Behavior
Distinct supported human-readable topic names should not silently address the same record. Writing or deleting one of these topics should leave the other topic's facts intact. The public topic parameter is documented as a human-readable name and currently accepts these values without rejecting them; non-English memory selection is also explicitly supported by #7130.
Steps to Reproduce
- Prepare a
MemoryContextProviderwith aMemoryFileStorefor one owner. - Invoke
write_memoryfor the two different topic names below. - Reopen the store and list topics: only the first name remains, holding both facts.
- Delete the second name, then attempt to read the first name.
- Compare with the ASCII pair in the same script, which keeps two records and preserves the first after deletion.
Minimal Reproduction
import asyncio
import json
from tempfile import TemporaryDirectory
from agent_framework import (
AgentSession,
MemoryContextProvider,
MemoryFileStore,
Message,
SessionContext,
)
async def reproduce(topics):
with TemporaryDirectory() as directory:
session = AgentSession(session_id="session-1")
session.state["owner"] = "local-owner"
store = MemoryFileStore(directory, owner_state_key="owner")
provider = MemoryContextProvider(store=store, recent_turns=0, max_extractions=0)
context = SessionContext(
session_id=session.session_id,
input_messages=[Message(role="user", contents=["Remember these facts."])],
)
await provider.before_run(agent=None, session=session, context=context, state={})
tools = {tool.name: tool for tool in context.tools}
for topic, fact in zip(topics, ["Travel by train.", "Prefer vegetarian food."]):
await tools["write_memory"].invoke(
arguments={"topic": topic, "memory": fact}, skip_parsing=True
)
reopened = MemoryFileStore(directory, owner_state_key="owner")
records = reopened.list_topics(session, source_id="memory")
print(json.dumps([record.to_dict() for record in records], ensure_ascii=False))
await tools["delete_memory_topic"].invoke(
arguments={"topic": topics[1]}, skip_parsing=True
)
try:
reopened.get_topic(session, source_id="memory", topic=topics[0])
print("first topic still exists")
except FileNotFoundError:
print("first topic is missing")
async def main():
await reproduce(["Travel", "Food"])
await reproduce(["旅行计划", "饮食偏好"])
asyncio.run(main())Error Messages and Stack Traces
No error is raised by either write. The ASCII pair produces two records followed by first topic still exists. The Chinese pair produces one record named 旅行计划, with both facts and slug memory-topic, followed by first topic is missing.
The experimental HARNESS warning is present. No model or external service is required.
Package Versions
Executed the complete tracked core package source from ca936db37708539bd502240a48543f6094259ce5 with cached dependencies. Current main 91ab44faa4824a30247d00c341f3498ba46b1ca3 has identical executable core package source; within this package, pyproject.toml changes the version from 1.20.0 to 1.21.0 and raises optional all extra package requirements. Installed core distribution metadata is 1.20.0. A fresh installation of the 1.21.0 dependency set or optional all extra has not been tested.
Python Version
Python 3.14.3
Operating System
Windows
Regression
Unknown
Additional Context
_slugify_topic() keeps only ASCII letters and digits, using memory-topic if none remain. _merge_memory() looks up the existing record by this slug and appends the new fact without checking whether it is a different topic name. Topic lookup and deletion use the same mapping.
The original memory harness was added in #5613. #7130 fixes Unicode keyword extraction for selecting memories; it does not change topic filenames. Open #9153 preserves explicitly chosen custom slugs during reload while leaving default slug derivation unchanged. This report concerns collisions between automatically derived topic identities, with no custom slug supplied. #9197 concerns contributor session ID serialization, and #9079 concerns summary metadata parsing.
No product patch has been implemented. Please confirm the intended topic identity and compatibility approach before implementation. Already conflated records cannot be reliably separated from their stored contents alone; changing the filename mapping also needs an explicit decision for existing files and lookup by slug.
AI Assistance
AI-assisted issue analysis and reproduction tests.
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 8, 2026
🤖 Automated triage reproduction notes (agent-authored — trust but verify)
Agent analysis
Repro:
python/packages/core/agent_framework/_harness/_memory.py::_slugify_topicat lines 141-143 andMemoryFileStore._topic_pathat lines 776-777 map distinct topics with identical ASCII projections to one file. Twowrite_memorycalls for旅行计划and饮食偏好merge both facts, anddelete_memory_topicfor the latter removes the former. Minimal repro: runtest_memory_context_provider_keps_distinct_non_ascii_topics_separateusing a temporaryMemoryFileStore.python/packages/core/tests/core/test_harness_memory.py::test_memory_context_provider_keps_distinct_non_ascii_topics_separate1.20.01.21.0he-yufeng commentedon Oct 9, 2026
Reproduced on current main (declarative/core at c24ef0a). The collision is exactly where the triage notes point:
_slugify_topicstrips every non-[a-z0-9]character, so旅行计划and饮食偏好both fall through to thememory-topicfallback andcafé/cafèboth becomecaf. Store-level check with one owner/session:Since the second write clobbers identity and
delete_memory_topicthen removes the shared file, this is silent data loss for any non-ASCII deployment.I'd like to take this. Plan: keep
_slugify_topic's readable output byte-identical for topics that are already safe (slug == normalized.lower(), so existing ASCII stems are untouched), and append a short sha256 digest of the normalized topic whenever the mapping discarded information, e.g.café->caf-4f8b…,旅行计划->memory-topic-9c21…. Same collision-resistance standard as the_storage_key_segmentdigest fallback already used for owner/source segments in this file. On top of that,get_topic/delete_topicfall back to the legacy lossy path when the new file is absent, andwrite_topicabsorbs a legacy-named file into the new name, so stores written by older versions stay readable and migrate on first rewrite instead of stranding. Regression tests: distinct CJK/Cyrillic/accented topics stay separate, deleting one leaves the other readable, legacy-name read-back, and slug idempotency throughMemoryTopicRecord/MemoryIndexEntryre-derivation.eavanvalkenburg commentedon Oct 27, 2026
/plan Scope a safe topic-identity and legacy-file compatibility correction for the confirmed non-English topic collision/write/delete bug on current main. Reuse the unresolved correctness findings from closed, unmerged #9227 rather than treating its hash/migration approach as accepted: distinguish human topic names from explicit slugs, handle primary-path and legacy-path ownership mismatches, custom-slug cleanup, case-folding semantics, adequate collision resistance, filename limits, and index/provider lookup before and after migration. Do not silently merge/delete unrelated records or claim to reconstruct already conflated facts. Compare minimally disruptive identity strategies and explicit compatibility/error behavior; cover real-file write/read/delete isolation and upgrade paths. Coordinate with westey (@westey-m) and the current shared-memory ownership/isolation/concurrency design work in #9218. This is scoped analysis only, not permission to bypass that design hold, reopen #9227, change assignments or begin implementation.
ktz03 commentedon Oct 27, 2026
On current main, writes use
record.slug, while reads and deletes derive a path from the supplied topic or slug (source); changing only the filename hash would leave that lookup ambiguity unresolved. Two strategies to compare are deterministic naming with identity-checked legacy lookup, and an explicit stable topic identity resolved through an index; both need a rule for a human topic name that matches another record's custom slug. Legacy ownership mismatches should remain non-destructive, and list/index/provider lookup needs to work before any permitted rewrite, preserving the unresolved #9227 review findings. These are compatibility decision points for the shared-memory design review; I have not started a replacement patch.