superlocalmemory 3.8.0 → 3.8.1
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/CHANGELOG.md +60 -0
- package/README.md +32 -120
- package/package.json +9 -2
- package/plugin/.claude-plugin/plugin.json +1 -2
- package/plugin/CLAUDE.md +3 -3
- package/plugin/agents/slm-governance-advisor.md +1 -1
- package/plugin/agents/slm-loop-runner.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/agents/slm-optimize-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-cache/SKILL.md +1 -1
- package/plugin/skills/slm-compress/SKILL.md +1 -1
- package/plugin/skills/slm-governance/SKILL.md +1 -1
- package/plugin/skills/slm-graph/SKILL.md +1 -1
- package/plugin/skills/slm-loop/SKILL.md +1 -1
- package/plugin/skills/slm-mesh/SKILL.md +1 -1
- package/plugin/skills/slm-profile/SKILL.md +1 -1
- package/plugin/skills/slm-recall/SKILL.md +1 -1
- package/plugin/skills/slm-remember/SKILL.md +1 -1
- package/plugin/skills/slm-scope/SKILL.md +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin/skills/slm-status/SKILL.md +1 -1
- package/plugin-src/rules/AGENTS.md +1 -1
- package/plugin-src/skills/slm-cache/SKILL.md +1 -1
- package/plugin-src/skills/slm-compress/SKILL.md +1 -1
- package/plugin-src/skills/slm-graph/SKILL.md +1 -1
- package/plugin-src/skills/slm-recall/SKILL.md +1 -1
- package/plugin-src/skills/slm-remember/SKILL.md +1 -1
- package/plugin-src/skills/slm-session/SKILL.md +1 -1
- package/plugin-src/skills/slm-status/SKILL.md +1 -1
- package/pyproject.toml +2 -1
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/commands.py +134 -7
- package/src/superlocalmemory/cli/daemon.py +7 -0
- package/src/superlocalmemory/cli/loop_cmd.py +2 -7
- package/src/superlocalmemory/cli/main.py +10 -4
- package/src/superlocalmemory/cli/version_banner.py +17 -3
- package/src/superlocalmemory/core/backend_orchestrator.py +18 -16
- package/src/superlocalmemory/core/embedding_worker.py +4 -5
- package/src/superlocalmemory/core/embeddings.py +119 -45
- package/src/superlocalmemory/core/engine.py +24 -21
- package/src/superlocalmemory/core/engine_ingestion.py +332 -45
- package/src/superlocalmemory/core/ingestion_command.py +134 -25
- package/src/superlocalmemory/core/injection.py +12 -7
- package/src/superlocalmemory/core/maintenance_scheduler.py +17 -7
- package/src/superlocalmemory/core/recall_pipeline.py +5 -3
- package/src/superlocalmemory/core/store_pipeline.py +153 -20
- package/src/superlocalmemory/hooks/hook_handlers.py +6 -1
- package/src/superlocalmemory/hooks/portable_kit.py +34 -2
- package/src/superlocalmemory/learning/model_rollback.py +3 -0
- package/src/superlocalmemory/learning/ranker_retrain_online.py +2 -0
- package/src/superlocalmemory/learning/reward.py +50 -0
- package/src/superlocalmemory/learning/source_quality.py +523 -1
- package/src/superlocalmemory/loops/ledger.py +25 -5
- package/src/superlocalmemory/mcp/server.py +11 -30
- package/src/superlocalmemory/mcp/tools_core.py +4 -3
- package/src/superlocalmemory/mcp/tools_learning.py +2 -2
- package/src/superlocalmemory/retrieval/engine.py +53 -21
- package/src/superlocalmemory/retrieval/reranker.py +3 -4
- package/src/superlocalmemory/server/config_file.py +90 -0
- package/src/superlocalmemory/server/origin.py +50 -0
- package/src/superlocalmemory/server/routes/backup.py +293 -70
- package/src/superlocalmemory/server/routes/behavioral.py +336 -59
- package/src/superlocalmemory/server/routes/brain.py +57 -16
- package/src/superlocalmemory/server/routes/config_api.py +84 -82
- package/src/superlocalmemory/server/routes/entity.py +100 -23
- package/src/superlocalmemory/server/routes/evolution.py +103 -100
- package/src/superlocalmemory/server/routes/learning.py +276 -105
- package/src/superlocalmemory/server/routes/learning_telemetry.py +153 -0
- package/src/superlocalmemory/server/routes/mesh.py +121 -32
- package/src/superlocalmemory/server/routes/ratelimit.py +33 -25
- package/src/superlocalmemory/server/routes/stats.py +93 -155
- package/src/superlocalmemory/server/routes/token.py +3 -13
- package/src/superlocalmemory/server/routes/v3_api.py +64 -20
- package/src/superlocalmemory/server/unified_daemon.py +467 -40
- package/src/superlocalmemory/storage/migration_runner.py +79 -1
- package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +5 -0
- package/src/superlocalmemory/storage/migrations/M028_fact_entity_associations.py +270 -0
- package/src/superlocalmemory/storage/migrations/M029_behavioral_history_indexes.py +137 -0
- package/src/superlocalmemory/storage/migrations/M030_entity_explorer_indexes.py +93 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +4 -0
- package/src/superlocalmemory/storage/schema.py +49 -1
- package/src/superlocalmemory/storage/schema_v32.py +2 -0
- package/src/superlocalmemory/storage/schema_v347.py +4 -0
- package/src/superlocalmemory/ui/index.html +3 -6
- package/src/superlocalmemory/ui/js/core.js +52 -9
- package/src/superlocalmemory/ui/js/dashboard.js +169 -82
- package/src/superlocalmemory/ui/js/od-backup.js +156 -65
- package/src/superlocalmemory/ui/js/od-brain.js +88 -51
- package/src/superlocalmemory/ui/js/od-entities.js +22 -22
- package/src/superlocalmemory/ui/js/od-graph.js +11 -4
- package/src/superlocalmemory/ui/js/od-memories.js +47 -5
- package/src/superlocalmemory/ui/js/od-mesh.js +23 -9
- package/src/superlocalmemory/ui/js/od-settings.js +113 -59
- package/src/superlocalmemory/ui/js/od-shell.js +249 -33
- package/src/superlocalmemory/ui/js/od-skills.js +44 -17
- package/src/superlocalmemory/ui/js/settings.js +15 -1
- package/plugin-src/.mcp.json +0 -12
- package/plugin-src/agents/slm-governance-advisor.md +0 -80
- package/plugin-src/agents/slm-loop-runner.md +0 -71
- package/plugin-src/agents/slm-memory-advisor.md +0 -49
- package/plugin-src/agents/slm-optimize-advisor.md +0 -44
- package/plugin-src/commands/slm-loop.md +0 -31
- package/plugin-src/hooks/.gitkeep +0 -0
- package/plugin-src/hooks/hooks.json +0 -102
- package/plugin-src/manifest.json +0 -30
- package/plugin-src/requirements.txt +0 -1
- package/plugin-src/rules/CLAUDE.md.fragment +0 -44
- package/plugin-src/scripts/ensure-venv.bat +0 -122
- package/plugin-src/scripts/ensure-venv.sh +0 -105
- package/plugin-src/scripts/slm-launch +0 -62
- package/plugin-src/scripts/slm-launch.bat +0 -23
- package/plugin-src/settings.json +0 -25
- package/plugin-src/skills/slm-governance/SKILL.md +0 -248
- package/plugin-src/skills/slm-loop/SKILL.md +0 -99
- package/plugin-src/skills/slm-mesh/SKILL.md +0 -282
- package/plugin-src/skills/slm-profile/SKILL.md +0 -148
- package/plugin-src/skills/slm-scope/SKILL.md +0 -176
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
All PyTorch/model work runs in a SEPARATE subprocess. The main process
|
|
8
8
|
(dashboard, MCP, CLI) never imports torch and stays at ~60 MB.
|
|
9
9
|
|
|
10
|
-
The worker subprocess
|
|
11
|
-
|
|
10
|
+
The worker subprocess has a configurable idle timeout and respawns on the
|
|
11
|
+
next embed call when it has been unloaded.
|
|
12
12
|
|
|
13
13
|
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
14
14
|
"""
|
|
@@ -29,14 +29,14 @@ from typing import TYPE_CHECKING
|
|
|
29
29
|
|
|
30
30
|
import numpy as np
|
|
31
31
|
|
|
32
|
+
from superlocalmemory.core.config import EmbeddingConfig
|
|
33
|
+
|
|
32
34
|
# Track all live embedding services for atexit cleanup
|
|
33
35
|
_live_embedding_services: set[weakref.ref] = set()
|
|
34
36
|
|
|
35
37
|
if TYPE_CHECKING:
|
|
36
38
|
from numpy.typing import NDArray
|
|
37
39
|
|
|
38
|
-
from superlocalmemory.core.config import EmbeddingConfig
|
|
39
|
-
|
|
40
40
|
logger = logging.getLogger(__name__)
|
|
41
41
|
|
|
42
42
|
# Fisher variance constants
|
|
@@ -63,6 +63,7 @@ class DimensionMismatchError(RuntimeError):
|
|
|
63
63
|
|
|
64
64
|
_MAX_CONCURRENT_WORKERS = int(os.environ.get("SLM_MAX_EMBEDDING_WORKERS", 1))
|
|
65
65
|
_embedding_lock_fd: int | None = None
|
|
66
|
+
_embedding_lock_state_guard = threading.Lock()
|
|
66
67
|
|
|
67
68
|
|
|
68
69
|
def _embedding_lock_file() -> Path:
|
|
@@ -106,57 +107,87 @@ def register_embedding_worker_pid(pid: int) -> None:
|
|
|
106
107
|
def acquire_embedding_lock(timeout: float = 5.0) -> bool:
|
|
107
108
|
"""Acquire system-wide embedding worker lock.
|
|
108
109
|
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
The caller must re-check the PID file after acquisition before spawning.
|
|
111
|
+
POSIX uses flock; Windows uses a one-byte msvcrt lock.
|
|
111
112
|
Returns True if lock acquired (safe to spawn), False if another worker active.
|
|
112
113
|
"""
|
|
113
114
|
global _embedding_lock_fd
|
|
114
115
|
|
|
115
|
-
#
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
lock_file = _embedding_lock_file()
|
|
124
|
-
lock_file.parent.mkdir(parents=True, exist_ok=True)
|
|
116
|
+
# Serialize local contenders as well as cross-process contenders. The file
|
|
117
|
+
# descriptor stays local until its OS lock succeeds, so a failed acquire
|
|
118
|
+
# can never overwrite and leak the descriptor that owns the live worker.
|
|
119
|
+
with _embedding_lock_state_guard:
|
|
120
|
+
if _embedding_lock_fd is not None:
|
|
121
|
+
return False
|
|
122
|
+
if _is_embedding_worker_alive():
|
|
123
|
+
return False
|
|
125
124
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
125
|
+
lock_file = _embedding_lock_file()
|
|
126
|
+
lock_file.parent.mkdir(parents=True, exist_ok=True)
|
|
127
|
+
candidate_fd: int | None = None
|
|
128
|
+
try:
|
|
129
|
+
candidate_fd = os.open(str(lock_file), os.O_CREAT | os.O_RDWR)
|
|
130
|
+
deadline = time.time() + timeout
|
|
131
|
+
while time.time() < deadline:
|
|
132
|
+
try:
|
|
133
|
+
if sys.platform == "win32":
|
|
134
|
+
import msvcrt
|
|
135
|
+
|
|
136
|
+
if os.fstat(candidate_fd).st_size == 0:
|
|
137
|
+
os.write(candidate_fd, b"\0")
|
|
138
|
+
os.lseek(candidate_fd, 0, os.SEEK_SET)
|
|
139
|
+
msvcrt.locking(candidate_fd, msvcrt.LK_NBLCK, 1)
|
|
140
|
+
else:
|
|
141
|
+
import fcntl
|
|
142
|
+
|
|
143
|
+
fcntl.flock(
|
|
144
|
+
candidate_fd,
|
|
145
|
+
fcntl.LOCK_EX | fcntl.LOCK_NB,
|
|
146
|
+
)
|
|
147
|
+
_embedding_lock_fd = candidate_fd
|
|
148
|
+
return True
|
|
149
|
+
except (BlockingIOError, OSError):
|
|
150
|
+
time.sleep(0.2)
|
|
151
|
+
os.close(candidate_fd)
|
|
152
|
+
return False
|
|
153
|
+
except Exception:
|
|
154
|
+
if candidate_fd is not None:
|
|
155
|
+
try:
|
|
156
|
+
os.close(candidate_fd)
|
|
157
|
+
except OSError:
|
|
158
|
+
pass
|
|
159
|
+
return False
|
|
141
160
|
|
|
142
161
|
|
|
143
162
|
def release_embedding_lock() -> None:
|
|
144
163
|
"""Release system-wide embedding worker lock."""
|
|
145
164
|
global _embedding_lock_fd
|
|
146
|
-
|
|
165
|
+
with _embedding_lock_state_guard:
|
|
166
|
+
if _embedding_lock_fd is None:
|
|
167
|
+
return
|
|
147
168
|
try:
|
|
148
|
-
|
|
149
|
-
|
|
169
|
+
if sys.platform == "win32":
|
|
170
|
+
import msvcrt
|
|
171
|
+
|
|
172
|
+
os.lseek(_embedding_lock_fd, 0, os.SEEK_SET)
|
|
173
|
+
msvcrt.locking(_embedding_lock_fd, msvcrt.LK_UNLCK, 1)
|
|
174
|
+
else:
|
|
175
|
+
import fcntl
|
|
176
|
+
|
|
177
|
+
fcntl.flock(_embedding_lock_fd, fcntl.LOCK_UN)
|
|
150
178
|
os.close(_embedding_lock_fd)
|
|
151
179
|
except Exception:
|
|
152
|
-
|
|
180
|
+
try:
|
|
181
|
+
os.close(_embedding_lock_fd)
|
|
182
|
+
except OSError:
|
|
183
|
+
pass
|
|
153
184
|
_embedding_lock_fd = None
|
|
154
185
|
|
|
155
186
|
|
|
156
|
-
_IDLE_TIMEOUT_SECONDS =
|
|
157
|
-
# V3.
|
|
158
|
-
#
|
|
159
|
-
#
|
|
187
|
+
_IDLE_TIMEOUT_SECONDS = 1800 # 30 minutes — keep interactive sessions warm.
|
|
188
|
+
# V3.8.1: existing-user soaks showed that the five-minute policy repeatedly
|
|
189
|
+
# recycled a ~1.1 GB local model and imposed 20-30 second cold starts. The
|
|
190
|
+
# explicit environment override remains available for low-RAM installations.
|
|
160
191
|
_IDLE_TIMEOUT_SECONDS = int(os.environ.get("SLM_EMBED_IDLE_TIMEOUT", _IDLE_TIMEOUT_SECONDS))
|
|
161
192
|
# V3.3.21: Configurable response timeout — 180s default, but batch ingestion
|
|
162
193
|
# (2-turn chunks across 10 conversations) needs 600s+ to survive cold-start
|
|
@@ -186,6 +217,7 @@ class EmbeddingService:
|
|
|
186
217
|
self._last_used: float = 0.0
|
|
187
218
|
self._idle_timer: threading.Timer | None = None
|
|
188
219
|
self._worker_ready = False
|
|
220
|
+
self._owns_worker_lock = False
|
|
189
221
|
self._request_count: int = 0
|
|
190
222
|
self._http_client: object | None = None
|
|
191
223
|
|
|
@@ -478,18 +510,32 @@ class EmbeddingService:
|
|
|
478
510
|
v3.4.13: Machine-wide singleton — checks PID file before spawning.
|
|
479
511
|
Only ONE embedding_worker can exist at a time on the machine.
|
|
480
512
|
"""
|
|
481
|
-
if self._worker_proc is not None
|
|
482
|
-
|
|
483
|
-
|
|
513
|
+
if self._worker_proc is not None:
|
|
514
|
+
if self._worker_proc.poll() is None:
|
|
515
|
+
return
|
|
516
|
+
# An unexpectedly exited child still leaves this service holding
|
|
517
|
+
# its lifetime flock. Fully close the dead process and release that
|
|
518
|
+
# lock before attempting the normal acquire/spawn sequence. Merely
|
|
519
|
+
# dropping the Popen reference makes the process deadlock against
|
|
520
|
+
# its own old flock until the acquire timeout expires.
|
|
521
|
+
self._kill_worker()
|
|
484
522
|
|
|
485
|
-
#
|
|
523
|
+
# Serialize the check/spawn/register sequence across processes. Checking
|
|
524
|
+
# the PID file without this lock allows two cold callers to both see no
|
|
525
|
+
# worker and launch memory-heavy children.
|
|
526
|
+
if not acquire_embedding_lock():
|
|
527
|
+
logger.debug("Embedding worker owned by another process")
|
|
528
|
+
self._available = False
|
|
529
|
+
return
|
|
486
530
|
if _is_embedding_worker_alive():
|
|
487
|
-
|
|
531
|
+
release_embedding_lock()
|
|
532
|
+
logger.debug("Embedding worker already alive after lock acquisition")
|
|
488
533
|
self._available = False
|
|
489
534
|
return
|
|
490
535
|
|
|
491
536
|
# V3.3.28: Check memory pressure before spawning
|
|
492
537
|
if not self._check_memory_pressure():
|
|
538
|
+
release_embedding_lock()
|
|
493
539
|
logger.warning("Skipping embedding worker spawn due to memory pressure")
|
|
494
540
|
self._available = False
|
|
495
541
|
return
|
|
@@ -524,9 +570,23 @@ class EmbeddingService:
|
|
|
524
570
|
)
|
|
525
571
|
# v3.4.13: Register PID for machine-wide singleton guard
|
|
526
572
|
register_embedding_worker_pid(self._worker_proc.pid)
|
|
573
|
+
self._owns_worker_lock = True
|
|
527
574
|
logger.info("Embedding worker spawned (PID %d)", self._worker_proc.pid)
|
|
528
575
|
self._worker_ready = True
|
|
529
576
|
except Exception as exc:
|
|
577
|
+
failed_proc = self._worker_proc
|
|
578
|
+
if failed_proc is not None:
|
|
579
|
+
try:
|
|
580
|
+
failed_proc.terminate()
|
|
581
|
+
failed_proc.wait(timeout=3)
|
|
582
|
+
except Exception:
|
|
583
|
+
try:
|
|
584
|
+
failed_proc.kill()
|
|
585
|
+
failed_proc.wait(timeout=3)
|
|
586
|
+
except Exception:
|
|
587
|
+
pass
|
|
588
|
+
release_embedding_lock()
|
|
589
|
+
self._owns_worker_lock = False
|
|
530
590
|
logger.warning(
|
|
531
591
|
"Failed to spawn embedding worker: %s. "
|
|
532
592
|
"Run 'slm doctor' to verify your Python environment. "
|
|
@@ -576,9 +636,23 @@ class EmbeddingService:
|
|
|
576
636
|
stream.close()
|
|
577
637
|
except (BrokenPipeError, OSError, ValueError):
|
|
578
638
|
pass
|
|
639
|
+
if getattr(self, "_owns_worker_lock", False):
|
|
640
|
+
try:
|
|
641
|
+
pid_file = _embedding_pid_file()
|
|
642
|
+
if (
|
|
643
|
+
proc is not None
|
|
644
|
+
and pid_file.exists()
|
|
645
|
+
and pid_file.read_text().strip() == str(proc.pid)
|
|
646
|
+
):
|
|
647
|
+
pid_file.unlink(missing_ok=True)
|
|
648
|
+
except (OSError, ValueError):
|
|
649
|
+
pass
|
|
650
|
+
finally:
|
|
651
|
+
self._owns_worker_lock = False
|
|
652
|
+
release_embedding_lock()
|
|
579
653
|
|
|
580
654
|
def _reset_idle_timer(self) -> None:
|
|
581
|
-
"""Reset
|
|
655
|
+
"""Reset the configurable worker-idle timer."""
|
|
582
656
|
if self._idle_timer is not None:
|
|
583
657
|
self._idle_timer.cancel()
|
|
584
658
|
self._idle_timer = threading.Timer(
|
|
@@ -180,6 +180,15 @@ class MemoryEngine:
|
|
|
180
180
|
except Exception as exc:
|
|
181
181
|
logger.warning("V3.4.6 schema migration failed: %s", exc)
|
|
182
182
|
|
|
183
|
+
# V3.4.7: Apply "Learning Brain" schema before deferred migrations.
|
|
184
|
+
# M029 adds composite history indexes to these runtime tables, so the
|
|
185
|
+
# tables must exist before the migration runner records M029 complete.
|
|
186
|
+
try:
|
|
187
|
+
from superlocalmemory.storage.schema_v347 import apply_v347_schema
|
|
188
|
+
apply_v347_schema(str(self._db.db_path))
|
|
189
|
+
except Exception as exc:
|
|
190
|
+
logger.warning("V3.4.7 schema migration failed: %s", exc)
|
|
191
|
+
|
|
183
192
|
# v3.6.15: apply ALL pending migrations — including DEFERRED ones like
|
|
184
193
|
# M016 (scope/shared_with columns) — for DIRECT-engine usage: `slm
|
|
185
194
|
# remember --sync`, the Python API, and LangChain/CrewAI integrations.
|
|
@@ -213,13 +222,6 @@ class MemoryEngine:
|
|
|
213
222
|
f"ingestion migration failed: {exc}"
|
|
214
223
|
) from exc
|
|
215
224
|
|
|
216
|
-
# V3.4.7: Apply "Learning Brain" schema (tool_events, behavioral_assertions)
|
|
217
|
-
try:
|
|
218
|
-
from superlocalmemory.storage.schema_v347 import apply_v347_schema
|
|
219
|
-
apply_v347_schema(str(self._db.db_path))
|
|
220
|
-
except Exception as exc:
|
|
221
|
-
logger.warning("V3.4.7 schema migration failed: %s", exc)
|
|
222
|
-
|
|
223
225
|
# V3.4.10: Apply "Fortress" schema (backup_destinations, entity_blacklist)
|
|
224
226
|
try:
|
|
225
227
|
from superlocalmemory.storage.schema_v3410 import apply_v3410_schema
|
|
@@ -360,16 +362,17 @@ class MemoryEngine:
|
|
|
360
362
|
|
|
361
363
|
self._check_embedding_migration()
|
|
362
364
|
|
|
363
|
-
#
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
365
|
+
# Lifecycle/tier evaluation, bounded housekeeping, and backup checks
|
|
366
|
+
# must continue even when optional forgetting/math maintenance is
|
|
367
|
+
# disabled. The scheduler itself gates those optional calculations.
|
|
368
|
+
try:
|
|
369
|
+
from superlocalmemory.core.maintenance_scheduler import MaintenanceScheduler
|
|
370
|
+
self._maintenance_scheduler = MaintenanceScheduler(
|
|
371
|
+
self._db, self._config, self._profile_id,
|
|
372
|
+
)
|
|
373
|
+
self._maintenance_scheduler.start()
|
|
374
|
+
except Exception as exc:
|
|
375
|
+
logger.debug("Maintenance scheduler init failed: %s", exc)
|
|
373
376
|
|
|
374
377
|
def _process_pending_memories(self) -> None:
|
|
375
378
|
"""Process pending memories from store-first async pattern.
|
|
@@ -624,10 +627,10 @@ class MemoryEngine:
|
|
|
624
627
|
``put_nowait`` and the actual ``pending_outcomes`` INSERT runs
|
|
625
628
|
on a background worker.
|
|
626
629
|
|
|
627
|
-
``fast=True`` is the latency-bounded path: it
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
630
|
+
``fast=True`` is the latency-bounded path: it retains all six local
|
|
631
|
+
retrieval channels but skips remote agentic verification. Use
|
|
632
|
+
``fast=False`` only when maximum multi-round quality matters more than
|
|
633
|
+
response time.
|
|
631
634
|
|
|
632
635
|
Multi-scope: ``include_global`` / ``include_shared`` control which
|
|
633
636
|
scopes participate in retrieval. ``None`` (the default) means "use the
|