superlocalmemory 3.6.22 → 3.7.0
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 +52 -0
- package/README.md +275 -72
- package/bin/slm-npm +43 -89
- package/docs/pi-dev-integration.md +43 -0
- package/ide/configs/antigravity-mcp.json +2 -2
- package/ide/configs/chatgpt-desktop-mcp.json +1 -1
- package/ide/configs/claude-desktop-mcp.json +2 -2
- package/ide/configs/windsurf-mcp.json +2 -2
- package/ide/hooks/context-hook.js +6 -2
- package/ide/hooks/post-recall-hook.js +7 -3
- package/ide/hooks/tool-event-hook.sh +2 -1
- package/package.json +19 -10
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/_GENERATED.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin-src/rules/AGENTS.md +1 -1
- package/pyproject.toml +40 -8
- package/scripts/postinstall-interactive.js +17 -94
- package/scripts/postinstall.js +185 -258
- package/scripts/preuninstall.js +9 -50
- package/src/superlocalmemory/__init__.py +2 -2
- package/src/superlocalmemory/attribution/mathematical_dna.py +1 -1
- package/src/superlocalmemory/attribution/signer.py +34 -19
- package/src/superlocalmemory/attribution/watermark.py +1 -1
- package/src/superlocalmemory/cli/_lazy_init.py +3 -5
- package/src/superlocalmemory/cli/commands.py +490 -195
- package/src/superlocalmemory/cli/context_commands.py +5 -4
- package/src/superlocalmemory/cli/daemon.py +282 -187
- package/src/superlocalmemory/cli/db_migrate.py +3 -1
- package/src/superlocalmemory/cli/diagnostics_cmd.py +28 -0
- package/src/superlocalmemory/cli/evidence_cmd.py +103 -0
- package/src/superlocalmemory/cli/ingest_cmd.py +7 -3
- package/src/superlocalmemory/cli/main.py +128 -31
- package/src/superlocalmemory/cli/pending_store.py +54 -38
- package/src/superlocalmemory/cli/scale_engine_cmd.py +37 -0
- package/src/superlocalmemory/cli/service_installer.py +57 -52
- package/src/superlocalmemory/cli/setup_wizard.py +142 -88
- package/src/superlocalmemory/cli/version_banner.py +2 -1
- package/src/superlocalmemory/code_graph/config.py +3 -1
- package/src/superlocalmemory/core/backend_orchestrator.py +81 -21
- package/src/superlocalmemory/core/config.py +65 -20
- package/src/superlocalmemory/core/consolidation_engine.py +9 -7
- package/src/superlocalmemory/core/context_cache.py +56 -8
- package/src/superlocalmemory/core/derivation_lineage.py +246 -0
- package/src/superlocalmemory/core/embedding_worker.py +32 -20
- package/src/superlocalmemory/core/embeddings.py +54 -18
- package/src/superlocalmemory/core/engine.py +150 -104
- package/src/superlocalmemory/core/engine_ingestion.py +513 -0
- package/src/superlocalmemory/core/engine_wiring.py +2 -0
- package/src/superlocalmemory/core/evidence_bundle.py +526 -0
- package/src/superlocalmemory/core/fact_consolidator.py +5 -11
- package/src/superlocalmemory/core/graph_analyzer.py +2 -2
- package/src/superlocalmemory/core/health_monitor.py +4 -2
- package/src/superlocalmemory/core/ingestion_command.py +636 -0
- package/src/superlocalmemory/core/injection.py +69 -18
- package/src/superlocalmemory/core/lifecycle_state.py +153 -0
- package/src/superlocalmemory/core/maintenance.py +23 -22
- package/src/superlocalmemory/core/maintenance_scheduler.py +51 -35
- package/src/superlocalmemory/core/mutations.py +143 -0
- package/src/superlocalmemory/core/platform_utils.py +7 -4
- package/src/superlocalmemory/core/ram_lock.py +16 -5
- package/src/superlocalmemory/core/rate_limit.py +1 -1
- package/src/superlocalmemory/core/recall_pipeline.py +60 -101
- package/src/superlocalmemory/core/recall_worker.py +76 -59
- package/src/superlocalmemory/core/registry.py +1 -1
- package/src/superlocalmemory/core/scale_engine.py +293 -0
- package/src/superlocalmemory/core/score_contract.py +62 -0
- package/src/superlocalmemory/core/security_primitives.py +3 -1
- package/src/superlocalmemory/core/slm_disabled.py +3 -5
- package/src/superlocalmemory/core/store_pipeline.py +172 -40
- package/src/superlocalmemory/core/tier_manager.py +32 -20
- package/src/superlocalmemory/core/worker_pool.py +13 -4
- package/src/superlocalmemory/dynamics/activation_guided_quantization.py +1 -1
- package/src/superlocalmemory/dynamics/eap_scheduler.py +10 -3
- package/src/superlocalmemory/dynamics/ebbinghaus_langevin_coupling.py +1 -1
- package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +1 -1
- package/src/superlocalmemory/encoding/auto_linker.py +1 -1
- package/src/superlocalmemory/encoding/cognitive_consolidator.py +7 -16
- package/src/superlocalmemory/encoding/consolidator.py +22 -5
- package/src/superlocalmemory/encoding/fact_extractor.py +1 -1
- package/src/superlocalmemory/encoding/foresight.py +2 -0
- package/src/superlocalmemory/encoding/graph_builder.py +1 -1
- package/src/superlocalmemory/encoding/temporal_parser.py +2 -0
- package/src/superlocalmemory/evaluation/__init__.py +13 -0
- package/src/superlocalmemory/evaluation/calibration.py +308 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +2 -1
- package/src/superlocalmemory/graph/cozo_backend.py +256 -23
- package/src/superlocalmemory/hooks/_outcome_common.py +21 -11
- package/src/superlocalmemory/hooks/antigravity_adapter.py +10 -31
- package/src/superlocalmemory/hooks/auto_invoker.py +25 -27
- package/src/superlocalmemory/hooks/auto_recall.py +31 -6
- package/src/superlocalmemory/hooks/auto_recall_hook.py +13 -33
- package/src/superlocalmemory/hooks/before_web_hook.py +9 -7
- package/src/superlocalmemory/hooks/claude_code_hooks.py +126 -39
- package/src/superlocalmemory/hooks/codex_assets.py +59 -0
- package/src/superlocalmemory/hooks/codex_hooks.py +186 -0
- package/src/superlocalmemory/hooks/context_payload.py +1 -1
- package/src/superlocalmemory/hooks/copilot_adapter.py +9 -24
- package/src/superlocalmemory/hooks/cursor_adapter.py +10 -32
- package/src/superlocalmemory/hooks/hook_daemon.py +4 -2
- package/src/superlocalmemory/hooks/hook_handlers.py +241 -55
- package/src/superlocalmemory/hooks/memory_protocol.py +5 -3
- package/src/superlocalmemory/hooks/post_tool_async_hook.py +4 -2
- package/src/superlocalmemory/hooks/session_registry.py +15 -8
- package/src/superlocalmemory/hooks/stop_outcome_hook.py +10 -6
- package/src/superlocalmemory/hooks/topic_shift_hook.py +42 -12
- package/src/superlocalmemory/hooks/user_prompt_hook.py +9 -14
- package/src/superlocalmemory/hooks/user_prompt_rehash_hook.py +19 -11
- package/src/superlocalmemory/infra/auth_middleware.py +38 -5
- package/src/superlocalmemory/infra/backup.py +7 -5
- package/src/superlocalmemory/infra/cloud_backup.py +18 -8
- package/src/superlocalmemory/infra/daemon_identity.py +248 -0
- package/src/superlocalmemory/infra/data_root.py +199 -0
- package/src/superlocalmemory/infra/event_bus.py +3 -1
- package/src/superlocalmemory/infra/local_diagnostics.py +327 -0
- package/src/superlocalmemory/infra/process_reaper.py +23 -0
- package/src/superlocalmemory/ingestion/adapter_manager.py +27 -9
- package/src/superlocalmemory/ingestion/base_adapter.py +25 -31
- package/src/superlocalmemory/ingestion/calendar_adapter.py +13 -4
- package/src/superlocalmemory/ingestion/credentials.py +14 -7
- package/src/superlocalmemory/ingestion/gmail_adapter.py +13 -4
- package/src/superlocalmemory/ingestion/transcript_adapter.py +7 -2
- package/src/superlocalmemory/learning/consolidation_quantization_worker.py +1 -1
- package/src/superlocalmemory/learning/ensemble.py +11 -0
- package/src/superlocalmemory/learning/entity_compiler.py +1 -1
- package/src/superlocalmemory/learning/feedback.py +1 -1
- package/src/superlocalmemory/learning/forgetting_scheduler.py +12 -7
- package/src/superlocalmemory/learning/quantization_scheduler.py +1 -1
- package/src/superlocalmemory/learning/ranker.py +4 -1
- package/src/superlocalmemory/learning/source_quality.py +1 -1
- package/src/superlocalmemory/learning/trigram_index.py +3 -2
- package/src/superlocalmemory/llm/backbone.py +13 -8
- package/src/superlocalmemory/math/ebbinghaus.py +1 -1
- package/src/superlocalmemory/math/fisher.py +1 -1
- package/src/superlocalmemory/math/fisher_quantized.py +1 -1
- package/src/superlocalmemory/math/hopfield.py +1 -1
- package/src/superlocalmemory/math/langevin.py +1 -1
- package/src/superlocalmemory/math/polar_quant.py +3 -4
- package/src/superlocalmemory/math/qjl.py +1 -1
- package/src/superlocalmemory/math/sheaf.py +1 -1
- package/src/superlocalmemory/math/turbo_quant.py +3 -2
- package/src/superlocalmemory/mcp/_daemon_proxy.py +12 -11
- package/src/superlocalmemory/mcp/_pool_adapter.py +27 -0
- package/src/superlocalmemory/mcp/http_transport.py +53 -0
- package/src/superlocalmemory/mcp/server.py +39 -13
- package/src/superlocalmemory/mcp/shared.py +69 -3
- package/src/superlocalmemory/mcp/tools_active.py +141 -31
- package/src/superlocalmemory/mcp/tools_core.py +128 -29
- package/src/superlocalmemory/mcp/tools_evolution.py +5 -7
- package/src/superlocalmemory/mcp/tools_learning.py +42 -2
- package/src/superlocalmemory/mcp/tools_mesh.py +7 -23
- package/src/superlocalmemory/mcp/tools_optimize.py +8 -1
- package/src/superlocalmemory/mcp/tools_v28.py +23 -2
- package/src/superlocalmemory/mcp/tools_v3.py +26 -1
- package/src/superlocalmemory/mcp/tools_v33.py +56 -17
- package/src/superlocalmemory/mesh/broker.py +2 -0
- package/src/superlocalmemory/mesh/remote_sync.py +50 -12
- package/src/superlocalmemory/optimize/cache/manager.py +77 -1
- package/src/superlocalmemory/optimize/cache/semantic.py +23 -3
- package/src/superlocalmemory/optimize/compress/ccr.py +4 -0
- package/src/superlocalmemory/optimize/compress/router.py +6 -1
- package/src/superlocalmemory/optimize/config/__init__.py +5 -0
- package/src/superlocalmemory/optimize/config/store.py +6 -4
- package/src/superlocalmemory/optimize/proxy/_helpers.py +15 -5
- package/src/superlocalmemory/optimize/proxy/capture.py +3 -2
- package/src/superlocalmemory/optimize/proxy/server.py +2 -2
- package/src/superlocalmemory/optimize/storage/db.py +14 -13
- package/src/superlocalmemory/retrieval/agentic.py +1 -1
- package/src/superlocalmemory/retrieval/ann_index.py +1 -1
- package/src/superlocalmemory/retrieval/bm25_channel.py +35 -11
- package/src/superlocalmemory/retrieval/bridge_discovery.py +73 -8
- package/src/superlocalmemory/retrieval/engine.py +169 -79
- package/src/superlocalmemory/retrieval/entity_channel.py +289 -67
- package/src/superlocalmemory/retrieval/forgetting_filter.py +1 -1
- package/src/superlocalmemory/retrieval/fusion.py +1 -1
- package/src/superlocalmemory/retrieval/hopfield_channel.py +118 -30
- package/src/superlocalmemory/retrieval/profile_channel.py +1 -1
- package/src/superlocalmemory/retrieval/quantization_aware_search.py +16 -10
- package/src/superlocalmemory/retrieval/reranker.py +56 -20
- package/src/superlocalmemory/retrieval/scope_policy.py +85 -0
- package/src/superlocalmemory/retrieval/semantic_channel.py +122 -14
- package/src/superlocalmemory/retrieval/spreading_activation.py +141 -25
- package/src/superlocalmemory/retrieval/strategy.py +1 -1
- package/src/superlocalmemory/retrieval/temporal_channel.py +30 -15
- package/src/superlocalmemory/retrieval/vector_store.py +1 -1
- package/src/superlocalmemory/server/api.py +10 -7
- package/src/superlocalmemory/server/bandit_loops.py +4 -2
- package/src/superlocalmemory/server/recall_serializer.py +24 -0
- package/src/superlocalmemory/server/route_mutations.py +84 -0
- package/src/superlocalmemory/server/routes/agents.py +8 -6
- package/src/superlocalmemory/server/routes/brain.py +14 -12
- package/src/superlocalmemory/server/routes/chat.py +29 -12
- package/src/superlocalmemory/server/routes/data_io.py +55 -24
- package/src/superlocalmemory/server/routes/helpers.py +29 -4
- package/src/superlocalmemory/server/routes/ingest.py +53 -36
- package/src/superlocalmemory/server/routes/memories.py +104 -43
- package/src/superlocalmemory/server/routes/mesh.py +31 -0
- package/src/superlocalmemory/server/routes/profiles.py +26 -4
- package/src/superlocalmemory/server/routes/tiers.py +43 -11
- package/src/superlocalmemory/server/routes/timeline.py +5 -1
- package/src/superlocalmemory/server/routes/v3_api.py +76 -21
- package/src/superlocalmemory/server/security_middleware.py +1 -1
- package/src/superlocalmemory/server/ui.py +6 -3
- package/src/superlocalmemory/server/unified_daemon.py +680 -293
- package/src/superlocalmemory/server/write_identity.py +147 -0
- package/src/superlocalmemory/storage/access_log.py +4 -3
- package/src/superlocalmemory/storage/database.py +118 -25
- package/src/superlocalmemory/storage/migration_runner.py +84 -1
- package/src/superlocalmemory/storage/migration_v33.py +1 -1
- package/src/superlocalmemory/storage/migrations/M002_model_state_history.py +6 -60
- package/src/superlocalmemory/storage/migrations/M018_ingestion_operations.py +120 -0
- package/src/superlocalmemory/storage/migrations/M019_derivation_lineage.py +54 -0
- package/src/superlocalmemory/storage/migrations/M020_model_state_integrity.py +52 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +5 -0
- package/src/superlocalmemory/storage/models.py +16 -0
- package/src/superlocalmemory/storage/quantized_store.py +20 -3
- package/src/superlocalmemory/storage/v2_migrator.py +5 -3
- package/src/superlocalmemory/ui/favicon.svg +5 -0
- package/src/superlocalmemory/ui/index.html +1 -0
- package/src/superlocalmemory/ui/js/compliance.js +1 -1
- package/src/superlocalmemory/ui/js/core.js +49 -8
- package/src/superlocalmemory/ui/js/dashboard.js +23 -2
- package/src/superlocalmemory/ui/js/feedback.js +1 -1
- package/src/superlocalmemory/ui/js/graph-filters.js +1 -1
- package/src/superlocalmemory/ui/js/graph-ui.js +1 -1
- package/src/superlocalmemory/ui/js/lifecycle.js +1 -1
- package/src/superlocalmemory/ui/js/ng-mesh.js +15 -49
- package/src/superlocalmemory/ui/js/settings.js +4 -2
- package/src/superlocalmemory/vector/lancedb_backend.py +57 -9
- package/bin/slm +0 -59
- package/bin/slm.bat +0 -77
- package/bin/slm.cmd +0 -5
- package/ide/integrations/langchain/README.md +0 -106
- package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +0 -9
- package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +0 -201
- package/ide/integrations/langchain/pyproject.toml +0 -38
- package/ide/integrations/langchain/tests/__init__.py +0 -3
- package/ide/integrations/langchain/tests/test_chat_message_history.py +0 -215
- package/ide/integrations/langchain/tests/test_security.py +0 -117
- package/ide/integrations/llamaindex/README.md +0 -81
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +0 -9
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +0 -316
- package/ide/integrations/llamaindex/pyproject.toml +0 -43
- package/ide/integrations/llamaindex/tests/__init__.py +0 -3
- package/ide/integrations/llamaindex/tests/test_chat_store.py +0 -294
- package/ide/integrations/llamaindex/tests/test_security.py +0 -241
- 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 -15
- package/plugin-src/scripts/slm-launch.bat +0 -17
- package/plugin-src/settings.json +0 -16
- package/plugin-src/skills/slm-cache/SKILL.md +0 -140
- package/plugin-src/skills/slm-compress/SKILL.md +0 -143
- package/plugin-src/skills/slm-graph/SKILL.md +0 -300
- package/plugin-src/skills/slm-recall/SKILL.md +0 -204
- package/plugin-src/skills/slm-remember/SKILL.md +0 -194
- package/plugin-src/skills/slm-session/SKILL.md +0 -207
- package/plugin-src/skills/slm-status/SKILL.md +0 -149
- package/scripts/__tests__/build-plugin.test.mjs +0 -613
- package/scripts/_savings_math.py +0 -270
- package/scripts/build-dmg.sh +0 -417
- package/scripts/build-plugin.js +0 -742
- package/scripts/build-slm-hook.ps1 +0 -40
- package/scripts/build-slm-hook.sh +0 -45
- package/scripts/build_entry.py +0 -452
- package/scripts/ci/stage5b_gate.sh +0 -50
- package/scripts/dogfood_savings.py +0 -490
- package/scripts/generate-thumbnails.py +0 -218
- package/scripts/install-skills.ps1 +0 -4
- package/scripts/install-skills.sh +0 -5
- package/scripts/install.ps1 +0 -701
- package/scripts/install.sh +0 -1015
- package/scripts/postinstall_binary.js +0 -287
- package/scripts/prepack.js +0 -33
- package/scripts/release_manifest.py +0 -273
- package/scripts/slm-hook.spec +0 -56
- package/scripts/start-dashboard.ps1 +0 -52
- package/scripts/start-dashboard.sh +0 -41
- package/scripts/sync-wiki.ps1 +0 -127
- package/scripts/sync-wiki.sh +0 -82
- package/scripts/test-dmg.sh +0 -161
- package/scripts/test-npm-package.ps1 +0 -252
- package/scripts/test-npm-package.sh +0 -207
- package/scripts/verify-install.ps1 +0 -294
- package/scripts/verify-install.sh +0 -266
- package/scripts/verify-v27.ps1 +0 -301
- package/scripts/verify-v27.sh +0 -233
- package/src/superlocalmemory.egg-info/PKG-INFO +0 -513
- package/src/superlocalmemory.egg-info/SOURCES.txt +0 -529
- package/src/superlocalmemory.egg-info/dependency_links.txt +0 -1
- package/src/superlocalmemory.egg-info/entry_points.txt +0 -2
- package/src/superlocalmemory.egg-info/requires.txt +0 -71
- package/src/superlocalmemory.egg-info/top_level.txt +0 -1
|
@@ -13,7 +13,7 @@ where all variances are identical, Fisher distance degenerates to a
|
|
|
13
13
|
monotonic transform of Euclidean distance — same ranking as cosine.
|
|
14
14
|
|
|
15
15
|
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
16
|
-
License:
|
|
16
|
+
License: AGPL-3.0-or-later
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
19
|
from __future__ import annotations
|
|
@@ -34,6 +34,20 @@ logger = logging.getLogger(__name__)
|
|
|
34
34
|
_VARIANCE_FLOOR: float = 1e-6
|
|
35
35
|
|
|
36
36
|
|
|
37
|
+
class _LanceCandidateSource:
|
|
38
|
+
"""Adapt the promoted Lance projection to the existing candidate contract."""
|
|
39
|
+
|
|
40
|
+
available = True
|
|
41
|
+
|
|
42
|
+
def __init__(self, backend: Any) -> None:
|
|
43
|
+
self._backend = backend
|
|
44
|
+
|
|
45
|
+
def search(self, query_embedding: list[float], *, top_k: int, profile_id: str) -> list[tuple[str, float]]:
|
|
46
|
+
return self._backend.similarity_search(
|
|
47
|
+
query_embedding, top_k=top_k, profile_id=profile_id,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
37
51
|
def _cosine_similarity(a: np.ndarray, b: np.ndarray) -> float:
|
|
38
52
|
"""Cosine similarity in [-1, 1]. Returns 0.0 on zero vectors."""
|
|
39
53
|
norm_a = np.linalg.norm(a)
|
|
@@ -95,6 +109,9 @@ class SemanticChannel:
|
|
|
95
109
|
# V3.3.26: Lazily instantiated FRQAD metric for mixed-precision scoring
|
|
96
110
|
self._frqad_metric: object | None = None
|
|
97
111
|
self._vector_store = vector_store
|
|
112
|
+
self._scale_vector_backend: Any | None = None
|
|
113
|
+
self._scale_shadow_checks = 0
|
|
114
|
+
self._scale_shadow_mismatches = 0
|
|
98
115
|
# V3.3.19: TurboQuant 3-tier search (stateless, optional)
|
|
99
116
|
self._qas = quantization_aware_search
|
|
100
117
|
|
|
@@ -123,6 +140,23 @@ class SemanticChannel:
|
|
|
123
140
|
|
|
124
141
|
q_vec = np.array(query_embedding, dtype=np.float32)
|
|
125
142
|
|
|
143
|
+
# Lance is a derived projection. It is never an authorization source
|
|
144
|
+
# and it never silently replaces the canonical sqlite-vec path: every
|
|
145
|
+
# promoted query is shadowed and falls back if membership/order differs.
|
|
146
|
+
if (
|
|
147
|
+
self._scale_vector_backend is not None
|
|
148
|
+
and not bool(getattr(self, "include_global", False))
|
|
149
|
+
and not bool(getattr(self, "include_shared", False))
|
|
150
|
+
):
|
|
151
|
+
projected = self._search_via_lance(query_embedding, q_vec, profile_id, top_k)
|
|
152
|
+
canonical = self._search_without_lance(query_embedding, q_vec, profile_id, top_k)
|
|
153
|
+
self._scale_shadow_checks += 1
|
|
154
|
+
if [fid for fid, _ in projected] == [fid for fid, _ in canonical]:
|
|
155
|
+
return projected
|
|
156
|
+
self._scale_shadow_mismatches += 1
|
|
157
|
+
logger.warning("Lance semantic projection diverged from SQLite; using SQLite")
|
|
158
|
+
return canonical
|
|
159
|
+
|
|
126
160
|
# --- FAST PATH: sqlite-vec KNN ---
|
|
127
161
|
if self._vector_store and self._vector_store.available:
|
|
128
162
|
results = self._search_via_vector_store(
|
|
@@ -135,6 +169,44 @@ class SemanticChannel:
|
|
|
135
169
|
# --- FALLBACK: full-table scan (original code, unchanged) ---
|
|
136
170
|
return self._search_full_scan(query_embedding, q_vec, profile_id, top_k)
|
|
137
171
|
|
|
172
|
+
def set_scale_vector_backend(self, backend: Any | None) -> None:
|
|
173
|
+
"""Attach a parity-verified Lance projection without replacing SQLite."""
|
|
174
|
+
self._scale_vector_backend = backend
|
|
175
|
+
|
|
176
|
+
def scale_projection_telemetry(self) -> dict[str, int]:
|
|
177
|
+
return {
|
|
178
|
+
"shadow_checks": self._scale_shadow_checks,
|
|
179
|
+
"shadow_mismatches": self._scale_shadow_mismatches,
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
def _search_via_lance(
|
|
183
|
+
self, query_embedding: list[float], q_vec: np.ndarray, profile_id: str, top_k: int,
|
|
184
|
+
) -> list[tuple[str, float]]:
|
|
185
|
+
original_store, original_qas = self._vector_store, self._qas
|
|
186
|
+
try:
|
|
187
|
+
self._vector_store = _LanceCandidateSource(self._scale_vector_backend)
|
|
188
|
+
# QAS indexes SQLite/quantized records and cannot represent Lance.
|
|
189
|
+
self._qas = None
|
|
190
|
+
return self._search_via_vector_store(query_embedding, q_vec, profile_id, top_k)
|
|
191
|
+
except Exception as exc:
|
|
192
|
+
logger.warning("Lance semantic projection failed closed to SQLite: %s", exc)
|
|
193
|
+
return []
|
|
194
|
+
finally:
|
|
195
|
+
self._vector_store, self._qas = original_store, original_qas
|
|
196
|
+
|
|
197
|
+
def _search_without_lance(
|
|
198
|
+
self, query_embedding: list[float], q_vec: np.ndarray, profile_id: str, top_k: int,
|
|
199
|
+
) -> list[tuple[str, float]]:
|
|
200
|
+
backend, self._scale_vector_backend = self._scale_vector_backend, None
|
|
201
|
+
try:
|
|
202
|
+
if self._vector_store and self._vector_store.available:
|
|
203
|
+
results = self._search_via_vector_store(query_embedding, q_vec, profile_id, top_k)
|
|
204
|
+
if results:
|
|
205
|
+
return results
|
|
206
|
+
return self._search_full_scan(query_embedding, q_vec, profile_id, top_k)
|
|
207
|
+
finally:
|
|
208
|
+
self._scale_vector_backend = backend
|
|
209
|
+
|
|
138
210
|
def _search_via_vector_store(
|
|
139
211
|
self,
|
|
140
212
|
query_embedding: list[float],
|
|
@@ -162,6 +234,36 @@ class SemanticChannel:
|
|
|
162
234
|
knn_results = self._vector_store.search(
|
|
163
235
|
query_embedding, top_k=top_k * 2, profile_id=profile_id,
|
|
164
236
|
)
|
|
237
|
+
|
|
238
|
+
# The vector index is partitioned by owner profile. An opted-in global
|
|
239
|
+
# or authorized shared fact owned by another profile cannot enter the
|
|
240
|
+
# local KNN candidate set, so merge the bounded cross-profile visible
|
|
241
|
+
# supplement using the same canonical DB scope predicate as fallback.
|
|
242
|
+
include_global = bool(getattr(self, "include_global", False))
|
|
243
|
+
include_shared = bool(getattr(self, "include_shared", False))
|
|
244
|
+
external_facts = self._db.get_external_visible_facts(
|
|
245
|
+
profile_id,
|
|
246
|
+
include_global=include_global,
|
|
247
|
+
include_shared=include_shared,
|
|
248
|
+
)
|
|
249
|
+
external_scores: list[tuple[str, float]] = []
|
|
250
|
+
for fact in external_facts:
|
|
251
|
+
if fact.embedding is None:
|
|
252
|
+
continue
|
|
253
|
+
fact_vec = np.array(fact.embedding, dtype=np.float32)
|
|
254
|
+
if fact_vec.shape != q_vec.shape:
|
|
255
|
+
continue
|
|
256
|
+
score = (_cosine_similarity(q_vec, fact_vec) + 1.0) / 2.0
|
|
257
|
+
if score > 0.05:
|
|
258
|
+
external_scores.append((fact.fact_id, score))
|
|
259
|
+
|
|
260
|
+
if external_scores:
|
|
261
|
+
combined = {fid: score for fid, score in knn_results}
|
|
262
|
+
for fact_id, score in external_scores:
|
|
263
|
+
combined[fact_id] = max(combined.get(fact_id, 0.0), score)
|
|
264
|
+
knn_results = sorted(
|
|
265
|
+
combined.items(), key=lambda item: item[1], reverse=True,
|
|
266
|
+
)[:top_k * 2]
|
|
165
267
|
if not knn_results:
|
|
166
268
|
return [] # Caller falls through to full scan
|
|
167
269
|
|
|
@@ -175,7 +277,12 @@ class SemanticChannel:
|
|
|
175
277
|
)
|
|
176
278
|
|
|
177
279
|
if not facts:
|
|
178
|
-
|
|
280
|
+
# The vector/QAS indexes are candidate sources, never an
|
|
281
|
+
# authorization source. Returning their raw IDs here leaked an
|
|
282
|
+
# owner-private fact precisely when canonical scope filtering
|
|
283
|
+
# rejected the entire candidate set. Empty means "no authorized
|
|
284
|
+
# fast-path hits" so the caller may use the scoped full scan.
|
|
285
|
+
return []
|
|
179
286
|
|
|
180
287
|
# Step 3: Fisher-Rao re-scoring on the subset
|
|
181
288
|
q_mean: np.ndarray | None = None
|
|
@@ -189,12 +296,7 @@ class SemanticChannel:
|
|
|
189
296
|
for fact in facts:
|
|
190
297
|
cos_sim = knn_scores.get(fact.fact_id, 0.0)
|
|
191
298
|
|
|
192
|
-
|
|
193
|
-
# Bug fix: access_count=0 for fresh facts → Fisher weight=0 → metric DEAD.
|
|
194
|
-
# Paper 2's +12pp on multi-hop came from Fisher-Rao. A 0.3 floor ensures
|
|
195
|
-
# fresh facts still benefit from variance-weighted similarity, while
|
|
196
|
-
# frequently accessed facts get progressively stronger Fisher influence.
|
|
197
|
-
fisher_weight = max(0.15, min(1.2, (fact.access_count or 0) / 10.0 * 1.2))
|
|
299
|
+
fisher_weight = self._fisher_weight(fact.access_count)
|
|
198
300
|
|
|
199
301
|
if (fisher_weight > 0.01
|
|
200
302
|
and fact.fisher_variance is not None
|
|
@@ -205,8 +307,7 @@ class SemanticChannel:
|
|
|
205
307
|
f_sim = self._compute_fisher_sim(
|
|
206
308
|
q_vec, f_vec, var_vec, fact, q_mean, q_var,
|
|
207
309
|
)
|
|
208
|
-
|
|
209
|
-
sim = capped_w * f_sim + (1.0 - capped_w) * cos_sim
|
|
310
|
+
sim = fisher_weight * f_sim + (1.0 - fisher_weight) * cos_sim
|
|
210
311
|
else:
|
|
211
312
|
sim = cos_sim
|
|
212
313
|
|
|
@@ -252,8 +353,8 @@ class SemanticChannel:
|
|
|
252
353
|
# Cosine baseline (always computed)
|
|
253
354
|
cos_sim = (_cosine_similarity(q_vec, f_vec) + 1.0) / 2.0
|
|
254
355
|
|
|
255
|
-
#
|
|
256
|
-
fisher_weight =
|
|
356
|
+
# The weighting contract is identical to the sqlite-vec path.
|
|
357
|
+
fisher_weight = self._fisher_weight(fact.access_count)
|
|
257
358
|
|
|
258
359
|
if (fisher_weight > 0.01
|
|
259
360
|
and fact.fisher_variance is not None
|
|
@@ -262,8 +363,7 @@ class SemanticChannel:
|
|
|
262
363
|
f_sim = self._compute_fisher_sim(
|
|
263
364
|
q_vec, f_vec, var_vec, fact, q_mean, q_var,
|
|
264
365
|
)
|
|
265
|
-
|
|
266
|
-
sim = capped_w * f_sim + (1.0 - capped_w) * cos_sim
|
|
366
|
+
sim = fisher_weight * f_sim + (1.0 - fisher_weight) * cos_sim
|
|
267
367
|
else:
|
|
268
368
|
sim = cos_sim
|
|
269
369
|
|
|
@@ -273,6 +373,14 @@ class SemanticChannel:
|
|
|
273
373
|
scored.sort(key=lambda x: x[1], reverse=True)
|
|
274
374
|
return scored[:top_k]
|
|
275
375
|
|
|
376
|
+
@staticmethod
|
|
377
|
+
def _fisher_weight(access_count: int | None) -> float:
|
|
378
|
+
"""Canonical Fisher blend shared by every semantic candidate path."""
|
|
379
|
+
graduated = (access_count or 0) / 10.0 * 1.2
|
|
380
|
+
# A small floor keeps Fisher variance active for new facts. The cap
|
|
381
|
+
# prevents the blend from extrapolating beyond its two score inputs.
|
|
382
|
+
return min(1.0, max(0.15, graduated))
|
|
383
|
+
|
|
276
384
|
# ------------------------------------------------------------------
|
|
277
385
|
# Fisher similarity dispatch
|
|
278
386
|
# ------------------------------------------------------------------
|
|
@@ -12,7 +12,7 @@ Reads BOTH graph_edges + association_edges via UNION query (Rule 13).
|
|
|
12
12
|
Registered as 5th channel via ChannelRegistry (needs_embedding=True).
|
|
13
13
|
|
|
14
14
|
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
15
|
-
License:
|
|
15
|
+
License: AGPL-3.0-or-later
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
@@ -25,6 +25,11 @@ from typing import Any
|
|
|
25
25
|
|
|
26
26
|
import numpy as np
|
|
27
27
|
|
|
28
|
+
from superlocalmemory.retrieval.scope_policy import (
|
|
29
|
+
authorized_fact_ids,
|
|
30
|
+
filter_authorized_results,
|
|
31
|
+
)
|
|
32
|
+
from superlocalmemory.storage.database import _scope_where
|
|
28
33
|
from superlocalmemory.storage.models import _new_id
|
|
29
34
|
|
|
30
35
|
logger = logging.getLogger(__name__)
|
|
@@ -113,22 +118,77 @@ class SpreadingActivation:
|
|
|
113
118
|
if not self._config.enabled:
|
|
114
119
|
return []
|
|
115
120
|
|
|
121
|
+
include_global = bool(getattr(self, "include_global", False))
|
|
122
|
+
include_shared = bool(getattr(self, "include_shared", False))
|
|
116
123
|
try:
|
|
117
124
|
# Step 0: Get seed nodes from VectorStore KNN
|
|
118
125
|
seed_results = self._vector_store.search(
|
|
119
126
|
query, top_k=self._config.top_m, profile_id=profile_id,
|
|
120
127
|
)
|
|
128
|
+
# Owner-partitioned vector indexes cannot discover opted-in peers.
|
|
129
|
+
# Add visible external embeddings with the same cosine seed signal.
|
|
130
|
+
try:
|
|
131
|
+
external_facts = self._db.get_external_visible_facts(
|
|
132
|
+
profile_id,
|
|
133
|
+
include_global=include_global,
|
|
134
|
+
include_shared=include_shared,
|
|
135
|
+
)
|
|
136
|
+
except Exception:
|
|
137
|
+
external_facts = []
|
|
138
|
+
q_vec = np.array(query, dtype=np.float32)
|
|
139
|
+
q_norm = float(np.linalg.norm(q_vec))
|
|
140
|
+
combined = {fact_id: score for fact_id, score in seed_results}
|
|
141
|
+
for fact in external_facts:
|
|
142
|
+
embedding = getattr(fact, "embedding", None)
|
|
143
|
+
if embedding is None:
|
|
144
|
+
continue
|
|
145
|
+
fact_vec = np.array(embedding, dtype=np.float32)
|
|
146
|
+
if fact_vec.shape != q_vec.shape:
|
|
147
|
+
continue
|
|
148
|
+
denominator = q_norm * float(np.linalg.norm(fact_vec))
|
|
149
|
+
if denominator <= 1e-8:
|
|
150
|
+
continue
|
|
151
|
+
score = (float(np.dot(q_vec, fact_vec) / denominator) + 1.0) / 2.0
|
|
152
|
+
combined[fact.fact_id] = max(combined.get(fact.fact_id, 0.0), score)
|
|
153
|
+
allowed_seeds = authorized_fact_ids(
|
|
154
|
+
self._db,
|
|
155
|
+
combined,
|
|
156
|
+
profile_id,
|
|
157
|
+
include_global=include_global,
|
|
158
|
+
include_shared=include_shared,
|
|
159
|
+
)
|
|
160
|
+
seed_results = [
|
|
161
|
+
(fact_id, score)
|
|
162
|
+
for fact_id, score in combined.items()
|
|
163
|
+
if fact_id in allowed_seeds
|
|
164
|
+
]
|
|
121
165
|
if not seed_results:
|
|
122
166
|
return []
|
|
123
167
|
|
|
124
168
|
# Check cache first
|
|
125
|
-
query_hash = self._compute_query_hash(
|
|
169
|
+
query_hash = self._compute_query_hash(
|
|
170
|
+
query,
|
|
171
|
+
profile_id,
|
|
172
|
+
include_global=include_global,
|
|
173
|
+
include_shared=include_shared,
|
|
174
|
+
)
|
|
126
175
|
cached = self._get_cached_results(query_hash, profile_id)
|
|
127
176
|
if cached:
|
|
128
|
-
return
|
|
177
|
+
return filter_authorized_results(
|
|
178
|
+
self._db,
|
|
179
|
+
cached,
|
|
180
|
+
profile_id,
|
|
181
|
+
include_global=include_global,
|
|
182
|
+
include_shared=include_shared,
|
|
183
|
+
)[:top_k]
|
|
129
184
|
|
|
130
185
|
# Run 5-step spreading activation
|
|
131
|
-
activations = self._propagate(
|
|
186
|
+
activations = self._propagate(
|
|
187
|
+
seed_results,
|
|
188
|
+
profile_id,
|
|
189
|
+
include_global=include_global,
|
|
190
|
+
include_shared=include_shared,
|
|
191
|
+
)
|
|
132
192
|
|
|
133
193
|
# FOK gating
|
|
134
194
|
if not self._fok_check(activations):
|
|
@@ -141,7 +201,13 @@ class SpreadingActivation:
|
|
|
141
201
|
results = sorted(
|
|
142
202
|
activations.items(), key=lambda x: x[1], reverse=True,
|
|
143
203
|
)
|
|
144
|
-
return
|
|
204
|
+
return filter_authorized_results(
|
|
205
|
+
self._db,
|
|
206
|
+
results,
|
|
207
|
+
profile_id,
|
|
208
|
+
include_global=include_global,
|
|
209
|
+
include_shared=include_shared,
|
|
210
|
+
)[:top_k]
|
|
145
211
|
|
|
146
212
|
except Exception as exc:
|
|
147
213
|
logger.warning(
|
|
@@ -154,6 +220,9 @@ class SpreadingActivation:
|
|
|
154
220
|
self,
|
|
155
221
|
seeds: list[tuple[str, float]],
|
|
156
222
|
profile_id: str,
|
|
223
|
+
*,
|
|
224
|
+
include_global: bool = False,
|
|
225
|
+
include_shared: bool = False,
|
|
157
226
|
) -> dict[str, float]:
|
|
158
227
|
"""Execute the 5-step SYNAPSE algorithm.
|
|
159
228
|
|
|
@@ -186,7 +255,22 @@ class SpreadingActivation:
|
|
|
186
255
|
|
|
187
256
|
# Get neighbors from BOTH tables (Rule 13) — cached per node
|
|
188
257
|
if node_id not in neighbor_cache:
|
|
189
|
-
|
|
258
|
+
raw_neighbors = self._get_unified_neighbors(
|
|
259
|
+
node_id,
|
|
260
|
+
profile_id,
|
|
261
|
+
include_global=include_global,
|
|
262
|
+
include_shared=include_shared,
|
|
263
|
+
)
|
|
264
|
+
allowed_neighbors = authorized_fact_ids(
|
|
265
|
+
self._db,
|
|
266
|
+
(neighbor_id for neighbor_id, _weight in raw_neighbors),
|
|
267
|
+
profile_id,
|
|
268
|
+
include_global=include_global,
|
|
269
|
+
include_shared=include_shared,
|
|
270
|
+
)
|
|
271
|
+
neighbor_cache[node_id] = [
|
|
272
|
+
item for item in raw_neighbors if item[0] in allowed_neighbors
|
|
273
|
+
]
|
|
190
274
|
neighbors = neighbor_cache[node_id]
|
|
191
275
|
|
|
192
276
|
# Out-degree for fan effect normalization
|
|
@@ -226,7 +310,12 @@ class SpreadingActivation:
|
|
|
226
310
|
return activations
|
|
227
311
|
|
|
228
312
|
def _get_unified_neighbors(
|
|
229
|
-
self,
|
|
313
|
+
self,
|
|
314
|
+
node_id: str,
|
|
315
|
+
profile_id: str,
|
|
316
|
+
*,
|
|
317
|
+
include_global: bool = False,
|
|
318
|
+
include_shared: bool = False,
|
|
230
319
|
) -> list[tuple[str, float]]:
|
|
231
320
|
"""Get neighbors from BOTH graph_edges and association_edges.
|
|
232
321
|
|
|
@@ -245,41 +334,56 @@ class SpreadingActivation:
|
|
|
245
334
|
# all 2.1M edges then sorting. Each branch wrapped in SELECT * FROM (...)
|
|
246
335
|
# because SQLite requires parentheses for ORDER BY+LIMIT in compound SELECTs.
|
|
247
336
|
lim = self._config.max_neighbors_per_node
|
|
337
|
+
graph_where, graph_params = _scope_where(
|
|
338
|
+
profile_id,
|
|
339
|
+
include_global=include_global,
|
|
340
|
+
include_shared=include_shared,
|
|
341
|
+
prefix="ge",
|
|
342
|
+
)
|
|
343
|
+
# association_edges has no scope/shared_with columns in the current
|
|
344
|
+
# schema, so it remains owner-profile-only. Endpoint authorization
|
|
345
|
+
# below still prevents a private candidate from entering results.
|
|
346
|
+
assoc_where = "ae.profile_id = ?"
|
|
347
|
+
assoc_params = [profile_id]
|
|
248
348
|
rows = self._db.execute(
|
|
249
|
-
"""
|
|
349
|
+
f"""
|
|
250
350
|
SELECT neighbor_id, weight FROM (
|
|
251
351
|
SELECT * FROM (
|
|
252
|
-
SELECT target_id AS neighbor_id, weight FROM graph_edges
|
|
253
|
-
WHERE source_id = ? AND
|
|
352
|
+
SELECT target_id AS neighbor_id, weight FROM graph_edges AS ge
|
|
353
|
+
WHERE source_id = ? AND {graph_where}
|
|
254
354
|
ORDER BY weight DESC LIMIT ?
|
|
255
355
|
)
|
|
256
356
|
UNION ALL
|
|
257
357
|
SELECT * FROM (
|
|
258
|
-
SELECT target_fact_id AS neighbor_id, weight
|
|
259
|
-
|
|
358
|
+
SELECT target_fact_id AS neighbor_id, weight
|
|
359
|
+
FROM association_edges AS ae
|
|
360
|
+
WHERE source_fact_id = ? AND {assoc_where}
|
|
260
361
|
ORDER BY weight DESC LIMIT ?
|
|
261
362
|
)
|
|
262
363
|
UNION ALL
|
|
263
364
|
SELECT * FROM (
|
|
264
|
-
SELECT source_id AS neighbor_id, weight FROM graph_edges
|
|
265
|
-
WHERE target_id = ? AND
|
|
365
|
+
SELECT source_id AS neighbor_id, weight FROM graph_edges AS ge
|
|
366
|
+
WHERE target_id = ? AND {graph_where}
|
|
266
367
|
ORDER BY weight DESC LIMIT ?
|
|
267
368
|
)
|
|
268
369
|
UNION ALL
|
|
269
370
|
SELECT * FROM (
|
|
270
|
-
SELECT source_fact_id AS neighbor_id, weight
|
|
271
|
-
|
|
371
|
+
SELECT source_fact_id AS neighbor_id, weight
|
|
372
|
+
FROM association_edges AS ae
|
|
373
|
+
WHERE target_fact_id = ? AND {assoc_where}
|
|
272
374
|
ORDER BY weight DESC LIMIT ?
|
|
273
375
|
)
|
|
274
376
|
)
|
|
275
377
|
ORDER BY weight DESC
|
|
276
378
|
LIMIT ?
|
|
277
379
|
""",
|
|
278
|
-
(
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
380
|
+
(
|
|
381
|
+
node_id, *graph_params, lim,
|
|
382
|
+
node_id, *assoc_params, lim,
|
|
383
|
+
node_id, *graph_params, lim,
|
|
384
|
+
node_id, *assoc_params, lim,
|
|
385
|
+
lim,
|
|
386
|
+
),
|
|
283
387
|
)
|
|
284
388
|
return [
|
|
285
389
|
(dict(r)["neighbor_id"], dict(r)["weight"]) for r in rows
|
|
@@ -301,14 +405,26 @@ class SpreadingActivation:
|
|
|
301
405
|
return False
|
|
302
406
|
return max(activations.values()) >= self._config.tau_gate
|
|
303
407
|
|
|
304
|
-
def _compute_query_hash(
|
|
408
|
+
def _compute_query_hash(
|
|
409
|
+
self,
|
|
410
|
+
query: Any,
|
|
411
|
+
profile_id: str,
|
|
412
|
+
*,
|
|
413
|
+
include_global: bool = False,
|
|
414
|
+
include_shared: bool = False,
|
|
415
|
+
) -> str:
|
|
305
416
|
"""Deterministic hash for cache key."""
|
|
417
|
+
scope_bytes = f"|g={int(include_global)}|s={int(include_shared)}".encode()
|
|
306
418
|
if isinstance(query, np.ndarray):
|
|
307
|
-
data = query.tobytes() + profile_id.encode()
|
|
419
|
+
data = query.tobytes() + profile_id.encode() + scope_bytes
|
|
308
420
|
elif isinstance(query, list):
|
|
309
|
-
data =
|
|
421
|
+
data = (
|
|
422
|
+
np.array(query, dtype=np.float32).tobytes()
|
|
423
|
+
+ profile_id.encode()
|
|
424
|
+
+ scope_bytes
|
|
425
|
+
)
|
|
310
426
|
else:
|
|
311
|
-
data = str(query).encode() + profile_id.encode()
|
|
427
|
+
data = str(query).encode() + profile_id.encode() + scope_bytes
|
|
312
428
|
return hashlib.sha256(data).hexdigest()[:16]
|
|
313
429
|
|
|
314
430
|
def _get_cached_results(
|
|
@@ -8,7 +8,7 @@ Classifies query type and returns per-type channel weights.
|
|
|
8
8
|
V1 had this code (strategy_learner.py) but never wired it in.
|
|
9
9
|
|
|
10
10
|
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
11
|
-
License:
|
|
11
|
+
License: AGPL-3.0-or-later
|
|
12
12
|
"""
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
@@ -8,7 +8,7 @@ Searches by referenced_date (NOT just created_at like V1).
|
|
|
8
8
|
Returns empty when query has no temporal signal (no recency noise).
|
|
9
9
|
|
|
10
10
|
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
11
|
-
License:
|
|
11
|
+
License: AGPL-3.0-or-later
|
|
12
12
|
"""
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
@@ -17,9 +17,10 @@ import math
|
|
|
17
17
|
from datetime import datetime
|
|
18
18
|
from typing import TYPE_CHECKING
|
|
19
19
|
|
|
20
|
-
from dateutil.parser import parse as dateutil_parse
|
|
20
|
+
from dateutil.parser import ParserError, parse as dateutil_parse
|
|
21
21
|
|
|
22
22
|
from superlocalmemory.encoding.temporal_parser import TemporalParser
|
|
23
|
+
from superlocalmemory.storage.database import _scope_where
|
|
23
24
|
|
|
24
25
|
if TYPE_CHECKING:
|
|
25
26
|
from superlocalmemory.storage.database import DatabaseManager
|
|
@@ -148,18 +149,24 @@ class TemporalChannel:
|
|
|
148
149
|
|
|
149
150
|
results: list[tuple[str, float]] = []
|
|
150
151
|
seen: set[str] = set()
|
|
152
|
+
where, params = _scope_where(
|
|
153
|
+
profile_id,
|
|
154
|
+
include_global=bool(getattr(self, "include_global", False)),
|
|
155
|
+
include_shared=bool(getattr(self, "include_shared", False)),
|
|
156
|
+
prefix="af",
|
|
157
|
+
)
|
|
151
158
|
|
|
152
159
|
for name in names[:3]: # Limit to first 3 entity mentions
|
|
153
|
-
#
|
|
154
|
-
entity
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
# Find all temporal events for this entity
|
|
160
|
+
# Resolve the entity and event in one scope-filtered query. Looking
|
|
161
|
+
# up the entity only in the requester's profile made global events
|
|
162
|
+
# owned by another profile undiscoverable before authorization was
|
|
163
|
+
# even evaluated.
|
|
159
164
|
rows = self._db.execute(
|
|
160
|
-
"SELECT fact_id FROM temporal_events "
|
|
161
|
-
"
|
|
162
|
-
|
|
165
|
+
"SELECT te.fact_id FROM temporal_events AS te "
|
|
166
|
+
"JOIN canonical_entities AS ce ON ce.entity_id = te.entity_id "
|
|
167
|
+
"JOIN atomic_facts AS af ON af.fact_id = te.fact_id "
|
|
168
|
+
f"WHERE {where} AND LOWER(ce.canonical_name) = LOWER(?)",
|
|
169
|
+
(*params, name),
|
|
163
170
|
)
|
|
164
171
|
for row in rows:
|
|
165
172
|
fid = dict(row)["fact_id"]
|
|
@@ -173,11 +180,19 @@ class TemporalChannel:
|
|
|
173
180
|
return results
|
|
174
181
|
|
|
175
182
|
def _load_events(self, profile_id: str) -> list[dict]:
|
|
183
|
+
where, params = _scope_where(
|
|
184
|
+
profile_id,
|
|
185
|
+
include_global=bool(getattr(self, "include_global", False)),
|
|
186
|
+
include_shared=bool(getattr(self, "include_shared", False)),
|
|
187
|
+
prefix="af",
|
|
188
|
+
)
|
|
176
189
|
rows = self._db.execute(
|
|
177
|
-
"SELECT fact_id, observation_date, referenced_date, "
|
|
178
|
-
"interval_start, interval_end "
|
|
179
|
-
"FROM temporal_events
|
|
180
|
-
|
|
190
|
+
"SELECT te.fact_id, te.observation_date, te.referenced_date, "
|
|
191
|
+
"te.interval_start, te.interval_end "
|
|
192
|
+
"FROM temporal_events AS te "
|
|
193
|
+
"JOIN atomic_facts AS af ON af.fact_id = te.fact_id "
|
|
194
|
+
f"WHERE {where}",
|
|
195
|
+
(*params,),
|
|
181
196
|
)
|
|
182
197
|
return [dict(r) for r in rows]
|
|
183
198
|
|
|
@@ -9,7 +9,7 @@ Falls back to ANNIndex if sqlite-vec is unavailable (Rule 03).
|
|
|
9
9
|
Implements ANNSearchable protocol for GraphBuilder compatibility (Rule 07).
|
|
10
10
|
|
|
11
11
|
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
12
|
-
License:
|
|
12
|
+
License: AGPL-3.0-or-later
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
@@ -24,12 +24,13 @@ import uvicorn
|
|
|
24
24
|
|
|
25
25
|
from superlocalmemory.server.security_middleware import SecurityHeadersMiddleware
|
|
26
26
|
from superlocalmemory.server.routes.helpers import SLM_VERSION
|
|
27
|
+
from superlocalmemory.infra.data_root import DynamicStatePath
|
|
27
28
|
|
|
28
29
|
logger = logging.getLogger("superlocalmemory.api_server")
|
|
29
30
|
|
|
30
31
|
# V3 paths
|
|
31
|
-
MEMORY_DIR =
|
|
32
|
-
DB_PATH =
|
|
32
|
+
MEMORY_DIR = DynamicStatePath()
|
|
33
|
+
DB_PATH = DynamicStatePath("memory.db")
|
|
33
34
|
# V3.3.21: UI shipped inside the package for pip/npm installs.
|
|
34
35
|
_PKG_UI = Path(__file__).resolve().parent.parent / "ui"
|
|
35
36
|
_REPO_UI = Path(__file__).resolve().parent.parent.parent.parent / "ui"
|
|
@@ -75,6 +76,12 @@ async def lifespan(application: FastAPI):
|
|
|
75
76
|
application.state.engine = None
|
|
76
77
|
application.state.config = None
|
|
77
78
|
|
|
79
|
+
# Event fan-out belongs to the same application lifecycle as the engine.
|
|
80
|
+
# Registering it through FastAPI.on_event created a second, deprecated
|
|
81
|
+
# startup path and made TestClient initialization emit warnings.
|
|
82
|
+
from superlocalmemory.server.routes.events import register_event_listener
|
|
83
|
+
|
|
84
|
+
register_event_listener()
|
|
78
85
|
yield
|
|
79
86
|
|
|
80
87
|
# Cleanup
|
|
@@ -169,7 +176,7 @@ def create_app() -> FastAPI:
|
|
|
169
176
|
from superlocalmemory.server.routes.profiles import router as profiles_router
|
|
170
177
|
from superlocalmemory.server.routes.backup import router as backup_router
|
|
171
178
|
from superlocalmemory.server.routes.data_io import router as data_io_router
|
|
172
|
-
from superlocalmemory.server.routes.events import router as events_router
|
|
179
|
+
from superlocalmemory.server.routes.events import router as events_router
|
|
173
180
|
from superlocalmemory.server.routes.agents import router as agents_router
|
|
174
181
|
from superlocalmemory.server.routes.ws import router as ws_router, manager as ws_manager
|
|
175
182
|
from superlocalmemory.server.routes.v3_api import router as v3_router
|
|
@@ -237,10 +244,6 @@ def create_app() -> FastAPI:
|
|
|
237
244
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
238
245
|
}
|
|
239
246
|
|
|
240
|
-
@application.on_event("startup")
|
|
241
|
-
async def startup_event():
|
|
242
|
-
register_event_listener()
|
|
243
|
-
|
|
244
247
|
return application
|
|
245
248
|
|
|
246
249
|
|
|
@@ -24,6 +24,8 @@ import os
|
|
|
24
24
|
from pathlib import Path
|
|
25
25
|
from typing import Any
|
|
26
26
|
|
|
27
|
+
from superlocalmemory.infra.data_root import state_path
|
|
28
|
+
|
|
27
29
|
logger = logging.getLogger(__name__)
|
|
28
30
|
|
|
29
31
|
_REWARD_INTERVAL = float(
|
|
@@ -42,7 +44,7 @@ def _learning_db(config: Any) -> Path:
|
|
|
42
44
|
cand = getattr(config, "learning_db_path", None)
|
|
43
45
|
if cand is not None:
|
|
44
46
|
return Path(cand)
|
|
45
|
-
return
|
|
47
|
+
return state_path("learning.db")
|
|
46
48
|
|
|
47
49
|
|
|
48
50
|
def _memory_db(config: Any) -> Path:
|
|
@@ -50,7 +52,7 @@ def _memory_db(config: Any) -> Path:
|
|
|
50
52
|
cand = getattr(config, "db_path", None)
|
|
51
53
|
if cand is not None:
|
|
52
54
|
return Path(cand)
|
|
53
|
-
return
|
|
55
|
+
return state_path("memory.db")
|
|
54
56
|
|
|
55
57
|
|
|
56
58
|
def _profile_id(config: Any) -> str:
|