superlocalmemory 2.8.6 → 3.0.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/LICENSE +9 -1
- package/NOTICE +63 -0
- package/README.md +165 -480
- package/bin/slm +17 -449
- package/bin/slm-npm +1 -1
- package/conftest.py +5 -0
- package/docs/api-reference.md +284 -0
- package/docs/architecture.md +149 -0
- package/docs/auto-memory.md +150 -0
- package/docs/cli-reference.md +276 -0
- package/docs/compliance.md +191 -0
- package/docs/configuration.md +182 -0
- package/docs/getting-started.md +102 -0
- package/docs/ide-setup.md +261 -0
- package/docs/mcp-tools.md +220 -0
- package/docs/migration-from-v2.md +170 -0
- package/docs/profiles.md +173 -0
- package/docs/troubleshooting.md +310 -0
- package/{configs → ide/configs}/antigravity-mcp.json +3 -3
- package/ide/configs/chatgpt-desktop-mcp.json +16 -0
- package/{configs → ide/configs}/claude-desktop-mcp.json +3 -3
- package/{configs → ide/configs}/codex-mcp.toml +4 -4
- package/{configs → ide/configs}/continue-mcp.yaml +4 -3
- package/{configs → ide/configs}/continue-skills.yaml +6 -6
- package/ide/configs/cursor-mcp.json +15 -0
- package/{configs → ide/configs}/gemini-cli-mcp.json +2 -2
- package/{configs → ide/configs}/jetbrains-mcp.json +2 -2
- package/{configs → ide/configs}/opencode-mcp.json +2 -2
- package/{configs → ide/configs}/perplexity-mcp.json +2 -2
- package/{configs → ide/configs}/vscode-copilot-mcp.json +2 -2
- package/{configs → ide/configs}/windsurf-mcp.json +3 -3
- package/{configs → ide/configs}/zed-mcp.json +2 -2
- package/{hooks → ide/hooks}/context-hook.js +9 -20
- package/ide/hooks/memory-list-skill.js +70 -0
- package/ide/hooks/memory-profile-skill.js +101 -0
- package/ide/hooks/memory-recall-skill.js +62 -0
- package/ide/hooks/memory-remember-skill.js +68 -0
- package/ide/hooks/memory-reset-skill.js +160 -0
- package/{hooks → ide/hooks}/post-recall-hook.js +2 -2
- package/ide/integrations/langchain/README.md +106 -0
- package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +9 -0
- package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +201 -0
- package/ide/integrations/langchain/pyproject.toml +38 -0
- package/{src/learning → ide/integrations/langchain}/tests/__init__.py +1 -0
- package/ide/integrations/langchain/tests/test_chat_message_history.py +215 -0
- package/ide/integrations/langchain/tests/test_security.py +117 -0
- package/ide/integrations/llamaindex/README.md +81 -0
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +9 -0
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +316 -0
- package/ide/integrations/llamaindex/pyproject.toml +43 -0
- package/{src/lifecycle → ide/integrations/llamaindex}/tests/__init__.py +1 -2
- package/ide/integrations/llamaindex/tests/test_chat_store.py +294 -0
- package/ide/integrations/llamaindex/tests/test_security.py +241 -0
- package/{skills → ide/skills}/slm-build-graph/SKILL.md +6 -6
- package/{skills → ide/skills}/slm-list-recent/SKILL.md +5 -5
- package/{skills → ide/skills}/slm-recall/SKILL.md +5 -5
- package/{skills → ide/skills}/slm-remember/SKILL.md +6 -6
- package/{skills → ide/skills}/slm-show-patterns/SKILL.md +7 -7
- package/{skills → ide/skills}/slm-status/SKILL.md +9 -9
- package/{skills → ide/skills}/slm-switch-profile/SKILL.md +9 -9
- package/package.json +13 -22
- package/pyproject.toml +85 -0
- package/scripts/build-dmg.sh +417 -0
- package/scripts/install-skills.ps1 +334 -0
- package/scripts/postinstall.js +2 -2
- package/scripts/start-dashboard.ps1 +52 -0
- package/scripts/start-dashboard.sh +41 -0
- package/scripts/sync-wiki.ps1 +127 -0
- package/scripts/sync-wiki.sh +82 -0
- package/scripts/test-dmg.sh +161 -0
- package/scripts/test-npm-package.ps1 +252 -0
- package/scripts/test-npm-package.sh +207 -0
- package/scripts/verify-install.ps1 +294 -0
- package/scripts/verify-install.sh +266 -0
- package/src/superlocalmemory/__init__.py +0 -0
- package/src/superlocalmemory/attribution/__init__.py +9 -0
- package/src/superlocalmemory/attribution/mathematical_dna.py +235 -0
- package/src/superlocalmemory/attribution/signer.py +153 -0
- package/src/superlocalmemory/attribution/watermark.py +189 -0
- package/src/superlocalmemory/cli/__init__.py +5 -0
- package/src/superlocalmemory/cli/commands.py +245 -0
- package/src/superlocalmemory/cli/main.py +89 -0
- package/src/superlocalmemory/cli/migrate_cmd.py +55 -0
- package/src/superlocalmemory/cli/post_install.py +99 -0
- package/src/superlocalmemory/cli/setup_wizard.py +129 -0
- package/src/superlocalmemory/compliance/__init__.py +0 -0
- package/src/superlocalmemory/compliance/abac.py +204 -0
- package/src/superlocalmemory/compliance/audit.py +314 -0
- package/src/superlocalmemory/compliance/eu_ai_act.py +131 -0
- package/src/superlocalmemory/compliance/gdpr.py +294 -0
- package/src/superlocalmemory/compliance/lifecycle.py +158 -0
- package/src/superlocalmemory/compliance/retention.py +232 -0
- package/src/superlocalmemory/compliance/scheduler.py +148 -0
- package/src/superlocalmemory/core/__init__.py +0 -0
- package/src/superlocalmemory/core/config.py +391 -0
- package/src/superlocalmemory/core/embeddings.py +293 -0
- package/src/superlocalmemory/core/engine.py +701 -0
- package/src/superlocalmemory/core/hooks.py +65 -0
- package/src/superlocalmemory/core/maintenance.py +172 -0
- package/src/superlocalmemory/core/modes.py +140 -0
- package/src/superlocalmemory/core/profiles.py +234 -0
- package/src/superlocalmemory/core/registry.py +117 -0
- package/src/superlocalmemory/dynamics/__init__.py +0 -0
- package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +223 -0
- package/src/superlocalmemory/encoding/__init__.py +0 -0
- package/src/superlocalmemory/encoding/consolidator.py +485 -0
- package/src/superlocalmemory/encoding/emotional.py +125 -0
- package/src/superlocalmemory/encoding/entity_resolver.py +525 -0
- package/src/superlocalmemory/encoding/entropy_gate.py +104 -0
- package/src/superlocalmemory/encoding/fact_extractor.py +775 -0
- package/src/superlocalmemory/encoding/foresight.py +91 -0
- package/src/superlocalmemory/encoding/graph_builder.py +302 -0
- package/src/superlocalmemory/encoding/observation_builder.py +160 -0
- package/src/superlocalmemory/encoding/scene_builder.py +183 -0
- package/src/superlocalmemory/encoding/signal_inference.py +90 -0
- package/src/superlocalmemory/encoding/temporal_parser.py +426 -0
- package/src/superlocalmemory/encoding/type_router.py +235 -0
- package/src/superlocalmemory/hooks/__init__.py +3 -0
- package/src/superlocalmemory/hooks/auto_capture.py +111 -0
- package/src/superlocalmemory/hooks/auto_recall.py +93 -0
- package/src/superlocalmemory/hooks/ide_connector.py +204 -0
- package/src/superlocalmemory/hooks/rules_engine.py +99 -0
- package/src/superlocalmemory/infra/__init__.py +3 -0
- package/src/superlocalmemory/infra/auth_middleware.py +82 -0
- package/src/superlocalmemory/infra/backup.py +317 -0
- package/src/superlocalmemory/infra/cache_manager.py +267 -0
- package/src/superlocalmemory/infra/event_bus.py +381 -0
- package/src/superlocalmemory/infra/rate_limiter.py +135 -0
- package/src/{webhook_dispatcher.py → superlocalmemory/infra/webhook_dispatcher.py} +104 -101
- package/src/superlocalmemory/learning/__init__.py +0 -0
- package/src/superlocalmemory/learning/adaptive.py +172 -0
- package/src/superlocalmemory/learning/behavioral.py +490 -0
- package/src/superlocalmemory/learning/behavioral_listener.py +94 -0
- package/src/superlocalmemory/learning/bootstrap.py +298 -0
- package/src/superlocalmemory/learning/cross_project.py +399 -0
- package/src/superlocalmemory/learning/database.py +376 -0
- package/src/superlocalmemory/learning/engagement.py +323 -0
- package/src/superlocalmemory/learning/features.py +138 -0
- package/src/superlocalmemory/learning/feedback.py +316 -0
- package/src/superlocalmemory/learning/outcomes.py +255 -0
- package/src/superlocalmemory/learning/project_context.py +366 -0
- package/src/superlocalmemory/learning/ranker.py +155 -0
- package/src/superlocalmemory/learning/source_quality.py +303 -0
- package/src/superlocalmemory/learning/workflows.py +309 -0
- package/src/superlocalmemory/llm/__init__.py +0 -0
- package/src/superlocalmemory/llm/backbone.py +316 -0
- package/src/superlocalmemory/math/__init__.py +0 -0
- package/src/superlocalmemory/math/fisher.py +356 -0
- package/src/superlocalmemory/math/langevin.py +398 -0
- package/src/superlocalmemory/math/sheaf.py +257 -0
- package/src/superlocalmemory/mcp/__init__.py +0 -0
- package/src/superlocalmemory/mcp/resources.py +245 -0
- package/src/superlocalmemory/mcp/server.py +61 -0
- package/src/superlocalmemory/mcp/tools.py +18 -0
- package/src/superlocalmemory/mcp/tools_core.py +305 -0
- package/src/superlocalmemory/mcp/tools_v28.py +223 -0
- package/src/superlocalmemory/mcp/tools_v3.py +286 -0
- package/src/superlocalmemory/retrieval/__init__.py +0 -0
- package/src/superlocalmemory/retrieval/agentic.py +295 -0
- package/src/superlocalmemory/retrieval/ann_index.py +223 -0
- package/src/superlocalmemory/retrieval/bm25_channel.py +185 -0
- package/src/superlocalmemory/retrieval/bridge_discovery.py +170 -0
- package/src/superlocalmemory/retrieval/engine.py +390 -0
- package/src/superlocalmemory/retrieval/entity_channel.py +179 -0
- package/src/superlocalmemory/retrieval/fusion.py +78 -0
- package/src/superlocalmemory/retrieval/profile_channel.py +105 -0
- package/src/superlocalmemory/retrieval/reranker.py +154 -0
- package/src/superlocalmemory/retrieval/semantic_channel.py +232 -0
- package/src/superlocalmemory/retrieval/strategy.py +96 -0
- package/src/superlocalmemory/retrieval/temporal_channel.py +175 -0
- package/src/superlocalmemory/server/__init__.py +1 -0
- package/src/superlocalmemory/server/api.py +248 -0
- package/src/superlocalmemory/server/routes/__init__.py +4 -0
- package/src/superlocalmemory/server/routes/agents.py +107 -0
- package/src/superlocalmemory/server/routes/backup.py +91 -0
- package/src/superlocalmemory/server/routes/behavioral.py +127 -0
- package/src/superlocalmemory/server/routes/compliance.py +160 -0
- package/src/superlocalmemory/server/routes/data_io.py +188 -0
- package/src/superlocalmemory/server/routes/events.py +183 -0
- package/src/superlocalmemory/server/routes/helpers.py +85 -0
- package/src/superlocalmemory/server/routes/learning.py +273 -0
- package/src/superlocalmemory/server/routes/lifecycle.py +116 -0
- package/src/superlocalmemory/server/routes/memories.py +399 -0
- package/src/superlocalmemory/server/routes/profiles.py +219 -0
- package/src/superlocalmemory/server/routes/stats.py +346 -0
- package/src/superlocalmemory/server/routes/v3_api.py +365 -0
- package/src/superlocalmemory/server/routes/ws.py +82 -0
- package/src/superlocalmemory/server/security_middleware.py +57 -0
- package/src/superlocalmemory/server/ui.py +245 -0
- package/src/superlocalmemory/storage/__init__.py +0 -0
- package/src/superlocalmemory/storage/access_control.py +182 -0
- package/src/superlocalmemory/storage/database.py +594 -0
- package/src/superlocalmemory/storage/migrations.py +303 -0
- package/src/superlocalmemory/storage/models.py +406 -0
- package/src/superlocalmemory/storage/schema.py +726 -0
- package/src/superlocalmemory/storage/v2_migrator.py +317 -0
- package/src/superlocalmemory/trust/__init__.py +0 -0
- package/src/superlocalmemory/trust/gate.py +130 -0
- package/src/superlocalmemory/trust/provenance.py +124 -0
- package/src/superlocalmemory/trust/scorer.py +347 -0
- package/src/superlocalmemory/trust/signals.py +153 -0
- package/ui/index.html +278 -5
- package/ui/js/auto-settings.js +70 -0
- package/ui/js/dashboard.js +90 -0
- package/ui/js/fact-detail.js +92 -0
- package/ui/js/feedback.js +2 -2
- package/ui/js/ide-status.js +102 -0
- package/ui/js/math-health.js +98 -0
- package/ui/js/recall-lab.js +127 -0
- package/ui/js/settings.js +2 -2
- package/ui/js/trust-dashboard.js +73 -0
- package/api_server.py +0 -724
- package/bin/aider-smart +0 -72
- package/bin/superlocalmemoryv2-learning +0 -4
- package/bin/superlocalmemoryv2-list +0 -3
- package/bin/superlocalmemoryv2-patterns +0 -4
- package/bin/superlocalmemoryv2-profile +0 -3
- package/bin/superlocalmemoryv2-recall +0 -3
- package/bin/superlocalmemoryv2-remember +0 -3
- package/bin/superlocalmemoryv2-reset +0 -3
- package/bin/superlocalmemoryv2-status +0 -3
- package/configs/chatgpt-desktop-mcp.json +0 -16
- package/configs/cursor-mcp.json +0 -15
- package/hooks/memory-list-skill.js +0 -139
- package/hooks/memory-profile-skill.js +0 -273
- package/hooks/memory-recall-skill.js +0 -114
- package/hooks/memory-remember-skill.js +0 -127
- package/hooks/memory-reset-skill.js +0 -274
- package/mcp_server.py +0 -1808
- package/requirements-core.txt +0 -22
- package/requirements-learning.txt +0 -12
- package/requirements.txt +0 -12
- package/src/agent_registry.py +0 -411
- package/src/auth_middleware.py +0 -61
- package/src/auto_backup.py +0 -459
- package/src/behavioral/__init__.py +0 -49
- package/src/behavioral/behavioral_listener.py +0 -203
- package/src/behavioral/behavioral_patterns.py +0 -275
- package/src/behavioral/cross_project_transfer.py +0 -206
- package/src/behavioral/outcome_inference.py +0 -194
- package/src/behavioral/outcome_tracker.py +0 -193
- package/src/behavioral/tests/__init__.py +0 -4
- package/src/behavioral/tests/test_behavioral_integration.py +0 -108
- package/src/behavioral/tests/test_behavioral_patterns.py +0 -150
- package/src/behavioral/tests/test_cross_project_transfer.py +0 -142
- package/src/behavioral/tests/test_mcp_behavioral.py +0 -139
- package/src/behavioral/tests/test_mcp_report_outcome.py +0 -117
- package/src/behavioral/tests/test_outcome_inference.py +0 -107
- package/src/behavioral/tests/test_outcome_tracker.py +0 -96
- package/src/cache_manager.py +0 -518
- package/src/compliance/__init__.py +0 -48
- package/src/compliance/abac_engine.py +0 -149
- package/src/compliance/abac_middleware.py +0 -116
- package/src/compliance/audit_db.py +0 -215
- package/src/compliance/audit_logger.py +0 -148
- package/src/compliance/retention_manager.py +0 -289
- package/src/compliance/retention_scheduler.py +0 -186
- package/src/compliance/tests/__init__.py +0 -4
- package/src/compliance/tests/test_abac_enforcement.py +0 -95
- package/src/compliance/tests/test_abac_engine.py +0 -124
- package/src/compliance/tests/test_abac_mcp_integration.py +0 -118
- package/src/compliance/tests/test_audit_db.py +0 -123
- package/src/compliance/tests/test_audit_logger.py +0 -98
- package/src/compliance/tests/test_mcp_audit.py +0 -128
- package/src/compliance/tests/test_mcp_retention_policy.py +0 -125
- package/src/compliance/tests/test_retention_manager.py +0 -131
- package/src/compliance/tests/test_retention_scheduler.py +0 -99
- package/src/compression/__init__.py +0 -25
- package/src/compression/cli.py +0 -150
- package/src/compression/cold_storage.py +0 -217
- package/src/compression/config.py +0 -72
- package/src/compression/orchestrator.py +0 -133
- package/src/compression/tier2_compressor.py +0 -228
- package/src/compression/tier3_compressor.py +0 -153
- package/src/compression/tier_classifier.py +0 -148
- package/src/db_connection_manager.py +0 -536
- package/src/embedding_engine.py +0 -63
- package/src/embeddings/__init__.py +0 -47
- package/src/embeddings/cache.py +0 -70
- package/src/embeddings/cli.py +0 -113
- package/src/embeddings/constants.py +0 -47
- package/src/embeddings/database.py +0 -91
- package/src/embeddings/engine.py +0 -247
- package/src/embeddings/model_loader.py +0 -145
- package/src/event_bus.py +0 -562
- package/src/graph/__init__.py +0 -36
- package/src/graph/build_helpers.py +0 -74
- package/src/graph/cli.py +0 -87
- package/src/graph/cluster_builder.py +0 -188
- package/src/graph/cluster_summary.py +0 -148
- package/src/graph/constants.py +0 -47
- package/src/graph/edge_builder.py +0 -162
- package/src/graph/entity_extractor.py +0 -95
- package/src/graph/graph_core.py +0 -226
- package/src/graph/graph_search.py +0 -231
- package/src/graph/hierarchical.py +0 -207
- package/src/graph/schema.py +0 -99
- package/src/graph_engine.py +0 -52
- package/src/hnsw_index.py +0 -628
- package/src/hybrid_search.py +0 -46
- package/src/learning/__init__.py +0 -217
- package/src/learning/adaptive_ranker.py +0 -682
- package/src/learning/bootstrap/__init__.py +0 -69
- package/src/learning/bootstrap/constants.py +0 -93
- package/src/learning/bootstrap/db_queries.py +0 -316
- package/src/learning/bootstrap/sampling.py +0 -82
- package/src/learning/bootstrap/text_utils.py +0 -71
- package/src/learning/cross_project_aggregator.py +0 -857
- package/src/learning/db/__init__.py +0 -40
- package/src/learning/db/constants.py +0 -44
- package/src/learning/db/schema.py +0 -279
- package/src/learning/engagement_tracker.py +0 -628
- package/src/learning/feature_extractor.py +0 -708
- package/src/learning/feedback_collector.py +0 -806
- package/src/learning/learning_db.py +0 -915
- package/src/learning/project_context_manager.py +0 -572
- package/src/learning/ranking/__init__.py +0 -33
- package/src/learning/ranking/constants.py +0 -84
- package/src/learning/ranking/helpers.py +0 -278
- package/src/learning/source_quality_scorer.py +0 -676
- package/src/learning/synthetic_bootstrap.py +0 -755
- package/src/learning/tests/test_adaptive_ranker.py +0 -325
- package/src/learning/tests/test_adaptive_ranker_v28.py +0 -60
- package/src/learning/tests/test_aggregator.py +0 -306
- package/src/learning/tests/test_auto_retrain_v28.py +0 -35
- package/src/learning/tests/test_e2e_ranking_v28.py +0 -82
- package/src/learning/tests/test_feature_extractor_v28.py +0 -93
- package/src/learning/tests/test_feedback_collector.py +0 -294
- package/src/learning/tests/test_learning_db.py +0 -602
- package/src/learning/tests/test_learning_db_v28.py +0 -110
- package/src/learning/tests/test_learning_init_v28.py +0 -48
- package/src/learning/tests/test_outcome_signals.py +0 -48
- package/src/learning/tests/test_project_context.py +0 -292
- package/src/learning/tests/test_schema_migration.py +0 -319
- package/src/learning/tests/test_signal_inference.py +0 -397
- package/src/learning/tests/test_source_quality.py +0 -351
- package/src/learning/tests/test_synthetic_bootstrap.py +0 -429
- package/src/learning/tests/test_workflow_miner.py +0 -318
- package/src/learning/workflow_pattern_miner.py +0 -655
- package/src/lifecycle/__init__.py +0 -54
- package/src/lifecycle/bounded_growth.py +0 -239
- package/src/lifecycle/compaction_engine.py +0 -226
- package/src/lifecycle/lifecycle_engine.py +0 -355
- package/src/lifecycle/lifecycle_evaluator.py +0 -257
- package/src/lifecycle/lifecycle_scheduler.py +0 -130
- package/src/lifecycle/retention_policy.py +0 -285
- package/src/lifecycle/tests/test_bounded_growth.py +0 -193
- package/src/lifecycle/tests/test_compaction.py +0 -179
- package/src/lifecycle/tests/test_lifecycle_engine.py +0 -137
- package/src/lifecycle/tests/test_lifecycle_evaluation.py +0 -177
- package/src/lifecycle/tests/test_lifecycle_scheduler.py +0 -127
- package/src/lifecycle/tests/test_lifecycle_search.py +0 -109
- package/src/lifecycle/tests/test_mcp_compact.py +0 -149
- package/src/lifecycle/tests/test_mcp_lifecycle_status.py +0 -114
- package/src/lifecycle/tests/test_retention_policy.py +0 -162
- package/src/mcp_tools_v28.py +0 -281
- package/src/memory/__init__.py +0 -36
- package/src/memory/cli.py +0 -205
- package/src/memory/constants.py +0 -39
- package/src/memory/helpers.py +0 -28
- package/src/memory/schema.py +0 -166
- package/src/memory-profiles.py +0 -595
- package/src/memory-reset.py +0 -491
- package/src/memory_compression.py +0 -989
- package/src/memory_store_v2.py +0 -1155
- package/src/migrate_v1_to_v2.py +0 -629
- package/src/pattern_learner.py +0 -34
- package/src/patterns/__init__.py +0 -24
- package/src/patterns/analyzers.py +0 -251
- package/src/patterns/learner.py +0 -271
- package/src/patterns/scoring.py +0 -171
- package/src/patterns/store.py +0 -225
- package/src/patterns/terminology.py +0 -140
- package/src/provenance_tracker.py +0 -312
- package/src/qualixar_attribution.py +0 -139
- package/src/qualixar_watermark.py +0 -78
- package/src/query_optimizer.py +0 -511
- package/src/rate_limiter.py +0 -83
- package/src/search/__init__.py +0 -20
- package/src/search/cli.py +0 -77
- package/src/search/constants.py +0 -26
- package/src/search/engine.py +0 -241
- package/src/search/fusion.py +0 -122
- package/src/search/index_loader.py +0 -114
- package/src/search/methods.py +0 -162
- package/src/search_engine_v2.py +0 -401
- package/src/setup_validator.py +0 -482
- package/src/subscription_manager.py +0 -391
- package/src/tree/__init__.py +0 -59
- package/src/tree/builder.py +0 -185
- package/src/tree/nodes.py +0 -202
- package/src/tree/queries.py +0 -257
- package/src/tree/schema.py +0 -80
- package/src/tree_manager.py +0 -19
- package/src/trust/__init__.py +0 -45
- package/src/trust/constants.py +0 -66
- package/src/trust/queries.py +0 -157
- package/src/trust/schema.py +0 -95
- package/src/trust/scorer.py +0 -299
- package/src/trust/signals.py +0 -95
- package/src/trust_scorer.py +0 -44
- package/ui/app.js +0 -1588
- package/ui/js/graph-cytoscape-monolithic-backup.js +0 -1168
- package/ui/js/graph-cytoscape.js +0 -1168
- package/ui/js/graph-d3-backup.js +0 -32
- package/ui/js/graph.js +0 -32
- package/ui_server.py +0 -286
- /package/docs/{ACCESSIBILITY.md → v2-archive/ACCESSIBILITY.md} +0 -0
- /package/docs/{ARCHITECTURE.md → v2-archive/ARCHITECTURE.md} +0 -0
- /package/docs/{CLI-COMMANDS-REFERENCE.md → v2-archive/CLI-COMMANDS-REFERENCE.md} +0 -0
- /package/docs/{COMPRESSION-README.md → v2-archive/COMPRESSION-README.md} +0 -0
- /package/docs/{FRAMEWORK-INTEGRATIONS.md → v2-archive/FRAMEWORK-INTEGRATIONS.md} +0 -0
- /package/docs/{MCP-MANUAL-SETUP.md → v2-archive/MCP-MANUAL-SETUP.md} +0 -0
- /package/docs/{MCP-TROUBLESHOOTING.md → v2-archive/MCP-TROUBLESHOOTING.md} +0 -0
- /package/docs/{PATTERN-LEARNING.md → v2-archive/PATTERN-LEARNING.md} +0 -0
- /package/docs/{PROFILES-GUIDE.md → v2-archive/PROFILES-GUIDE.md} +0 -0
- /package/docs/{RESET-GUIDE.md → v2-archive/RESET-GUIDE.md} +0 -0
- /package/docs/{SEARCH-ENGINE-V2.2.0.md → v2-archive/SEARCH-ENGINE-V2.2.0.md} +0 -0
- /package/docs/{SEARCH-INTEGRATION-GUIDE.md → v2-archive/SEARCH-INTEGRATION-GUIDE.md} +0 -0
- /package/docs/{UI-SERVER.md → v2-archive/UI-SERVER.md} +0 -0
- /package/docs/{UNIVERSAL-INTEGRATION.md → v2-archive/UNIVERSAL-INTEGRATION.md} +0 -0
- /package/docs/{V2.2.0-OPTIONAL-SEARCH.md → v2-archive/V2.2.0-OPTIONAL-SEARCH.md} +0 -0
- /package/docs/{WINDOWS-INSTALL-README.txt → v2-archive/WINDOWS-INSTALL-README.txt} +0 -0
- /package/docs/{WINDOWS-POST-INSTALL.txt → v2-archive/WINDOWS-POST-INSTALL.txt} +0 -0
- /package/docs/{example_graph_usage.py → v2-archive/example_graph_usage.py} +0 -0
- /package/{completions → ide/completions}/slm.bash +0 -0
- /package/{completions → ide/completions}/slm.zsh +0 -0
- /package/{configs → ide/configs}/cody-commands.json +0 -0
- /package/{install-skills.sh → scripts/install-skills.sh} +0 -0
- /package/{install.ps1 → scripts/install.ps1} +0 -0
- /package/{install.sh → scripts/install.sh} +0 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under the MIT License - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
+
|
|
5
|
+
"""Riemannian Langevin dynamics with persistence for memory lifecycle.
|
|
6
|
+
|
|
7
|
+
Evolves memory positions via a discretised Langevin SDE driven by an
|
|
8
|
+
access-aware potential function. Frequently accessed memories are
|
|
9
|
+
confined near the origin (ACTIVE, high retrieval weight) by a strong
|
|
10
|
+
potential, while neglected memories diffuse toward the boundary
|
|
11
|
+
(ARCHIVED, weight approaches 0) as their confinement weakens.
|
|
12
|
+
|
|
13
|
+
Core SDE (Girolami & Calderhead 2011):
|
|
14
|
+
|
|
15
|
+
xi_{t+1} = xi_t - g^{-1}(xi_t) * grad U(xi_t) * dt
|
|
16
|
+
+ sqrt(2 * T * dt) * g^{-1/2}(xi_t) * eta
|
|
17
|
+
|
|
18
|
+
Potential function:
|
|
19
|
+
|
|
20
|
+
U(xi) = alpha * ||xi||^2
|
|
21
|
+
- beta * log(access_count + 1)
|
|
22
|
+
+ gamma * age_days
|
|
23
|
+
- delta * importance
|
|
24
|
+
|
|
25
|
+
- Accessed memories: beta term lowers potential -> drift to origin
|
|
26
|
+
- Old / unused: gamma term raises potential -> drift to boundary
|
|
27
|
+
- Important memories: delta term lowers potential -> retain near origin
|
|
28
|
+
|
|
29
|
+
V1 bugs fixed in Innovation Wave 4:
|
|
30
|
+
1. Positions were computed per-recall then DISCARDED. Now ``step()``
|
|
31
|
+
and ``batch_step()`` return new positions for the caller to persist.
|
|
32
|
+
2. Weight range was [0.7, 1.0] --- too narrow to change rankings.
|
|
33
|
+
Now [0.0, 1.0] so archived memories can be effectively suppressed.
|
|
34
|
+
|
|
35
|
+
References:
|
|
36
|
+
Girolami M & Calderhead B (2011). Riemann manifold Langevin and
|
|
37
|
+
Hamiltonian Monte Carlo methods. JRSS-B, 73(2), 123--214.
|
|
38
|
+
Roberts G O & Tweedie R L (1996). Exponential convergence of Langevin
|
|
39
|
+
distributions. Bernoulli, 2(4), 341--363.
|
|
40
|
+
Xifara T, Sherlock C, Livingstone S, Byrne S & Girolami M (2014).
|
|
41
|
+
Langevin diffusions and the MALA algorithm. Statistics &
|
|
42
|
+
Probability Letters, 91, 59--67.
|
|
43
|
+
|
|
44
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
45
|
+
License: MIT
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
from __future__ import annotations
|
|
49
|
+
|
|
50
|
+
import math
|
|
51
|
+
from typing import Any
|
|
52
|
+
|
|
53
|
+
import numpy as np
|
|
54
|
+
|
|
55
|
+
from superlocalmemory.storage.models import MemoryLifecycle
|
|
56
|
+
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
# Lifecycle zone boundaries (radius thresholds on the unit ball)
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
_RADIUS_ACTIVE: float = 0.3 # [0, 0.3) -> ACTIVE
|
|
62
|
+
_RADIUS_WARM: float = 0.55 # [0.3, 0.55) -> WARM
|
|
63
|
+
_RADIUS_COLD: float = 0.8 # [0.55, 0.8) -> COLD
|
|
64
|
+
# [0.8, 1.0) -> ARCHIVED
|
|
65
|
+
|
|
66
|
+
# Potential coefficients (defaults)
|
|
67
|
+
# Alpha must be > 0.5*T*(d-2)/lam_inv ≈ 1.8 at T=0.3 for confinement
|
|
68
|
+
# to counter the Riemannian curvature correction near the origin.
|
|
69
|
+
_ALPHA: float = 3.0 # Base confinement (strong enough to counter correction)
|
|
70
|
+
_BETA: float = 0.8 # Access STRENGTHENS confinement (keeps active facts near origin)
|
|
71
|
+
_GAMMA: float = 0.005 # Age WEAKENS confinement (old facts drift to boundary)
|
|
72
|
+
_DELTA: float = 0.5 # Importance STRENGTHENS confinement
|
|
73
|
+
|
|
74
|
+
# Safety
|
|
75
|
+
_MAX_NORM: float = 0.99 # Clamp radius < 1 (open ball)
|
|
76
|
+
_EPS: float = 1e-8
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# ---------------------------------------------------------------------------
|
|
80
|
+
# Public API
|
|
81
|
+
# ---------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
class LangevinDynamics:
|
|
84
|
+
"""Riemannian Langevin dynamics with persistence for memory lifecycle.
|
|
85
|
+
|
|
86
|
+
Positions live on the open unit ball B^d. The potential function
|
|
87
|
+
encodes access frequency, age, and importance as competing forces.
|
|
88
|
+
The lifecycle state (ACTIVE / WARM / COLD / ARCHIVED) is determined
|
|
89
|
+
purely by radial position.
|
|
90
|
+
|
|
91
|
+
Attributes:
|
|
92
|
+
dt: Time-step size for Euler-Maruyama integration.
|
|
93
|
+
temperature: Boltzmann temperature controlling diffusion spread.
|
|
94
|
+
weight_range: (min_weight, max_weight) for lifecycle weighting.
|
|
95
|
+
dim: Dimensionality of the position space.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
__slots__ = (
|
|
99
|
+
"dt", "temperature", "weight_range", "dim",
|
|
100
|
+
"_alpha", "_beta", "_gamma", "_delta",
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def __init__(
|
|
104
|
+
self,
|
|
105
|
+
dt: float = 0.01,
|
|
106
|
+
temperature: float = 1.0,
|
|
107
|
+
weight_range: tuple[float, float] = (0.0, 1.0),
|
|
108
|
+
dim: int = 8,
|
|
109
|
+
) -> None:
|
|
110
|
+
if dt <= 0:
|
|
111
|
+
raise ValueError(f"dt must be positive, got {dt}")
|
|
112
|
+
if temperature <= 0:
|
|
113
|
+
raise ValueError(f"temperature must be positive, got {temperature}")
|
|
114
|
+
if weight_range[0] > weight_range[1]:
|
|
115
|
+
raise ValueError(
|
|
116
|
+
f"weight_range min > max: {weight_range[0]} > {weight_range[1]}"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
self.dt = dt
|
|
120
|
+
self.temperature = temperature
|
|
121
|
+
self.weight_range = weight_range
|
|
122
|
+
self.dim = dim
|
|
123
|
+
|
|
124
|
+
# Potential coefficients
|
|
125
|
+
self._alpha = _ALPHA
|
|
126
|
+
self._beta = _BETA
|
|
127
|
+
self._gamma = _GAMMA
|
|
128
|
+
self._delta = _DELTA
|
|
129
|
+
|
|
130
|
+
# ------------------------------------------------------------------
|
|
131
|
+
# Single-fact step
|
|
132
|
+
# ------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
def step(
|
|
135
|
+
self,
|
|
136
|
+
position: list[float],
|
|
137
|
+
access_count: int,
|
|
138
|
+
age_days: float,
|
|
139
|
+
importance: float,
|
|
140
|
+
seed: int | None = None,
|
|
141
|
+
) -> tuple[list[float], float]:
|
|
142
|
+
"""One Euler-Maruyama step of the Langevin SDE.
|
|
143
|
+
|
|
144
|
+
Computes the gradient of the potential at the current position,
|
|
145
|
+
applies drift + stochastic diffusion, and returns the new position
|
|
146
|
+
plus the resulting lifecycle weight.
|
|
147
|
+
|
|
148
|
+
The caller is responsible for PERSISTING the returned position
|
|
149
|
+
back to the database. This is the key V1 fix: positions must
|
|
150
|
+
evolve across recalls, not be recomputed from scratch each time.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
position: Current position on the unit ball, length ``dim``.
|
|
154
|
+
access_count: Total number of times this fact has been accessed.
|
|
155
|
+
age_days: Days since the fact was first stored.
|
|
156
|
+
importance: Fact importance score in [0, 1].
|
|
157
|
+
seed: Optional RNG seed for reproducible diffusion.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
(new_position, lifecycle_weight) where new_position is a list
|
|
161
|
+
of floats inside the unit ball and lifecycle_weight is in
|
|
162
|
+
[weight_range[0], weight_range[1]].
|
|
163
|
+
"""
|
|
164
|
+
xi = np.asarray(position, dtype=np.float64)
|
|
165
|
+
if xi.shape[0] != self.dim:
|
|
166
|
+
# Graceful resize: pad or truncate
|
|
167
|
+
xi = _resize_position(xi, self.dim)
|
|
168
|
+
|
|
169
|
+
# --- Gradient of potential ---
|
|
170
|
+
grad = self._potential_gradient(xi, access_count, age_days, importance)
|
|
171
|
+
|
|
172
|
+
# --- Conformal factor on Poincare ball: lambda = 2 / (1 - ||x||^2) ---
|
|
173
|
+
norm_sq = float(np.dot(xi, xi))
|
|
174
|
+
norm_sq = min(norm_sq, _MAX_NORM ** 2)
|
|
175
|
+
lam = 2.0 / (1.0 - norm_sq + _EPS)
|
|
176
|
+
lam_inv = 1.0 / lam
|
|
177
|
+
|
|
178
|
+
# --- Drift: -lambda^{-2} * grad_U * dt (Eq. 5 term 1) ---
|
|
179
|
+
drift = -(lam_inv ** 2) * grad * self.dt
|
|
180
|
+
|
|
181
|
+
# --- Curvature correction: 0.5 * T * (d-2) * lambda^{-1} * xi * dt (Eq. 5 term 3) ---
|
|
182
|
+
correction = 0.5 * self.temperature * (self.dim - 2) * lam_inv * xi * self.dt
|
|
183
|
+
|
|
184
|
+
# --- Diffusion: sqrt(2T * dt) * lambda^{-1} * noise (Eq. 5 term 2) ---
|
|
185
|
+
rng = np.random.default_rng(seed)
|
|
186
|
+
noise = rng.standard_normal(self.dim)
|
|
187
|
+
diffusion = math.sqrt(2.0 * self.temperature * self.dt) * lam_inv * noise
|
|
188
|
+
|
|
189
|
+
# --- Full Euler-Maruyama update (Girolami & Calderhead 2011) ---
|
|
190
|
+
new_xi = xi + drift + correction + diffusion
|
|
191
|
+
|
|
192
|
+
# --- Project back into the open ball ---
|
|
193
|
+
new_xi = _project_to_ball(new_xi)
|
|
194
|
+
|
|
195
|
+
weight = self.compute_lifecycle_weight(new_xi.tolist())
|
|
196
|
+
return new_xi.tolist(), weight
|
|
197
|
+
|
|
198
|
+
# ------------------------------------------------------------------
|
|
199
|
+
# Lifecycle weight from position
|
|
200
|
+
# ------------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
def compute_lifecycle_weight(self, position: list[float]) -> float:
|
|
203
|
+
"""Compute retrieval weight from radial position.
|
|
204
|
+
|
|
205
|
+
Weight decreases linearly from max to min as radius moves from
|
|
206
|
+
origin to boundary. This ensures archived memories (near boundary)
|
|
207
|
+
are strongly suppressed in retrieval rankings.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
position: Current position, length ``dim``.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Weight in [weight_range[0], weight_range[1]].
|
|
214
|
+
"""
|
|
215
|
+
xi = np.asarray(position, dtype=np.float64)
|
|
216
|
+
radius = float(np.linalg.norm(xi))
|
|
217
|
+
radius = min(radius, _MAX_NORM)
|
|
218
|
+
|
|
219
|
+
# Linear interpolation: radius 0 -> max weight, radius 1 -> min weight
|
|
220
|
+
lo, hi = self.weight_range
|
|
221
|
+
weight = hi - (hi - lo) * (radius / _MAX_NORM)
|
|
222
|
+
return max(lo, min(hi, weight))
|
|
223
|
+
|
|
224
|
+
# ------------------------------------------------------------------
|
|
225
|
+
# Lifecycle state classification
|
|
226
|
+
# ------------------------------------------------------------------
|
|
227
|
+
|
|
228
|
+
def get_lifecycle_state(self, weight: float) -> MemoryLifecycle:
|
|
229
|
+
"""Classify lifecycle state from weight.
|
|
230
|
+
|
|
231
|
+
Uses the weight (derived from radius) rather than raw radius,
|
|
232
|
+
so the caller doesn't need to know zone boundaries.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
weight: Lifecycle weight in [0, 1].
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
MemoryLifecycle enum value.
|
|
239
|
+
"""
|
|
240
|
+
# Map weight back to approximate radius for zone classification
|
|
241
|
+
lo, hi = self.weight_range
|
|
242
|
+
span = hi - lo
|
|
243
|
+
if span < _EPS:
|
|
244
|
+
return MemoryLifecycle.ACTIVE
|
|
245
|
+
|
|
246
|
+
# radius_approx: weight=hi -> radius=0, weight=lo -> radius=MAX_NORM
|
|
247
|
+
radius_approx = (hi - weight) / span * _MAX_NORM
|
|
248
|
+
|
|
249
|
+
if radius_approx < _RADIUS_ACTIVE:
|
|
250
|
+
return MemoryLifecycle.ACTIVE
|
|
251
|
+
if radius_approx < _RADIUS_WARM:
|
|
252
|
+
return MemoryLifecycle.WARM
|
|
253
|
+
if radius_approx < _RADIUS_COLD:
|
|
254
|
+
return MemoryLifecycle.COLD
|
|
255
|
+
return MemoryLifecycle.ARCHIVED
|
|
256
|
+
|
|
257
|
+
# ------------------------------------------------------------------
|
|
258
|
+
# Batch step (background maintenance)
|
|
259
|
+
# ------------------------------------------------------------------
|
|
260
|
+
|
|
261
|
+
def batch_step(
|
|
262
|
+
self,
|
|
263
|
+
facts: list[dict[str, Any]],
|
|
264
|
+
seed: int | None = None,
|
|
265
|
+
) -> list[dict[str, Any]]:
|
|
266
|
+
"""Batch Langevin step for background maintenance.
|
|
267
|
+
|
|
268
|
+
Evolves all fact positions in a single pass. Designed to be
|
|
269
|
+
called periodically (e.g. every N hours) to let the memory store
|
|
270
|
+
self-organise over time.
|
|
271
|
+
|
|
272
|
+
Each input dict must contain:
|
|
273
|
+
- ``fact_id``: str
|
|
274
|
+
- ``position``: list[float]
|
|
275
|
+
- ``access_count``: int
|
|
276
|
+
- ``age_days``: float
|
|
277
|
+
- ``importance``: float
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
facts: List of fact dicts with position and metadata.
|
|
281
|
+
seed: Optional base seed (incremented per fact for variety).
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
List of dicts with ``fact_id``, ``position`` (updated),
|
|
285
|
+
``weight``, and ``lifecycle`` fields.
|
|
286
|
+
"""
|
|
287
|
+
results: list[dict[str, Any]] = []
|
|
288
|
+
base_seed = seed
|
|
289
|
+
|
|
290
|
+
for i, fact in enumerate(facts):
|
|
291
|
+
fact_seed = (base_seed + i) if base_seed is not None else None
|
|
292
|
+
|
|
293
|
+
new_pos, weight = self.step(
|
|
294
|
+
position=fact["position"],
|
|
295
|
+
access_count=fact["access_count"],
|
|
296
|
+
age_days=fact["age_days"],
|
|
297
|
+
importance=fact["importance"],
|
|
298
|
+
seed=fact_seed,
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
results.append({
|
|
302
|
+
"fact_id": fact["fact_id"],
|
|
303
|
+
"position": new_pos,
|
|
304
|
+
"weight": weight,
|
|
305
|
+
"lifecycle": self.get_lifecycle_state(weight).value,
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
return results
|
|
309
|
+
|
|
310
|
+
# ------------------------------------------------------------------
|
|
311
|
+
# Potential gradient (private)
|
|
312
|
+
# ------------------------------------------------------------------
|
|
313
|
+
|
|
314
|
+
def _potential_gradient(
|
|
315
|
+
self,
|
|
316
|
+
xi: np.ndarray,
|
|
317
|
+
access_count: int,
|
|
318
|
+
age_days: float,
|
|
319
|
+
importance: float,
|
|
320
|
+
) -> np.ndarray:
|
|
321
|
+
"""Metadata-modulated confinement gradient .
|
|
322
|
+
|
|
323
|
+
Potential:
|
|
324
|
+
U(xi) = effective_alpha * ||xi||^2
|
|
325
|
+
|
|
326
|
+
Where effective_alpha is modulated by metadata:
|
|
327
|
+
effective_alpha = alpha
|
|
328
|
+
+ beta * log(access+1)/10 (frequent access → stronger confinement → ACTIVE)
|
|
329
|
+
- gamma * min(age, 365)/365 (aging → weaker confinement → drifts to ARCHIVED)
|
|
330
|
+
+ delta * importance (important → stronger confinement → stays ACTIVE)
|
|
331
|
+
|
|
332
|
+
Gradient:
|
|
333
|
+
grad U = 2 * effective_alpha * xi
|
|
334
|
+
|
|
335
|
+
This ensures metadata drives correct lifecycle dynamics:
|
|
336
|
+
- Frequently accessed facts stay near origin (ACTIVE, high weight)
|
|
337
|
+
- Old unused facts drift toward boundary (ARCHIVED, low weight)
|
|
338
|
+
- Important facts resist archival (persistent)
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
xi: Position array.
|
|
342
|
+
access_count: Total access count.
|
|
343
|
+
age_days: Days since creation.
|
|
344
|
+
importance: Importance score [0, 1].
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
Gradient vector, same shape as xi.
|
|
348
|
+
"""
|
|
349
|
+
import math as _math
|
|
350
|
+
effective_alpha = (
|
|
351
|
+
self._alpha
|
|
352
|
+
+ self._beta * _math.log(access_count + 1) / 10.0
|
|
353
|
+
- self._gamma * min(age_days, 365.0) / 365.0
|
|
354
|
+
+ self._delta * importance
|
|
355
|
+
)
|
|
356
|
+
effective_alpha = max(0.1, effective_alpha)
|
|
357
|
+
return 2.0 * effective_alpha * xi
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
# ---------------------------------------------------------------------------
|
|
361
|
+
# Helpers (module-private)
|
|
362
|
+
# ---------------------------------------------------------------------------
|
|
363
|
+
|
|
364
|
+
def _project_to_ball(xi: np.ndarray) -> np.ndarray:
|
|
365
|
+
"""Project point back into the open unit ball.
|
|
366
|
+
|
|
367
|
+
If ||xi|| >= MAX_NORM, rescale to sit at MAX_NORM.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
xi: Position array.
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
Projected position with ||result|| < 1.
|
|
374
|
+
"""
|
|
375
|
+
norm = float(np.linalg.norm(xi))
|
|
376
|
+
if norm >= _MAX_NORM:
|
|
377
|
+
return xi * (_MAX_NORM / (norm + _EPS))
|
|
378
|
+
return xi
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def _resize_position(xi: np.ndarray, target_dim: int) -> np.ndarray:
|
|
382
|
+
"""Resize a position vector to target dimensionality.
|
|
383
|
+
|
|
384
|
+
Pads with zeros or truncates as needed.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
xi: Source position.
|
|
388
|
+
target_dim: Desired dimensionality.
|
|
389
|
+
|
|
390
|
+
Returns:
|
|
391
|
+
Resized position array.
|
|
392
|
+
"""
|
|
393
|
+
current = xi.shape[0]
|
|
394
|
+
if current == target_dim:
|
|
395
|
+
return xi
|
|
396
|
+
if current < target_dim:
|
|
397
|
+
return np.pad(xi, (0, target_dim - current), constant_values=0.0)
|
|
398
|
+
return xi[:target_dim]
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under the MIT License - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
+
|
|
5
|
+
"""Sheaf cohomology for contradiction detection at ENCODING time.
|
|
6
|
+
|
|
7
|
+
V1 applied sheaf at RECALL (HARMFUL — penalized diverse multi-hop results).
|
|
8
|
+
V2 runs at ENCODING ONLY, checking new facts against graph-connected facts.
|
|
9
|
+
|
|
10
|
+
Coboundary: delta_0 f(e) = R_b emb_b - R_a emb_a
|
|
11
|
+
Restriction maps by edge type: ENTITY=I, TEMPORAL=s*I (s<1), SEMANTIC=I.
|
|
12
|
+
Severity = ||delta_0 f(e)|| / (||R_a emb_a|| + ||R_b emb_b||).
|
|
13
|
+
|
|
14
|
+
Refs: Curry 2014 (arXiv:1303.3255), Hansen & Ghrist 2019, Robinson 2020.
|
|
15
|
+
|
|
16
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
17
|
+
License: MIT
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import logging
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from typing import TYPE_CHECKING
|
|
24
|
+
|
|
25
|
+
import numpy as np
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from superlocalmemory.storage.database import DatabaseManager
|
|
29
|
+
from superlocalmemory.storage.models import AtomicFact
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
_EPS = 1e-12
|
|
34
|
+
|
|
35
|
+
# Temporal tolerance: fraction of coboundary norm below which temporal
|
|
36
|
+
# edges are NOT flagged as contradictions (legitimate state changes).
|
|
37
|
+
TEMPORAL_TOLERANCE: float = 0.15
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
# Result type
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True)
|
|
45
|
+
class ContradictionResult:
|
|
46
|
+
"""Detected contradiction between two facts."""
|
|
47
|
+
|
|
48
|
+
fact_id_a: str # New fact being stored
|
|
49
|
+
fact_id_b: str # Existing fact it conflicts with
|
|
50
|
+
severity: float # Normalized disagreement [0.0, 1.0]
|
|
51
|
+
edge_type: str # Graph edge type (entity/temporal/semantic/causal)
|
|
52
|
+
description: str
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ---------------------------------------------------------------------------
|
|
56
|
+
# Restriction maps — non-trivial (V1 used identity everywhere)
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
def _restriction_for_edge_type(
|
|
60
|
+
edge_type: str,
|
|
61
|
+
dim: int,
|
|
62
|
+
emb_a: np.ndarray | None = None,
|
|
63
|
+
emb_b: np.ndarray | None = None,
|
|
64
|
+
) -> np.ndarray:
|
|
65
|
+
"""Edge-type-specific restriction map.
|
|
66
|
+
|
|
67
|
+
Entity edges use non-trivial maps that AMPLIFY the disagreement
|
|
68
|
+
along the axis of maximum difference between embeddings:
|
|
69
|
+
R = 0.7*I + 0.3 * outer(diff, diff) / ||diff||^2
|
|
70
|
+
|
|
71
|
+
This projects contradictions onto the most discriminative subspace,
|
|
72
|
+
making the coboundary norm more sensitive to real disagreements.
|
|
73
|
+
|
|
74
|
+
Temporal edges use identity restriction (scalar multiples cancel
|
|
75
|
+
in the normalized coboundary). Temporal tolerance is applied at
|
|
76
|
+
the caller level via TEMPORAL_TOLERANCE threshold.
|
|
77
|
+
"""
|
|
78
|
+
if edge_type == "entity" and emb_a is not None and emb_b is not None:
|
|
79
|
+
diff = emb_a - emb_b
|
|
80
|
+
norm_sq = max(float(np.dot(diff, diff)), 1e-12)
|
|
81
|
+
return 0.7 * np.eye(dim) + 0.3 * np.outer(diff, diff) / norm_sq
|
|
82
|
+
return np.eye(dim)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def edge_residual(
|
|
86
|
+
emb_a: np.ndarray, emb_b: np.ndarray,
|
|
87
|
+
R_a: np.ndarray, R_b: np.ndarray,
|
|
88
|
+
) -> np.ndarray:
|
|
89
|
+
"""Coboundary residual: delta_0 f(e) = R_b emb_b - R_a emb_a."""
|
|
90
|
+
return R_b @ emb_b - R_a @ emb_a
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def coboundary_norm(
|
|
94
|
+
emb_a: np.ndarray, emb_b: np.ndarray,
|
|
95
|
+
R_a: np.ndarray, R_b: np.ndarray,
|
|
96
|
+
) -> float:
|
|
97
|
+
"""Normalized coboundary: ||delta|| / (||R_a emb_a|| + ||R_b emb_b||).
|
|
98
|
+
|
|
99
|
+
Returns [0, ~2]. Near 0 = agree; near 1+ = strong disagreement.
|
|
100
|
+
"""
|
|
101
|
+
residual = edge_residual(emb_a, emb_b, R_a, R_b)
|
|
102
|
+
res_norm = float(np.linalg.norm(residual))
|
|
103
|
+
denom = float(np.linalg.norm(R_a @ emb_a) + np.linalg.norm(R_b @ emb_b) + _EPS)
|
|
104
|
+
return res_norm / denom
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class SheafConsistencyChecker:
|
|
108
|
+
"""Detect contradictions at ENCODING time (not retrieval).
|
|
109
|
+
|
|
110
|
+
V1 differences: uses real graph edges (not all-pairs), non-trivial
|
|
111
|
+
restriction maps, and returns results for the pipeline to act on.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
def __init__(
|
|
115
|
+
self,
|
|
116
|
+
db: DatabaseManager,
|
|
117
|
+
contradiction_threshold: float = 0.7,
|
|
118
|
+
) -> None:
|
|
119
|
+
self._db = db
|
|
120
|
+
self._threshold = max(0.0, min(2.0, contradiction_threshold))
|
|
121
|
+
|
|
122
|
+
# -- Public API ---------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
def check_consistency(
|
|
125
|
+
self,
|
|
126
|
+
new_fact: AtomicFact,
|
|
127
|
+
profile_id: str,
|
|
128
|
+
) -> list[ContradictionResult]:
|
|
129
|
+
"""Check new fact against graph-connected existing facts."""
|
|
130
|
+
if new_fact.embedding is None:
|
|
131
|
+
return []
|
|
132
|
+
if not new_fact.canonical_entities:
|
|
133
|
+
return []
|
|
134
|
+
|
|
135
|
+
emb_a = np.asarray(new_fact.embedding, dtype=np.float64)
|
|
136
|
+
dim = emb_a.shape[0]
|
|
137
|
+
if dim == 0:
|
|
138
|
+
return []
|
|
139
|
+
|
|
140
|
+
contradictions: list[ContradictionResult] = []
|
|
141
|
+
checked_pairs: set[str] = set()
|
|
142
|
+
|
|
143
|
+
# Get all graph edges touching this fact
|
|
144
|
+
edges = self._db.get_edges_for_node(new_fact.fact_id, profile_id)
|
|
145
|
+
|
|
146
|
+
for edge in edges:
|
|
147
|
+
# Determine the OTHER fact in this edge
|
|
148
|
+
other_id = (
|
|
149
|
+
edge.target_id
|
|
150
|
+
if edge.source_id == new_fact.fact_id
|
|
151
|
+
else edge.source_id
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Skip already-checked pairs and skip contradiction/supersedes edges
|
|
155
|
+
if other_id in checked_pairs:
|
|
156
|
+
continue
|
|
157
|
+
if edge.edge_type.value in ("contradiction", "supersedes"):
|
|
158
|
+
continue
|
|
159
|
+
checked_pairs.add(other_id)
|
|
160
|
+
|
|
161
|
+
# Look up the other fact's embedding
|
|
162
|
+
other_emb = self._get_fact_embedding(other_id)
|
|
163
|
+
if other_emb is None or other_emb.shape[0] != dim:
|
|
164
|
+
continue
|
|
165
|
+
|
|
166
|
+
# Compute coboundary with edge-type-specific restriction map
|
|
167
|
+
edge_type_str = edge.edge_type.value
|
|
168
|
+
R = _restriction_for_edge_type(edge_type_str, dim, emb_a, other_emb)
|
|
169
|
+
severity = coboundary_norm(emb_a, other_emb, R, R)
|
|
170
|
+
|
|
171
|
+
if severity > self._threshold:
|
|
172
|
+
contradictions.append(ContradictionResult(
|
|
173
|
+
fact_id_a=new_fact.fact_id,
|
|
174
|
+
fact_id_b=other_id,
|
|
175
|
+
severity=min(severity, 1.0),
|
|
176
|
+
edge_type=edge_type_str,
|
|
177
|
+
description=(
|
|
178
|
+
f"Sheaf coboundary {severity:.3f} > {self._threshold:.2f} "
|
|
179
|
+
f"along {edge_type_str} edge"
|
|
180
|
+
),
|
|
181
|
+
))
|
|
182
|
+
|
|
183
|
+
if contradictions:
|
|
184
|
+
logger.info(
|
|
185
|
+
"Sheaf: %d contradiction(s) for fact %s",
|
|
186
|
+
len(contradictions), new_fact.fact_id,
|
|
187
|
+
)
|
|
188
|
+
return contradictions
|
|
189
|
+
|
|
190
|
+
def detect_contradictions_batch(
|
|
191
|
+
self,
|
|
192
|
+
facts: list[AtomicFact],
|
|
193
|
+
profile_id: str,
|
|
194
|
+
) -> list[ContradictionResult]:
|
|
195
|
+
"""Pairwise check within entity groups (for batch imports)."""
|
|
196
|
+
# Group facts by canonical entity
|
|
197
|
+
entity_groups: dict[str, list[AtomicFact]] = {}
|
|
198
|
+
for fact in facts:
|
|
199
|
+
if fact.embedding is None:
|
|
200
|
+
continue
|
|
201
|
+
for eid in fact.canonical_entities:
|
|
202
|
+
entity_groups.setdefault(eid, []).append(fact)
|
|
203
|
+
|
|
204
|
+
contradictions: list[ContradictionResult] = []
|
|
205
|
+
checked_pairs: set[tuple[str, str]] = set()
|
|
206
|
+
|
|
207
|
+
for _entity_id, group in entity_groups.items():
|
|
208
|
+
for i in range(len(group)):
|
|
209
|
+
for j in range(i + 1, len(group)):
|
|
210
|
+
fa, fb = group[i], group[j]
|
|
211
|
+
pair = (min(fa.fact_id, fb.fact_id), max(fa.fact_id, fb.fact_id))
|
|
212
|
+
if pair in checked_pairs:
|
|
213
|
+
continue
|
|
214
|
+
checked_pairs.add(pair)
|
|
215
|
+
|
|
216
|
+
emb_a = np.asarray(fa.embedding, dtype=np.float64)
|
|
217
|
+
emb_b = np.asarray(fb.embedding, dtype=np.float64)
|
|
218
|
+
if emb_a.shape[0] != emb_b.shape[0] or emb_a.shape[0] == 0:
|
|
219
|
+
continue
|
|
220
|
+
|
|
221
|
+
dim = emb_a.shape[0]
|
|
222
|
+
R = np.eye(dim)
|
|
223
|
+
severity = coboundary_norm(emb_a, emb_b, R, R)
|
|
224
|
+
|
|
225
|
+
if severity > self._threshold:
|
|
226
|
+
contradictions.append(ContradictionResult(
|
|
227
|
+
fact_id_a=fa.fact_id,
|
|
228
|
+
fact_id_b=fb.fact_id,
|
|
229
|
+
severity=min(severity, 1.0),
|
|
230
|
+
edge_type="entity",
|
|
231
|
+
description=(
|
|
232
|
+
f"Batch sheaf coboundary {severity:.3f} > "
|
|
233
|
+
f"{self._threshold:.2f} (shared entity group)"
|
|
234
|
+
),
|
|
235
|
+
))
|
|
236
|
+
|
|
237
|
+
return contradictions
|
|
238
|
+
|
|
239
|
+
# -- Internal -----------------------------------------------------------
|
|
240
|
+
|
|
241
|
+
def _get_fact_embedding(self, fact_id: str) -> np.ndarray | None:
|
|
242
|
+
"""Load a fact's embedding from the database."""
|
|
243
|
+
import json
|
|
244
|
+
rows = self._db.execute(
|
|
245
|
+
"SELECT embedding FROM atomic_facts WHERE fact_id = ?",
|
|
246
|
+
(fact_id,),
|
|
247
|
+
)
|
|
248
|
+
if not rows:
|
|
249
|
+
return None
|
|
250
|
+
raw = dict(rows[0]).get("embedding")
|
|
251
|
+
if raw is None or raw == "":
|
|
252
|
+
return None
|
|
253
|
+
try:
|
|
254
|
+
data = json.loads(raw) if isinstance(raw, str) else raw
|
|
255
|
+
return np.asarray(data, dtype=np.float64)
|
|
256
|
+
except (json.JSONDecodeError, TypeError, ValueError):
|
|
257
|
+
return None
|
|
File without changes
|