mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-11-12 14:00:35 +00:00
fix(memory): preserve retrieval indexes on operational startup failures (#8084)
Signed-off-by: PeaceMaker-best <221849497+PeaceMaker-best@users.noreply.github.com> Co-authored-by: PeaceMaker-best <221849497+PeaceMaker-best@users.noreply.github.com>
This commit is contained in:
parent
226d1bc5ce
commit
91f4fb4570
@ -111,6 +111,10 @@ the same item or undo a pause/cancellation observed after their initial read.
|
||||
|
||||
### Memory System
|
||||
|
||||
If the local retrieval index is busy, read-only, or temporarily unavailable,
|
||||
DeerMem falls back to substring search without deleting it. Automatic index
|
||||
recreation is reserved for SQLite-confirmed corruption or a non-database file.
|
||||
|
||||
LLM-powered persistent context retention across conversations:
|
||||
|
||||
- **Automatic extraction**: Analyzes conversations for user context, facts, and preferences
|
||||
|
||||
@ -264,6 +264,8 @@ multi-instance deployment leaves it inside `storage_path`.
|
||||
Chinese tokenization uses `jieba` only with the `memory-zh` extra.
|
||||
Malformed facts are logged and skipped during rebuild.
|
||||
A fatal rebuild failure keeps lazy retry active.
|
||||
Index startup recreates files only for SQLite CORRUPT/NOTADB codes; operational
|
||||
failures select substring fallback without deleting the existing index.
|
||||
|
||||
Storage sends adapter updates after it releases durable locks.
|
||||
Adapter failures mark the scope dirty.
|
||||
|
||||
@ -741,7 +741,10 @@ def create_fts5_retrieval(config: Any) -> FTS5RetrievalAdapter | None:
|
||||
try:
|
||||
return FTS5RetrievalAdapter(db_path)
|
||||
except sqlite3.DatabaseError as exc:
|
||||
if db_path == ":memory:":
|
||||
# Busy/locked, permissions, I/O and unavailable FTS5 are not evidence
|
||||
# of corruption. Extended SQLite codes retain the primary low byte.
|
||||
error_code = getattr(exc, "sqlite_errorcode", 0) & 0xFF
|
||||
if db_path == ":memory:" or error_code not in {sqlite3.SQLITE_CORRUPT, sqlite3.SQLITE_NOTADB}:
|
||||
logger.warning("SQLite FTS5 is unavailable; DeerMem will use substring retrieval: %s", exc)
|
||||
return None
|
||||
|
||||
|
||||
37
backend/tests/test_memory_index_startup_failures.py
Normal file
37
backend/tests/test_memory_index_startup_failures.py
Normal file
@ -0,0 +1,37 @@
|
||||
"""Operational startup failures must not be treated as index corruption."""
|
||||
|
||||
import sqlite3
|
||||
|
||||
import pytest
|
||||
|
||||
from deerflow.agents.memory.backends.deermem.deermem.config import DeerMemConfig
|
||||
from deerflow.agents.memory.backends.deermem.deermem.core import retrieval
|
||||
|
||||
|
||||
@pytest.mark.parametrize("code", [sqlite3.SQLITE_BUSY, sqlite3.SQLITE_LOCKED, sqlite3.SQLITE_READONLY, sqlite3.SQLITE_IOERR, sqlite3.SQLITE_ERROR])
|
||||
def test_operational_startup_failure_preserves_existing_index(tmp_path, monkeypatch, code):
|
||||
config = DeerMemConfig(storage_path=str(tmp_path))
|
||||
adapter = retrieval.create_fts5_retrieval(config)
|
||||
adapter._engine.index_fact("existing", "remember deployment preference")
|
||||
adapter.close()
|
||||
index = tmp_path / ".retrieval" / "memory-fts5.sqlite3"
|
||||
original = index.read_bytes()
|
||||
attempts = []
|
||||
|
||||
def unavailable(self):
|
||||
attempts.append(self)
|
||||
error = sqlite3.OperationalError("temporary startup failure")
|
||||
error.sqlite_errorcode = code
|
||||
raise error
|
||||
|
||||
with monkeypatch.context() as patched:
|
||||
patched.setattr(retrieval.FTS5Retrieval, "_init_schema", unavailable)
|
||||
assert retrieval.create_fts5_retrieval(config) is None
|
||||
|
||||
assert index.read_bytes() == original
|
||||
assert len(attempts) == 1
|
||||
recovered = retrieval.FTS5Retrieval(index)
|
||||
try:
|
||||
assert [fact["id"] for fact in recovered.search("deployment")] == ["existing"]
|
||||
finally:
|
||||
recovered.close()
|
||||
Loading…
x
Reference in New Issue
Block a user