mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 13:16:24 +00:00
fix(memory): sign DeerMem reload before reading the document (#6238)
* fix(memory): sign DeerMem reload before reading the document reload() read the document before computing its cache signature, so a write committed in between cached the old document under the new signature and later load() calls served stale memory until the next write. Compute the signature first, matching load(). * test(memory): cover markdown storage and agent scope in reload race test * docs(changelog): reference #6238 for the DeerMem reload fix
This commit is contained in:
parent
aaeed26d69
commit
e3064f541b
@ -466,6 +466,13 @@ This release closes that milestone with **301 merged pull requests**.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **memory:** A DeerMem memory reload no longer pins an older document in the
|
||||
cache. `reload()` read the document before computing its cache signature, so
|
||||
a write committed in between (for example by the background memory updater)
|
||||
cached the old document under the new signature, and every later `load()`
|
||||
returned the outdated memory until the next write. `reload()` now computes
|
||||
the signature first, as `load()` already did, so a racing write forces a
|
||||
re-read instead. ([#6238])
|
||||
- **channels:** Discord now runs its channel-connection database work on the
|
||||
Gateway event loop. discord.py delivers messages on a private loop in the
|
||||
client thread, and the Discord adapter awaited the connection repository there
|
||||
@ -7679,3 +7686,4 @@ with **180 merged pull requests** since the first 2.0 milestone tag.
|
||||
[#6214]: https://github.com/bytedance/deer-flow/pull/6214
|
||||
[#6229]: https://github.com/bytedance/deer-flow/pull/6229
|
||||
[#6230]: https://github.com/bytedance/deer-flow/pull/6230
|
||||
[#6238]: https://github.com/bytedance/deer-flow/pull/6238
|
||||
|
||||
@ -423,6 +423,11 @@
|
||||
|
||||
### 修复
|
||||
|
||||
- **记忆:** DeerMem 记忆重新加载不再把旧文档固定在缓存中。`reload()`
|
||||
此前先读取文档、后计算缓存签名,若两者之间有写入提交(例如后台记忆更
|
||||
新器),旧文档就会以新签名写入缓存,之后每次 `load()` 都返回过时的记
|
||||
忆,直到下一次写入。`reload()` 现在与 `load()` 一样先计算签名,竞争写
|
||||
入只会触发重新读取。([#6238])
|
||||
- **渠道:** Discord 的渠道连接数据库操作现在在 Gateway 事件循环上执行。
|
||||
discord.py 在客户端线程的私有事件循环上投递消息,而 Discord 适配器此前就在该
|
||||
循环上 await 连接仓库,但仓库的 SQLAlchemy 引擎与连接池属于 Gateway 循环。在
|
||||
@ -6414,3 +6419,4 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子
|
||||
[#6214]: https://github.com/bytedance/deer-flow/pull/6214
|
||||
[#6229]: https://github.com/bytedance/deer-flow/pull/6229
|
||||
[#6230]: https://github.com/bytedance/deer-flow/pull/6230
|
||||
[#6238]: https://github.com/bytedance/deer-flow/pull/6238
|
||||
|
||||
@ -134,6 +134,7 @@ Use the typed conflict classes instead of matching exception text.
|
||||
|
||||
The weak lock cache must not retain inactive user scopes.
|
||||
Cache validation uses the manifest metadata and persisted revision.
|
||||
Unlocked `load()`/`reload()` compute that signature before reading the document.
|
||||
Out-of-band Markdown edits require `reload()`.
|
||||
POSIX atomic replacement must sync the parent directory.
|
||||
|
||||
|
||||
@ -1396,8 +1396,11 @@ class FileMemoryStorage(MemoryStorage):
|
||||
migration_notifications = self._run_read_migrations_locked(path, agent_name, user_id=user_id)
|
||||
for notification_agent, notifications in migration_notifications:
|
||||
self._dispatch_retrieval_notifications(notifications, user_id=user_id, agent_name=notification_agent)
|
||||
document = self._read_document(path, agent_name, user_id=user_id)
|
||||
# Sign before reading, as load() does: a write landing in between then
|
||||
# leaves a stale signature that forces a re-read, never a stale
|
||||
# document cached under the new signature.
|
||||
signature = self._scope_signature(path, agent_name)
|
||||
document = self._read_document(path, agent_name, user_id=user_id)
|
||||
with self._cache_lock:
|
||||
self._memory_cache[key] = (copy.deepcopy(document), signature)
|
||||
if _rebuild_retrieval and agent_name is not None and self._retrieval is not None:
|
||||
|
||||
@ -191,6 +191,45 @@ class TestFileMemoryStorage:
|
||||
memory2 = storage.reload()
|
||||
assert memory2["user"]["workContext"]["summary"] == "updated"
|
||||
|
||||
@pytest.mark.parametrize("storage_class", [FileMemoryStorage, MarkdownMemoryStorage])
|
||||
@pytest.mark.parametrize("agent_name", [None, "test-agent"])
|
||||
def test_reload_does_not_pin_a_document_older_than_its_signature(self, tmp_path, storage_class, agent_name):
|
||||
"""A write committed while reload() reads must not be hidden from later loads."""
|
||||
storage = storage_class(DeerMemConfig(storage_path=str(tmp_path)))
|
||||
|
||||
def memory_with(text: str) -> dict:
|
||||
memory = create_empty_memory()
|
||||
if agent_name is None:
|
||||
memory["user"]["workContext"]["summary"] = text
|
||||
else:
|
||||
memory["facts"] = [{"id": "fact_1", "content": text, "category": "context", "confidence": 0.9}]
|
||||
return memory
|
||||
|
||||
def text_of(memory: dict) -> str:
|
||||
if agent_name is None:
|
||||
return memory["user"]["workContext"]["summary"]
|
||||
return memory["facts"][0]["content"]
|
||||
|
||||
assert storage.save(memory_with("old"), agent_name, user_id="alice")
|
||||
read_document = storage._read_document
|
||||
raced = False
|
||||
|
||||
def read_then_concurrent_write(*args, **kwargs):
|
||||
nonlocal raced
|
||||
document = read_document(*args, **kwargs)
|
||||
if not raced:
|
||||
raced = True
|
||||
# A background updater commits after reload() has read the
|
||||
# document but before it caches the result.
|
||||
assert storage.save(memory_with("new"), agent_name, user_id="alice")
|
||||
return document
|
||||
|
||||
with patch.object(storage, "_read_document", side_effect=read_then_concurrent_write):
|
||||
storage.reload(agent_name, user_id="alice")
|
||||
|
||||
assert raced
|
||||
assert text_of(storage.load(agent_name, user_id="alice")) == "new"
|
||||
|
||||
|
||||
class TestCreateStorage:
|
||||
"""Test create_storage(config) (replaces the old get_memory_storage() singleton)."""
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user