fix(memory): serialize cursor allocation in append_history (#4081)
This commit is contained in:
+16
-11
@@ -6,6 +6,7 @@ import asyncio
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import threading
|
||||
import weakref
|
||||
from contextlib import suppress
|
||||
from datetime import datetime
|
||||
@@ -61,6 +62,7 @@ class MemoryStore:
|
||||
self._dream_cursor_file = self.memory_dir / ".dream_cursor"
|
||||
self._corruption_logged = False # rate-limit non-int cursor warning
|
||||
self._oversize_logged = False # rate-limit oversized-entry warning
|
||||
self._append_lock = threading.Lock() # serialize cursor allocation + append
|
||||
self._git = GitStore(workspace, tracked_files=[
|
||||
"SOUL.md", "USER.md", "memory/MEMORY.md", "memory/.dream_cursor",
|
||||
])
|
||||
@@ -248,7 +250,6 @@ class MemoryStore:
|
||||
large writes (e.g. an LLM echoing its input back as a "summary").
|
||||
"""
|
||||
limit = max_chars if max_chars is not None else _HISTORY_ENTRY_HARD_CAP
|
||||
cursor = self._next_cursor()
|
||||
ts = datetime.now().strftime("%Y-%m-%d %H:%M")
|
||||
raw = entry.rstrip()
|
||||
if len(raw) > limit:
|
||||
@@ -262,16 +263,20 @@ class MemoryStore:
|
||||
)
|
||||
raw = truncate_text(raw, limit)
|
||||
content = strip_think(raw)
|
||||
if raw and not content:
|
||||
logger.debug(
|
||||
"history entry {} stripped to empty (likely template leak); "
|
||||
"persisting empty content to avoid re-polluting context",
|
||||
cursor,
|
||||
)
|
||||
record = {"cursor": cursor, "timestamp": ts, "content": content}
|
||||
with open(self.history_file, "a", encoding="utf-8") as f:
|
||||
f.write(json.dumps(record, ensure_ascii=False) + "\n")
|
||||
self._cursor_file.write_text(str(cursor), encoding="utf-8")
|
||||
# Cursor allocation and the append must be atomic: concurrent writers
|
||||
# could otherwise read the same current cursor and emit duplicates.
|
||||
with self._append_lock:
|
||||
cursor = self._next_cursor()
|
||||
if raw and not content:
|
||||
logger.debug(
|
||||
"history entry {} stripped to empty (likely template leak); "
|
||||
"persisting empty content to avoid re-polluting context",
|
||||
cursor,
|
||||
)
|
||||
record = {"cursor": cursor, "timestamp": ts, "content": content}
|
||||
with open(self.history_file, "a", encoding="utf-8") as f:
|
||||
f.write(json.dumps(record, ensure_ascii=False) + "\n")
|
||||
self._cursor_file.write_text(str(cursor), encoding="utf-8")
|
||||
return cursor
|
||||
|
||||
@staticmethod
|
||||
|
||||
Reference in New Issue
Block a user