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
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under AGPL-3.0-or-later - see LICENSE file
|
|
3
|
+
|
|
4
|
+
"""Capture derivation lineage without inventing source spans."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import hashlib
|
|
9
|
+
import json
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _lineage_id(profile_id: str, object_type: str, object_id: str, operation_id: str) -> str:
|
|
14
|
+
value = f"{profile_id}\0{object_type}\0{object_id}\0{operation_id}"
|
|
15
|
+
return hashlib.sha256(value.encode("utf-8")).hexdigest()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _table_exists(db: Any, table: str) -> bool:
|
|
19
|
+
return bool(db.execute(
|
|
20
|
+
"SELECT 1 FROM sqlite_master WHERE type='table' AND name=?", (table,),
|
|
21
|
+
))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _record(
|
|
25
|
+
db: Any,
|
|
26
|
+
*,
|
|
27
|
+
profile_id: str,
|
|
28
|
+
object_type: str,
|
|
29
|
+
object_id: str,
|
|
30
|
+
operation_id: str,
|
|
31
|
+
derivation_version: str,
|
|
32
|
+
source_status: str,
|
|
33
|
+
source_start: int | None = None,
|
|
34
|
+
source_end: int | None = None,
|
|
35
|
+
source_text_sha256: str = "",
|
|
36
|
+
source_fact_ids: tuple[str, ...] = (),
|
|
37
|
+
unresolved_reason: str = "",
|
|
38
|
+
) -> None:
|
|
39
|
+
db.execute(
|
|
40
|
+
"INSERT OR REPLACE INTO derivation_lineage "
|
|
41
|
+
"(lineage_id,profile_id,object_type,object_id,operation_id,"
|
|
42
|
+
"derivation_version,source_status,source_start,source_end,"
|
|
43
|
+
"source_text_sha256,source_fact_ids_json,unresolved_reason) "
|
|
44
|
+
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",
|
|
45
|
+
(
|
|
46
|
+
_lineage_id(profile_id, object_type, object_id, operation_id),
|
|
47
|
+
profile_id, object_type, object_id, operation_id,
|
|
48
|
+
derivation_version, source_status, source_start, source_end,
|
|
49
|
+
source_text_sha256,
|
|
50
|
+
json.dumps(list(source_fact_ids), separators=(",", ":")),
|
|
51
|
+
unresolved_reason,
|
|
52
|
+
),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _fact_ids(value: Any) -> tuple[str, ...]:
|
|
57
|
+
try:
|
|
58
|
+
decoded = json.loads(value or "[]")
|
|
59
|
+
except (TypeError, ValueError):
|
|
60
|
+
return ()
|
|
61
|
+
if not isinstance(decoded, list):
|
|
62
|
+
return ()
|
|
63
|
+
return tuple(str(item) for item in decoded)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _capture_derived_rows(
|
|
67
|
+
db: Any,
|
|
68
|
+
*,
|
|
69
|
+
table: str,
|
|
70
|
+
id_column: str,
|
|
71
|
+
object_type: str,
|
|
72
|
+
profile_id: str,
|
|
73
|
+
operation_id: str,
|
|
74
|
+
derivation_version: str,
|
|
75
|
+
operation_fact_ids: frozenset[str],
|
|
76
|
+
) -> None:
|
|
77
|
+
if not _table_exists(db, table):
|
|
78
|
+
return
|
|
79
|
+
rows = db.execute(
|
|
80
|
+
f'SELECT "{id_column}",fact_ids_json FROM "{table}" WHERE profile_id=?',
|
|
81
|
+
(profile_id,),
|
|
82
|
+
)
|
|
83
|
+
for row in rows:
|
|
84
|
+
source_ids = tuple(
|
|
85
|
+
fact_id
|
|
86
|
+
for fact_id in _fact_ids(row["fact_ids_json"])
|
|
87
|
+
if fact_id in operation_fact_ids
|
|
88
|
+
)
|
|
89
|
+
if not source_ids:
|
|
90
|
+
continue
|
|
91
|
+
_record(
|
|
92
|
+
db,
|
|
93
|
+
profile_id=profile_id,
|
|
94
|
+
object_type=object_type,
|
|
95
|
+
object_id=str(row[id_column]),
|
|
96
|
+
operation_id=operation_id,
|
|
97
|
+
derivation_version=derivation_version,
|
|
98
|
+
source_status="derived_from_facts",
|
|
99
|
+
source_fact_ids=source_ids,
|
|
100
|
+
unresolved_reason="direct_span_not_applicable",
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def capture_operation_lineage(
|
|
105
|
+
db: Any,
|
|
106
|
+
*,
|
|
107
|
+
operation_id: str,
|
|
108
|
+
profile_id: str,
|
|
109
|
+
raw_content: str,
|
|
110
|
+
fact_ids: tuple[str, ...],
|
|
111
|
+
derivation_version: str,
|
|
112
|
+
) -> None:
|
|
113
|
+
"""Persist operation lineage while distinguishing spans from derivations."""
|
|
114
|
+
if not _table_exists(db, "derivation_lineage"):
|
|
115
|
+
return
|
|
116
|
+
fact_lineage: dict[str, dict[str, Any]] = {}
|
|
117
|
+
for fact_id in fact_ids:
|
|
118
|
+
rows = db.execute(
|
|
119
|
+
"SELECT content FROM atomic_facts WHERE fact_id=? AND profile_id=?",
|
|
120
|
+
(fact_id, profile_id),
|
|
121
|
+
)
|
|
122
|
+
if not rows:
|
|
123
|
+
_record(
|
|
124
|
+
db, profile_id=profile_id, object_type="fact", object_id=fact_id,
|
|
125
|
+
operation_id=operation_id, derivation_version=derivation_version,
|
|
126
|
+
source_status="unresolved", unresolved_reason="final_fact_missing",
|
|
127
|
+
)
|
|
128
|
+
fact_lineage[fact_id] = {
|
|
129
|
+
"source_status": "unresolved",
|
|
130
|
+
"unresolved_reason": "final_fact_missing",
|
|
131
|
+
}
|
|
132
|
+
continue
|
|
133
|
+
content = str(rows[0]["content"])
|
|
134
|
+
start = raw_content.find(content)
|
|
135
|
+
if start >= 0:
|
|
136
|
+
_record(
|
|
137
|
+
db, profile_id=profile_id, object_type="fact", object_id=fact_id,
|
|
138
|
+
operation_id=operation_id, derivation_version=derivation_version,
|
|
139
|
+
source_status="exact", source_start=start,
|
|
140
|
+
source_end=start + len(content),
|
|
141
|
+
source_text_sha256=hashlib.sha256(content.encode("utf-8")).hexdigest(),
|
|
142
|
+
)
|
|
143
|
+
fact_lineage[fact_id] = {
|
|
144
|
+
"source_status": "exact",
|
|
145
|
+
"source_start": start,
|
|
146
|
+
"source_end": start + len(content),
|
|
147
|
+
"source_text_sha256": hashlib.sha256(
|
|
148
|
+
content.encode("utf-8")
|
|
149
|
+
).hexdigest(),
|
|
150
|
+
}
|
|
151
|
+
else:
|
|
152
|
+
_record(
|
|
153
|
+
db, profile_id=profile_id, object_type="fact", object_id=fact_id,
|
|
154
|
+
operation_id=operation_id, derivation_version=derivation_version,
|
|
155
|
+
source_status="unresolved",
|
|
156
|
+
unresolved_reason="no_exact_span_in_raw_source",
|
|
157
|
+
)
|
|
158
|
+
fact_lineage[fact_id] = {
|
|
159
|
+
"source_status": "unresolved",
|
|
160
|
+
"unresolved_reason": "no_exact_span_in_raw_source",
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
operation_fact_ids = frozenset(fact_ids)
|
|
164
|
+
_record(
|
|
165
|
+
db,
|
|
166
|
+
profile_id=profile_id,
|
|
167
|
+
object_type="profile",
|
|
168
|
+
object_id=profile_id,
|
|
169
|
+
operation_id=operation_id,
|
|
170
|
+
derivation_version=derivation_version,
|
|
171
|
+
source_status="not_applicable",
|
|
172
|
+
unresolved_reason="profile_scope_not_derived_from_source_span",
|
|
173
|
+
)
|
|
174
|
+
_capture_derived_rows(
|
|
175
|
+
db,
|
|
176
|
+
table="entity_profiles",
|
|
177
|
+
id_column="profile_entry_id",
|
|
178
|
+
object_type="entity_summary",
|
|
179
|
+
profile_id=profile_id,
|
|
180
|
+
operation_id=operation_id,
|
|
181
|
+
derivation_version=derivation_version,
|
|
182
|
+
operation_fact_ids=operation_fact_ids,
|
|
183
|
+
)
|
|
184
|
+
_capture_derived_rows(
|
|
185
|
+
db,
|
|
186
|
+
table="memory_scenes",
|
|
187
|
+
id_column="scene_id",
|
|
188
|
+
object_type="memory_scene",
|
|
189
|
+
profile_id=profile_id,
|
|
190
|
+
operation_id=operation_id,
|
|
191
|
+
derivation_version=derivation_version,
|
|
192
|
+
operation_fact_ids=operation_fact_ids,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if _table_exists(db, "graph_edges"):
|
|
196
|
+
rows = db.execute(
|
|
197
|
+
"SELECT edge_id,source_id,target_id FROM graph_edges WHERE profile_id=?",
|
|
198
|
+
(profile_id,),
|
|
199
|
+
)
|
|
200
|
+
for row in rows:
|
|
201
|
+
source_ids = tuple(
|
|
202
|
+
value
|
|
203
|
+
for value in (str(row["source_id"]), str(row["target_id"]))
|
|
204
|
+
if value in operation_fact_ids
|
|
205
|
+
)
|
|
206
|
+
if source_ids:
|
|
207
|
+
_record(
|
|
208
|
+
db,
|
|
209
|
+
profile_id=profile_id,
|
|
210
|
+
object_type="graph_edge",
|
|
211
|
+
object_id=str(row["edge_id"]),
|
|
212
|
+
operation_id=operation_id,
|
|
213
|
+
derivation_version=derivation_version,
|
|
214
|
+
source_status="derived_from_facts",
|
|
215
|
+
source_fact_ids=source_ids,
|
|
216
|
+
unresolved_reason="direct_span_not_applicable",
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
if _table_exists(db, "bm25_tokens"):
|
|
220
|
+
indexed_ids = {
|
|
221
|
+
str(row["fact_id"])
|
|
222
|
+
for row in db.execute(
|
|
223
|
+
"SELECT fact_id FROM bm25_tokens WHERE profile_id=?", (profile_id,)
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
for fact_id in operation_fact_ids & indexed_ids:
|
|
227
|
+
lineage = fact_lineage.get(fact_id, {})
|
|
228
|
+
_record(
|
|
229
|
+
db,
|
|
230
|
+
profile_id=profile_id,
|
|
231
|
+
object_type="index_bm25",
|
|
232
|
+
object_id=fact_id,
|
|
233
|
+
operation_id=operation_id,
|
|
234
|
+
derivation_version=derivation_version,
|
|
235
|
+
source_status=str(lineage.get("source_status", "unresolved")),
|
|
236
|
+
source_start=lineage.get("source_start"),
|
|
237
|
+
source_end=lineage.get("source_end"),
|
|
238
|
+
source_text_sha256=str(lineage.get("source_text_sha256", "")),
|
|
239
|
+
source_fact_ids=(fact_id,),
|
|
240
|
+
unresolved_reason=str(
|
|
241
|
+
lineage.get("unresolved_reason", "source_fact_lineage_missing")
|
|
242
|
+
),
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
__all__ = ["capture_operation_lineage"]
|
|
@@ -24,6 +24,7 @@ from __future__ import annotations
|
|
|
24
24
|
|
|
25
25
|
import json
|
|
26
26
|
import os
|
|
27
|
+
import platform
|
|
27
28
|
import signal
|
|
28
29
|
import sys
|
|
29
30
|
|
|
@@ -52,6 +53,18 @@ def _start_parent_watchdog() -> None:
|
|
|
52
53
|
start_parent_watchdog()
|
|
53
54
|
|
|
54
55
|
|
|
56
|
+
def _embedding_backend_order() -> tuple[str, str]:
|
|
57
|
+
"""Return the stable local backend preference for this platform.
|
|
58
|
+
|
|
59
|
+
ONNX model export/loading can stall indefinitely on Apple Silicon while
|
|
60
|
+
the same cached model loads through PyTorch CPU in a few seconds. Keep
|
|
61
|
+
ONNX first elsewhere and retain it as a fallback on macOS.
|
|
62
|
+
"""
|
|
63
|
+
if sys.platform == "darwin" and platform.machine().lower() == "arm64":
|
|
64
|
+
return ("pytorch", "onnx")
|
|
65
|
+
return ("onnx", "pytorch")
|
|
66
|
+
|
|
67
|
+
|
|
55
68
|
def _load_embedding_model(name: str) -> tuple:
|
|
56
69
|
"""Load embedding model. ONNX CPU-only first, PyTorch fallback.
|
|
57
70
|
|
|
@@ -59,26 +72,25 @@ def _load_embedding_model(name: str) -> tuple:
|
|
|
59
72
|
"""
|
|
60
73
|
from sentence_transformers import SentenceTransformer
|
|
61
74
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return None, ""
|
|
75
|
+
for backend in _embedding_backend_order():
|
|
76
|
+
try:
|
|
77
|
+
if backend == "onnx":
|
|
78
|
+
m = SentenceTransformer(
|
|
79
|
+
name,
|
|
80
|
+
backend="onnx",
|
|
81
|
+
trust_remote_code=True,
|
|
82
|
+
model_kwargs={"provider": "CPUExecutionProvider"},
|
|
83
|
+
)
|
|
84
|
+
else:
|
|
85
|
+
import torch
|
|
86
|
+
with torch.inference_mode():
|
|
87
|
+
m = SentenceTransformer(
|
|
88
|
+
name, trust_remote_code=True, device="cpu",
|
|
89
|
+
)
|
|
90
|
+
return m, backend
|
|
91
|
+
except Exception:
|
|
92
|
+
continue
|
|
93
|
+
return None, ""
|
|
82
94
|
|
|
83
95
|
|
|
84
96
|
def _worker_main() -> None:
|
|
@@ -61,12 +61,22 @@ class DimensionMismatchError(RuntimeError):
|
|
|
61
61
|
# This lock is the secondary safety net for when the daemon isn't available.
|
|
62
62
|
# ---------------------------------------------------------------------------
|
|
63
63
|
|
|
64
|
-
_EMBEDDING_LOCK_FILE = Path.home() / ".superlocalmemory" / ".embedding.lock"
|
|
65
|
-
_EMBEDDING_PID_FILE = Path.home() / ".superlocalmemory" / ".embedding-worker.pid"
|
|
66
64
|
_MAX_CONCURRENT_WORKERS = int(os.environ.get("SLM_MAX_EMBEDDING_WORKERS", 1))
|
|
67
65
|
_embedding_lock_fd: int | None = None
|
|
68
66
|
|
|
69
67
|
|
|
68
|
+
def _embedding_lock_file() -> Path:
|
|
69
|
+
from superlocalmemory.infra.data_root import state_path
|
|
70
|
+
|
|
71
|
+
return state_path(".embedding.lock")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _embedding_pid_file() -> Path:
|
|
75
|
+
from superlocalmemory.infra.data_root import state_path
|
|
76
|
+
|
|
77
|
+
return state_path(".embedding-worker.pid")
|
|
78
|
+
|
|
79
|
+
|
|
70
80
|
def _is_embedding_worker_alive() -> bool:
|
|
71
81
|
"""Check if an embedding worker PID file exists and that PID is alive.
|
|
72
82
|
|
|
@@ -74,21 +84,23 @@ def _is_embedding_worker_alive() -> bool:
|
|
|
74
84
|
check if one is already running. Prevents duplicate 1.6GB workers.
|
|
75
85
|
"""
|
|
76
86
|
try:
|
|
77
|
-
|
|
87
|
+
pid_file = _embedding_pid_file()
|
|
88
|
+
if not pid_file.exists():
|
|
78
89
|
return False
|
|
79
|
-
pid = int(
|
|
90
|
+
pid = int(pid_file.read_text().strip())
|
|
80
91
|
os.kill(pid, 0) # Signal 0 = check if alive
|
|
81
92
|
return True
|
|
82
93
|
except (ValueError, OSError, ProcessLookupError):
|
|
83
94
|
# PID file invalid or process dead — clean up stale file
|
|
84
|
-
|
|
95
|
+
_embedding_pid_file().unlink(missing_ok=True)
|
|
85
96
|
return False
|
|
86
97
|
|
|
87
98
|
|
|
88
99
|
def register_embedding_worker_pid(pid: int) -> None:
|
|
89
100
|
"""Write the embedding worker PID to the machine-wide PID file."""
|
|
90
|
-
|
|
91
|
-
|
|
101
|
+
pid_file = _embedding_pid_file()
|
|
102
|
+
pid_file.parent.mkdir(parents=True, exist_ok=True)
|
|
103
|
+
pid_file.write_text(str(pid))
|
|
92
104
|
|
|
93
105
|
|
|
94
106
|
def acquire_embedding_lock(timeout: float = 5.0) -> bool:
|
|
@@ -108,10 +120,11 @@ def acquire_embedding_lock(timeout: float = 5.0) -> bool:
|
|
|
108
120
|
return True # No file locking on Windows — PID check above is the guard
|
|
109
121
|
|
|
110
122
|
import fcntl
|
|
111
|
-
|
|
123
|
+
lock_file = _embedding_lock_file()
|
|
124
|
+
lock_file.parent.mkdir(parents=True, exist_ok=True)
|
|
112
125
|
|
|
113
126
|
try:
|
|
114
|
-
_embedding_lock_fd = os.open(str(
|
|
127
|
+
_embedding_lock_fd = os.open(str(lock_file), os.O_CREAT | os.O_RDWR)
|
|
115
128
|
deadline = time.time() + timeout
|
|
116
129
|
while time.time() < deadline:
|
|
117
130
|
try:
|
|
@@ -505,22 +518,45 @@ class EmbeddingService:
|
|
|
505
518
|
self._worker_proc = None
|
|
506
519
|
|
|
507
520
|
def _kill_worker(self) -> None:
|
|
508
|
-
"""Terminate worker
|
|
521
|
+
"""Terminate the worker and close every owned pipe exactly once."""
|
|
509
522
|
if self._idle_timer is not None:
|
|
510
523
|
self._idle_timer.cancel()
|
|
511
524
|
self._idle_timer = None
|
|
512
|
-
|
|
525
|
+
|
|
526
|
+
proc = self._worker_proc
|
|
527
|
+
if proc is not None:
|
|
528
|
+
# Detach first so re-entrant/finalizer cleanup is idempotent.
|
|
529
|
+
self._worker_proc = None
|
|
530
|
+
self._worker_ready = False
|
|
513
531
|
try:
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
532
|
+
proc.stdin.write('{"cmd":"quit"}\n')
|
|
533
|
+
proc.stdin.flush()
|
|
534
|
+
proc.wait(timeout=3)
|
|
517
535
|
except Exception:
|
|
518
536
|
try:
|
|
519
|
-
|
|
537
|
+
returncode = proc.poll()
|
|
520
538
|
except Exception:
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
539
|
+
returncode = None
|
|
540
|
+
# MagicMock/unknown poll results are treated conservatively as
|
|
541
|
+
# live; a real exited child always reports an integer code.
|
|
542
|
+
if returncode is None or not isinstance(returncode, int):
|
|
543
|
+
try:
|
|
544
|
+
proc.kill()
|
|
545
|
+
proc.wait(timeout=3)
|
|
546
|
+
except Exception:
|
|
547
|
+
pass
|
|
548
|
+
finally:
|
|
549
|
+
# TextIOWrapper.close() can itself raise BrokenPipeError while
|
|
550
|
+
# flushing buffered stdin. Suppress it here, while the stream
|
|
551
|
+
# is still strongly referenced, so it cannot surface later as
|
|
552
|
+
# an unraisable finalizer warning.
|
|
553
|
+
for stream_name in ("stdin", "stdout", "stderr"):
|
|
554
|
+
stream = getattr(proc, stream_name, None)
|
|
555
|
+
if stream is not None:
|
|
556
|
+
try:
|
|
557
|
+
stream.close()
|
|
558
|
+
except (BrokenPipeError, OSError, ValueError):
|
|
559
|
+
pass
|
|
524
560
|
|
|
525
561
|
def _reset_idle_timer(self) -> None:
|
|
526
562
|
"""Reset idle timer — kills worker after 2 min inactivity."""
|