superlocalmemory 3.0.32 → 3.0.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "superlocalmemory",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.34",
|
|
4
4
|
"description": "Information-geometric agent memory with mathematical guarantees. 4-channel retrieval, Fisher-Rao similarity, zero-LLM mode, EU AI Act compliant. Works with Claude, Cursor, Windsurf, and 17+ AI tools.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-memory",
|
package/pyproject.toml
CHANGED
|
@@ -348,7 +348,7 @@ class SLMConfig:
|
|
|
348
348
|
),
|
|
349
349
|
llm=LLMConfig(), # No LLM
|
|
350
350
|
retrieval=RetrievalConfig(
|
|
351
|
-
use_cross_encoder=
|
|
351
|
+
use_cross_encoder=False, # Disabled: 30s PyTorch cold start kills UX
|
|
352
352
|
),
|
|
353
353
|
math=MathConfig(
|
|
354
354
|
sheaf_contradiction_threshold=0.45, # 768d threshold
|
|
@@ -370,7 +370,7 @@ class SLMConfig:
|
|
|
370
370
|
api_base=llm_api_base or "http://localhost:11434",
|
|
371
371
|
api_key=llm_api_key or "",
|
|
372
372
|
),
|
|
373
|
-
retrieval=RetrievalConfig(use_cross_encoder=
|
|
373
|
+
retrieval=RetrievalConfig(use_cross_encoder=False),
|
|
374
374
|
)
|
|
375
375
|
|
|
376
376
|
# Mode C — FULL POWER, UNRESTRICTED
|
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
"""SuperLocalMemory V3 — Database Manager.
|
|
6
6
|
|
|
7
7
|
SQLite with WAL, profile-scoped CRUD, FTS5 search, BM25 persistence.
|
|
8
|
-
|
|
8
|
+
Concurrent-safe: WAL mode + busy_timeout + retry on SQLITE_BUSY.
|
|
9
|
+
Multiple processes (MCP, CLI, integrations) can read/write safely.
|
|
9
10
|
|
|
10
11
|
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
11
12
|
"""
|
|
12
13
|
from __future__ import annotations
|
|
13
14
|
|
|
14
|
-
import json, logging, sqlite3, threading
|
|
15
|
+
import json, logging, sqlite3, threading, time
|
|
15
16
|
from contextlib import contextmanager
|
|
16
17
|
from pathlib import Path
|
|
17
18
|
from types import ModuleType
|
|
@@ -37,11 +38,22 @@ def _jd(val: Any) -> str | None:
|
|
|
37
38
|
return json.dumps(val) if val is not None else None
|
|
38
39
|
|
|
39
40
|
|
|
41
|
+
_BUSY_TIMEOUT_MS = 10_000 # 10 seconds — wait for other writers
|
|
42
|
+
_MAX_RETRIES = 5 # retry on transient SQLITE_BUSY
|
|
43
|
+
_RETRY_BASE_DELAY = 0.1 # seconds — exponential backoff base
|
|
44
|
+
|
|
45
|
+
|
|
40
46
|
class DatabaseManager:
|
|
41
|
-
"""
|
|
47
|
+
"""Concurrent-safe SQLite manager with WAL, profile isolation, and FTS5.
|
|
48
|
+
|
|
49
|
+
Designed for multi-process access: MCP server, CLI, LangChain, CrewAI,
|
|
50
|
+
and other integrations can all read/write the same database safely.
|
|
42
51
|
|
|
43
|
-
|
|
44
|
-
|
|
52
|
+
Concurrency model:
|
|
53
|
+
- WAL mode: readers never block writers, writers never block readers
|
|
54
|
+
- busy_timeout: writers wait up to 10s for other writers instead of failing
|
|
55
|
+
- Retry with backoff: transient SQLITE_BUSY errors are retried automatically
|
|
56
|
+
- Per-call connections: no shared state between processes
|
|
45
57
|
"""
|
|
46
58
|
|
|
47
59
|
def __init__(self, db_path: str | Path) -> None:
|
|
@@ -55,6 +67,7 @@ class DatabaseManager:
|
|
|
55
67
|
conn = sqlite3.connect(str(self.db_path))
|
|
56
68
|
try:
|
|
57
69
|
conn.execute("PRAGMA journal_mode=WAL")
|
|
70
|
+
conn.execute(f"PRAGMA busy_timeout={_BUSY_TIMEOUT_MS}")
|
|
58
71
|
conn.execute("PRAGMA foreign_keys=ON")
|
|
59
72
|
conn.commit()
|
|
60
73
|
finally:
|
|
@@ -62,9 +75,8 @@ class DatabaseManager:
|
|
|
62
75
|
|
|
63
76
|
def initialize(self, schema_module: ModuleType) -> None:
|
|
64
77
|
"""Create all tables. *schema_module* must expose ``create_all_tables(conn)``."""
|
|
65
|
-
conn =
|
|
78
|
+
conn = self._connect()
|
|
66
79
|
try:
|
|
67
|
-
conn.execute("PRAGMA foreign_keys=ON")
|
|
68
80
|
schema_module.create_all_tables(conn)
|
|
69
81
|
conn.commit()
|
|
70
82
|
logger.info("Schema initialized at %s", self.db_path)
|
|
@@ -81,8 +93,9 @@ class DatabaseManager:
|
|
|
81
93
|
self.close()
|
|
82
94
|
|
|
83
95
|
def _connect(self) -> sqlite3.Connection:
|
|
84
|
-
conn = sqlite3.connect(str(self.db_path))
|
|
96
|
+
conn = sqlite3.connect(str(self.db_path), timeout=_BUSY_TIMEOUT_MS / 1000)
|
|
85
97
|
conn.row_factory = sqlite3.Row
|
|
98
|
+
conn.execute(f"PRAGMA busy_timeout={_BUSY_TIMEOUT_MS}")
|
|
86
99
|
conn.execute("PRAGMA foreign_keys=ON")
|
|
87
100
|
return conn
|
|
88
101
|
|
|
@@ -103,16 +116,36 @@ class DatabaseManager:
|
|
|
103
116
|
conn.close()
|
|
104
117
|
|
|
105
118
|
def execute(self, sql: str, params: tuple[Any, ...] = ()) -> list[sqlite3.Row]:
|
|
106
|
-
"""Execute SQL
|
|
119
|
+
"""Execute SQL with automatic retry on SQLITE_BUSY.
|
|
120
|
+
|
|
121
|
+
Uses shared conn inside transaction, else per-call with retry.
|
|
122
|
+
"""
|
|
107
123
|
if self._txn_conn is not None:
|
|
108
124
|
return self._txn_conn.execute(sql, params).fetchall()
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
conn.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
125
|
+
|
|
126
|
+
last_error: Exception | None = None
|
|
127
|
+
for attempt in range(_MAX_RETRIES):
|
|
128
|
+
conn = self._connect()
|
|
129
|
+
try:
|
|
130
|
+
rows = conn.execute(sql, params).fetchall()
|
|
131
|
+
conn.commit()
|
|
132
|
+
return rows
|
|
133
|
+
except sqlite3.OperationalError as exc:
|
|
134
|
+
last_error = exc
|
|
135
|
+
if "locked" in str(exc).lower() or "busy" in str(exc).lower():
|
|
136
|
+
delay = _RETRY_BASE_DELAY * (2 ** attempt)
|
|
137
|
+
logger.debug(
|
|
138
|
+
"DB busy (attempt %d/%d), retrying in %.1fs: %s",
|
|
139
|
+
attempt + 1, _MAX_RETRIES, delay, exc,
|
|
140
|
+
)
|
|
141
|
+
time.sleep(delay)
|
|
142
|
+
continue
|
|
143
|
+
raise
|
|
144
|
+
finally:
|
|
145
|
+
conn.close()
|
|
146
|
+
|
|
147
|
+
logger.warning("DB operation failed after %d retries: %s", _MAX_RETRIES, last_error)
|
|
148
|
+
raise last_error # type: ignore[misc]
|
|
116
149
|
|
|
117
150
|
def store_memory(self, record: MemoryRecord) -> str:
|
|
118
151
|
"""Persist a raw memory record. Returns memory_id."""
|