superlocalmemory 3.7.8 → 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/.claude-plugin/marketplace.json +1 -1
- package/ATTRIBUTION.md +1 -3
- package/CHANGELOG.md +129 -0
- package/README.md +205 -123
- package/package.json +12 -3
- package/plugin/.claude-plugin/plugin.json +2 -3
- package/plugin/CLAUDE.md +8 -8
- package/plugin/agents/slm-governance-advisor.md +80 -0
- package/plugin/agents/slm-loop-runner.md +71 -0
- package/plugin/agents/slm-memory-advisor.md +10 -5
- package/plugin/agents/slm-optimize-advisor.md +9 -3
- package/plugin/commands/slm-loop.md +31 -0
- package/plugin/hooks/hooks.json +79 -0
- package/plugin/requirements.txt +1 -1
- package/plugin/scripts/slm-launch +46 -7
- package/plugin/settings.json +9 -0
- package/plugin/skills/slm-cache/SKILL.md +9 -1
- package/plugin/skills/slm-compress/SKILL.md +8 -1
- package/plugin/skills/slm-governance/SKILL.md +248 -0
- package/plugin/skills/slm-graph/SKILL.md +17 -3
- package/plugin/skills/slm-loop/SKILL.md +99 -0
- package/plugin/skills/slm-mesh/SKILL.md +282 -0
- package/plugin/skills/slm-profile/SKILL.md +148 -0
- package/plugin/skills/slm-recall/SKILL.md +46 -10
- package/plugin/skills/slm-remember/SKILL.md +48 -1
- package/plugin/skills/slm-scope/SKILL.md +176 -0
- package/plugin/skills/slm-session/SKILL.md +24 -1
- package/plugin/skills/slm-status/SKILL.md +18 -1
- package/plugin-src/rules/AGENTS.md +57 -18
- package/plugin-src/skills/slm-cache/SKILL.md +9 -1
- package/plugin-src/skills/slm-compress/SKILL.md +8 -1
- package/plugin-src/skills/slm-graph/SKILL.md +17 -3
- package/plugin-src/skills/slm-recall/SKILL.md +46 -10
- package/plugin-src/skills/slm-remember/SKILL.md +48 -1
- package/plugin-src/skills/slm-session/SKILL.md +24 -1
- package/plugin-src/skills/slm-status/SKILL.md +18 -1
- package/pyproject.toml +2 -1
- package/scripts/postinstall/validation.js +2 -0
- package/scripts/postinstall-interactive.js +74 -2
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/access/__init__.py +3 -0
- package/src/superlocalmemory/access/rbac.py +477 -0
- package/src/superlocalmemory/cli/commands.py +228 -17
- package/src/superlocalmemory/cli/compress_cmd.py +17 -7
- package/src/superlocalmemory/cli/daemon.py +7 -0
- package/src/superlocalmemory/cli/loop_cmd.py +187 -0
- package/src/superlocalmemory/cli/main.py +49 -8
- package/src/superlocalmemory/cli/mesh_cmd.py +38 -0
- package/src/superlocalmemory/cli/optimize_cmd.py +3 -0
- package/src/superlocalmemory/cli/pending_store.py +49 -13
- package/src/superlocalmemory/cli/proxy_cmd.py +4 -0
- package/src/superlocalmemory/cli/scale_engine_cmd.py +6 -0
- package/src/superlocalmemory/cli/setup_wizard.py +22 -13
- package/src/superlocalmemory/cli/version_banner.py +17 -3
- package/src/superlocalmemory/compliance/audit.py +6 -0
- package/src/superlocalmemory/compliance/gdpr.py +128 -138
- package/src/superlocalmemory/compliance/retention.py +176 -45
- package/src/superlocalmemory/core/backend_orchestrator.py +23 -59
- package/src/superlocalmemory/core/community_summary.py +267 -0
- package/src/superlocalmemory/core/config.py +216 -3
- package/src/superlocalmemory/core/consolidation_engine.py +95 -22
- package/src/superlocalmemory/core/context_cache.py +61 -18
- package/src/superlocalmemory/core/embedding_worker.py +21 -7
- package/src/superlocalmemory/core/embeddings.py +131 -46
- package/src/superlocalmemory/core/engine.py +41 -22
- package/src/superlocalmemory/core/engine_ingestion.py +359 -43
- package/src/superlocalmemory/core/engine_wiring.py +13 -0
- package/src/superlocalmemory/core/entity_community.py +178 -0
- package/src/superlocalmemory/core/graph_analyzer.py +39 -2
- package/src/superlocalmemory/core/graph_pruner.py +13 -8
- package/src/superlocalmemory/core/ingestion_command.py +134 -25
- package/src/superlocalmemory/core/injection.py +12 -7
- package/src/superlocalmemory/core/key_expander.py +138 -0
- package/src/superlocalmemory/core/maintenance.py +23 -0
- package/src/superlocalmemory/core/maintenance_scheduler.py +17 -7
- package/src/superlocalmemory/core/modes.py +1 -1
- package/src/superlocalmemory/core/mutations.py +2 -2
- package/src/superlocalmemory/core/pii.py +105 -0
- package/src/superlocalmemory/core/progressive_abstraction.py +208 -0
- package/src/superlocalmemory/core/recall_pipeline.py +7 -3
- package/src/superlocalmemory/core/recall_worker.py +20 -6
- package/src/superlocalmemory/core/scale_engine.py +60 -1
- package/src/superlocalmemory/core/security_primitives.py +40 -2
- package/src/superlocalmemory/core/store_pipeline.py +186 -29
- package/src/superlocalmemory/core/worker_pool.py +21 -6
- package/src/superlocalmemory/encoding/entity_reflexion.py +200 -0
- package/src/superlocalmemory/encoding/entity_resolver.py +34 -24
- package/src/superlocalmemory/encoding/fact_extractor.py +26 -1
- package/src/superlocalmemory/encoding/temporal_validator.py +64 -1
- package/src/superlocalmemory/evolution/evolution_store.py +122 -45
- package/src/superlocalmemory/evolution/llm_dispatch.py +12 -1
- package/src/superlocalmemory/evolution/model_selection.py +160 -0
- package/src/superlocalmemory/evolution/mutation_generator.py +16 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +127 -42
- package/src/superlocalmemory/evolution/triggers.py +22 -13
- package/src/superlocalmemory/graph/cozo_backend.py +43 -20
- package/src/superlocalmemory/hooks/adapter_base.py +5 -1
- package/src/superlocalmemory/hooks/auto_recall.py +13 -1
- package/src/superlocalmemory/hooks/claude_code_hooks.py +11 -0
- package/src/superlocalmemory/hooks/codex_assets.py +64 -5
- package/src/superlocalmemory/hooks/hook_daemon.py +20 -3
- package/src/superlocalmemory/hooks/hook_handlers.py +6 -1
- package/src/superlocalmemory/hooks/memory_protocol.py +54 -0
- package/src/superlocalmemory/hooks/portable_kit.py +148 -3
- package/src/superlocalmemory/infra/backup.py +12 -1
- package/src/superlocalmemory/infra/daemon_identity.py +40 -4
- package/src/superlocalmemory/infra/data_root.py +43 -4
- package/src/superlocalmemory/infra/event_bus.py +107 -24
- package/src/superlocalmemory/infra/rate_limiter.py +93 -0
- package/src/superlocalmemory/ingestion/adapter_manager.py +4 -1
- package/src/superlocalmemory/ingestion/credentials.py +1 -1
- package/src/superlocalmemory/learning/cross_project.py +28 -19
- 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/reward_proxy.py +42 -9
- package/src/superlocalmemory/learning/source_quality.py +523 -1
- package/src/superlocalmemory/loops/__init__.py +56 -0
- package/src/superlocalmemory/loops/budget.py +58 -0
- package/src/superlocalmemory/loops/engine.py +164 -0
- package/src/superlocalmemory/loops/ledger.py +263 -0
- package/src/superlocalmemory/loops/models.py +152 -0
- package/src/superlocalmemory/loops/rules.py +52 -0
- package/src/superlocalmemory/mcp/_daemon_proxy.py +3 -0
- package/src/superlocalmemory/mcp/_pool_adapter.py +2 -0
- package/src/superlocalmemory/mcp/profiles.py +103 -0
- package/src/superlocalmemory/mcp/server.py +32 -79
- package/src/superlocalmemory/mcp/tools_active.py +4 -7
- package/src/superlocalmemory/mcp/tools_code_graph.py +51 -5
- package/src/superlocalmemory/mcp/tools_core.py +12 -4
- package/src/superlocalmemory/mcp/tools_evolution.py +6 -3
- package/src/superlocalmemory/mcp/tools_learning.py +2 -2
- package/src/superlocalmemory/mcp/tools_loops.py +300 -0
- package/src/superlocalmemory/mcp/tools_mesh.py +140 -4
- package/src/superlocalmemory/mcp/tools_optimize.py +15 -8
- package/src/superlocalmemory/mesh/broker.py +237 -129
- package/src/superlocalmemory/mesh/remote_sync.py +50 -8
- package/src/superlocalmemory/optimize/NOTICE +1 -6
- package/src/superlocalmemory/optimize/adapters/anthropic_adapter.py +1 -4
- package/src/superlocalmemory/optimize/adapters/openai_adapter.py +1 -4
- package/src/superlocalmemory/optimize/cache/semantic.py +27 -19
- package/src/superlocalmemory/optimize/compress/align.py +32 -26
- package/src/superlocalmemory/optimize/compress/ccr.py +14 -71
- package/src/superlocalmemory/optimize/compress/router.py +105 -22
- package/src/superlocalmemory/optimize/config/defaults.py +1 -1
- package/src/superlocalmemory/optimize/config/schema.py +87 -4
- package/src/superlocalmemory/optimize/metrics/counters.py +13 -4
- package/src/superlocalmemory/optimize/metrics/estimator.py +0 -3
- package/src/superlocalmemory/optimize/proxy/_helpers.py +31 -4
- package/src/superlocalmemory/optimize/storage/db.py +38 -9
- package/src/superlocalmemory/optimize/storage/schema.py +10 -0
- package/src/superlocalmemory/parameterization/pattern_extractor.py +6 -3
- package/src/superlocalmemory/retrieval/agentic.py +1 -1
- package/src/superlocalmemory/retrieval/bm25_channel.py +68 -10
- package/src/superlocalmemory/retrieval/engine.py +221 -47
- package/src/superlocalmemory/retrieval/entity_channel.py +7 -5
- package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -2
- package/src/superlocalmemory/retrieval/reranker.py +3 -4
- package/src/superlocalmemory/retrieval/semantic_channel.py +114 -21
- package/src/superlocalmemory/retrieval/spreading_activation.py +11 -2
- package/src/superlocalmemory/retrieval/temporal_channel.py +48 -9
- package/src/superlocalmemory/retrieval/temporal_frame.py +102 -0
- package/src/superlocalmemory/retrieval/temporal_validity_filter.py +135 -0
- package/src/superlocalmemory/retrieval/time_window.py +181 -0
- package/src/superlocalmemory/server/api.py +4 -4
- package/src/superlocalmemory/server/config_file.py +90 -0
- package/src/superlocalmemory/server/origin.py +50 -0
- package/src/superlocalmemory/server/profile_runtime.py +125 -8
- package/src/superlocalmemory/server/rbac_enforce.py +142 -0
- package/src/superlocalmemory/server/recall_health.py +24 -3
- package/src/superlocalmemory/server/recall_serializer.py +19 -1
- package/src/superlocalmemory/server/routes/abstraction.py +115 -0
- package/src/superlocalmemory/server/routes/agents.py +128 -38
- package/src/superlocalmemory/server/routes/backup.py +317 -70
- package/src/superlocalmemory/server/routes/behavioral.py +349 -71
- package/src/superlocalmemory/server/routes/brain.py +69 -12
- package/src/superlocalmemory/server/routes/chat.py +10 -5
- package/src/superlocalmemory/server/routes/compliance.py +171 -21
- package/src/superlocalmemory/server/routes/config_api.py +438 -0
- package/src/superlocalmemory/server/routes/data_io.py +30 -8
- package/src/superlocalmemory/server/routes/entity.py +108 -26
- package/src/superlocalmemory/server/routes/events.py +24 -8
- package/src/superlocalmemory/server/routes/evolution.py +189 -68
- package/src/superlocalmemory/server/routes/helpers.py +16 -1
- package/src/superlocalmemory/server/routes/ingest.py +7 -4
- package/src/superlocalmemory/server/routes/insights.py +3 -3
- package/src/superlocalmemory/server/routes/learning.py +289 -118
- package/src/superlocalmemory/server/routes/learning_telemetry.py +153 -0
- package/src/superlocalmemory/server/routes/lifecycle.py +59 -8
- package/src/superlocalmemory/server/routes/memories.py +182 -57
- package/src/superlocalmemory/server/routes/mesh.py +200 -31
- package/src/superlocalmemory/server/routes/optimize.py +33 -1
- package/src/superlocalmemory/server/routes/prewarm.py +2 -0
- package/src/superlocalmemory/server/routes/profiles.py +63 -17
- package/src/superlocalmemory/server/routes/ratelimit.py +132 -0
- package/src/superlocalmemory/server/routes/rbac.py +367 -0
- package/src/superlocalmemory/server/routes/stats.py +103 -158
- package/src/superlocalmemory/server/routes/tiers.py +11 -9
- package/src/superlocalmemory/server/routes/token.py +3 -13
- package/src/superlocalmemory/server/routes/v3_api.py +247 -89
- package/src/superlocalmemory/server/routes/ws.py +5 -2
- package/src/superlocalmemory/server/security_middleware.py +12 -5
- package/src/superlocalmemory/server/ui.py +20 -5
- package/src/superlocalmemory/server/unified_daemon.py +827 -72
- package/src/superlocalmemory/server/write_identity.py +38 -8
- package/src/superlocalmemory/storage/database.py +265 -53
- package/src/superlocalmemory/storage/migration_runner.py +132 -1
- package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +5 -0
- package/src/superlocalmemory/storage/migrations/M021_ingestion_log_profile.py +108 -0
- package/src/superlocalmemory/storage/migrations/M022_entity_aliases_profile.py +86 -0
- package/src/superlocalmemory/storage/migrations/M023_mesh_profile_isolation.py +194 -0
- package/src/superlocalmemory/storage/migrations/M024_rbac_users_roles.py +87 -0
- package/src/superlocalmemory/storage/migrations/M025_perf_indexes.py +90 -0
- package/src/superlocalmemory/storage/migrations/M026_rbac_memberships_fk.py +136 -0
- package/src/superlocalmemory/storage/migrations/M027_transferable_patterns_profile.py +163 -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/models.py +4 -0
- package/src/superlocalmemory/storage/schema.py +136 -1
- package/src/superlocalmemory/storage/schema_v32.py +2 -0
- package/src/superlocalmemory/storage/schema_v343.py +24 -12
- package/src/superlocalmemory/storage/schema_v347.py +4 -0
- package/src/superlocalmemory/trust/gate.py +49 -8
- package/src/superlocalmemory/ui/assets/slm-icon-white.svg +64 -0
- package/src/superlocalmemory/ui/assets/slm-icon.svg +36 -0
- package/src/superlocalmemory/ui/css/design-system.css +621 -0
- package/src/superlocalmemory/ui/css/neural-glass.css +6 -0
- package/src/superlocalmemory/ui/css/od-bridge.css +158 -0
- package/src/superlocalmemory/ui/favicon.svg +35 -4
- package/src/superlocalmemory/ui/index.html +303 -173
- package/src/superlocalmemory/ui/js/brain.js +5 -20
- package/src/superlocalmemory/ui/js/core.js +100 -41
- package/src/superlocalmemory/ui/js/dashboard.js +403 -65
- package/src/superlocalmemory/ui/js/event-delegation.js +102 -0
- package/src/superlocalmemory/ui/js/knowledge-graph.js +11 -11
- package/src/superlocalmemory/ui/js/math-health.js +1 -1
- package/src/superlocalmemory/ui/js/memories.js +15 -4
- package/src/superlocalmemory/ui/js/memory-chat.js +7 -7
- package/src/superlocalmemory/ui/js/ng-entities.js +6 -8
- package/src/superlocalmemory/ui/js/ng-ingestion.js +4 -4
- package/src/superlocalmemory/ui/js/ng-mesh.js +4 -9
- package/src/superlocalmemory/ui/js/ng-shell.js +8 -8
- package/src/superlocalmemory/ui/js/ng-skills.js +54 -2
- package/src/superlocalmemory/ui/js/od-agents.js +544 -0
- package/src/superlocalmemory/ui/js/od-auth-gate.js +257 -0
- package/src/superlocalmemory/ui/js/od-backup.js +871 -0
- package/src/superlocalmemory/ui/js/od-brain.js +816 -0
- package/src/superlocalmemory/ui/js/od-entities.js +579 -0
- package/src/superlocalmemory/ui/js/od-graph.js +600 -0
- package/src/superlocalmemory/ui/js/od-health.js +539 -0
- package/src/superlocalmemory/ui/js/od-mcp.js +508 -0
- package/src/superlocalmemory/ui/js/od-memories.js +929 -0
- package/src/superlocalmemory/ui/js/od-mesh.js +553 -0
- package/src/superlocalmemory/ui/js/od-operations.js +1250 -0
- package/src/superlocalmemory/ui/js/od-optimize.js +787 -0
- package/src/superlocalmemory/ui/js/od-settings.js +1107 -0
- package/src/superlocalmemory/ui/js/od-shell.js +809 -0
- package/src/superlocalmemory/ui/js/od-skills.js +600 -0
- package/src/superlocalmemory/ui/js/od-team.js +258 -0
- package/src/superlocalmemory/ui/js/profiles.js +159 -46
- package/src/superlocalmemory/ui/js/settings.js +17 -3
- package/src/superlocalmemory/ui/js/timeline.js +34 -5
- package/src/superlocalmemory/ui/js/trust-dashboard.js +2 -2
- package/src/superlocalmemory/vector/lancedb_backend.py +8 -6
- package/plugin-src/.mcp.json +0 -12
- package/plugin-src/agents/slm-memory-advisor.md +0 -44
- package/plugin-src/agents/slm-optimize-advisor.md +0 -38
- package/plugin-src/hooks/.gitkeep +0 -0
- package/plugin-src/hooks/hooks.json +0 -23
- package/plugin-src/manifest.json +0 -25
- 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 -23
- package/plugin-src/scripts/slm-launch.bat +0 -23
- package/plugin-src/settings.json +0 -16
- package/src/superlocalmemory/learning/behavioral_listener.py +0 -94
|
@@ -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
|
|
|
@@ -295,7 +327,13 @@ class EmbeddingService:
|
|
|
295
327
|
never hangs indefinitely on cold model loads or network issues.
|
|
296
328
|
"""
|
|
297
329
|
with self._lock:
|
|
298
|
-
|
|
330
|
+
# Only an explicit terminal disable (``False``) short-circuits. A
|
|
331
|
+
# ``None`` availability is the recall-health self-heal's "re-probe"
|
|
332
|
+
# signal (recall_health._heal_embedder) — it must fall through and
|
|
333
|
+
# respawn the worker, matching OllamaEmbedder's tri-state
|
|
334
|
+
# convention. Using ``not self._available`` here bricked the local
|
|
335
|
+
# worker on the first heal tick, because ``None`` is falsy.
|
|
336
|
+
if self._available is False:
|
|
299
337
|
return None
|
|
300
338
|
# Worker recycling: restart after N requests to prevent
|
|
301
339
|
# C++ allocator fragmentation over long-running sessions.
|
|
@@ -348,6 +386,11 @@ class EmbeddingService:
|
|
|
348
386
|
self._available = False
|
|
349
387
|
self._kill_worker()
|
|
350
388
|
return None
|
|
389
|
+
# A successful embed proves the worker is healthy, so clear any
|
|
390
|
+
# transient/``None`` availability left by a self-heal re-probe
|
|
391
|
+
# back to a definite ``True``. Without this the flag lingers at
|
|
392
|
+
# ``None`` and the next ``not``-style check elsewhere re-blocks.
|
|
393
|
+
self._available = True
|
|
351
394
|
self._reset_idle_timer()
|
|
352
395
|
self._request_count += 1
|
|
353
396
|
return resp["vectors"]
|
|
@@ -467,18 +510,32 @@ class EmbeddingService:
|
|
|
467
510
|
v3.4.13: Machine-wide singleton — checks PID file before spawning.
|
|
468
511
|
Only ONE embedding_worker can exist at a time on the machine.
|
|
469
512
|
"""
|
|
470
|
-
if self._worker_proc is not None
|
|
471
|
-
|
|
472
|
-
|
|
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()
|
|
473
522
|
|
|
474
|
-
#
|
|
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
|
|
475
530
|
if _is_embedding_worker_alive():
|
|
476
|
-
|
|
531
|
+
release_embedding_lock()
|
|
532
|
+
logger.debug("Embedding worker already alive after lock acquisition")
|
|
477
533
|
self._available = False
|
|
478
534
|
return
|
|
479
535
|
|
|
480
536
|
# V3.3.28: Check memory pressure before spawning
|
|
481
537
|
if not self._check_memory_pressure():
|
|
538
|
+
release_embedding_lock()
|
|
482
539
|
logger.warning("Skipping embedding worker spawn due to memory pressure")
|
|
483
540
|
self._available = False
|
|
484
541
|
return
|
|
@@ -513,9 +570,23 @@ class EmbeddingService:
|
|
|
513
570
|
)
|
|
514
571
|
# v3.4.13: Register PID for machine-wide singleton guard
|
|
515
572
|
register_embedding_worker_pid(self._worker_proc.pid)
|
|
573
|
+
self._owns_worker_lock = True
|
|
516
574
|
logger.info("Embedding worker spawned (PID %d)", self._worker_proc.pid)
|
|
517
575
|
self._worker_ready = True
|
|
518
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
|
|
519
590
|
logger.warning(
|
|
520
591
|
"Failed to spawn embedding worker: %s. "
|
|
521
592
|
"Run 'slm doctor' to verify your Python environment. "
|
|
@@ -565,9 +636,23 @@ class EmbeddingService:
|
|
|
565
636
|
stream.close()
|
|
566
637
|
except (BrokenPipeError, OSError, ValueError):
|
|
567
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()
|
|
568
653
|
|
|
569
654
|
def _reset_idle_timer(self) -> None:
|
|
570
|
-
"""Reset
|
|
655
|
+
"""Reset the configurable worker-idle timer."""
|
|
571
656
|
if self._idle_timer is not None:
|
|
572
657
|
self._idle_timer.cancel()
|
|
573
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
|
|
@@ -276,6 +278,18 @@ class MemoryEngine:
|
|
|
276
278
|
)
|
|
277
279
|
self._llm = None
|
|
278
280
|
|
|
281
|
+
# H-03 (3.7.9): surface mode-capability degradation explicitly at
|
|
282
|
+
# startup. validate_mode_config existed but was never called, so Mode B
|
|
283
|
+
# silently using rule-based extraction after Ollama vanished (update,
|
|
284
|
+
# restart, port conflict) went unwarned. Each mode only checks its own
|
|
285
|
+
# capabilities, so passing the single llm-availability signal is safe.
|
|
286
|
+
from superlocalmemory.core.modes import validate_mode_config
|
|
287
|
+
_llm_up = getattr(self, "_llm", None) is not None
|
|
288
|
+
for _warning in validate_mode_config(
|
|
289
|
+
self._config.mode, has_ollama=_llm_up, has_cloud_llm=_llm_up,
|
|
290
|
+
):
|
|
291
|
+
logger.warning("Mode config: %s", _warning)
|
|
292
|
+
|
|
279
293
|
from superlocalmemory.trust.scorer import TrustScorer
|
|
280
294
|
from superlocalmemory.trust.provenance import ProvenanceTracker
|
|
281
295
|
from superlocalmemory.compliance.eu_ai_act import EUAIActChecker
|
|
@@ -348,16 +362,17 @@ class MemoryEngine:
|
|
|
348
362
|
|
|
349
363
|
self._check_embedding_migration()
|
|
350
364
|
|
|
351
|
-
#
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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)
|
|
361
376
|
|
|
362
377
|
def _process_pending_memories(self) -> None:
|
|
363
378
|
"""Process pending memories from store-first async pattern.
|
|
@@ -374,7 +389,9 @@ class MemoryEngine:
|
|
|
374
389
|
return
|
|
375
390
|
|
|
376
391
|
base_dir = self._config.base_dir
|
|
377
|
-
|
|
392
|
+
# Only drain items enqueued under THIS engine's profile — a queued
|
|
393
|
+
# memory must never materialize under a profile it was not written for.
|
|
394
|
+
pending = get_pending(base_dir, limit=20, profile_id=self.profile_id)
|
|
378
395
|
if not pending:
|
|
379
396
|
return
|
|
380
397
|
|
|
@@ -599,6 +616,7 @@ class MemoryEngine:
|
|
|
599
616
|
*,
|
|
600
617
|
include_global: bool | None = None,
|
|
601
618
|
include_shared: bool | None = None,
|
|
619
|
+
window: str | tuple[str, str] | None = None,
|
|
602
620
|
) -> RecallResponse:
|
|
603
621
|
"""Recall relevant facts for a query.
|
|
604
622
|
|
|
@@ -609,10 +627,10 @@ class MemoryEngine:
|
|
|
609
627
|
``put_nowait`` and the actual ``pending_outcomes`` INSERT runs
|
|
610
628
|
on a background worker.
|
|
611
629
|
|
|
612
|
-
``fast=True`` is the latency-bounded path: it
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
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.
|
|
616
634
|
|
|
617
635
|
Multi-scope: ``include_global`` / ``include_shared`` control which
|
|
618
636
|
scopes participate in retrieval. ``None`` (the default) means "use the
|
|
@@ -650,6 +668,7 @@ class MemoryEngine:
|
|
|
650
668
|
fast=fast,
|
|
651
669
|
include_global=include_global,
|
|
652
670
|
include_shared=include_shared,
|
|
671
|
+
window=window,
|
|
653
672
|
)
|
|
654
673
|
except Exception as exc:
|
|
655
674
|
from superlocalmemory.infra.local_diagnostics import record_operation
|