superlocalmemory 3.6.23 → 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 +271 -71
- package/bin/slm-npm +43 -89
- 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 +18 -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 +453 -191
- 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 +1 -1
- 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 +123 -35
- 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 +219 -32
- 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 +1 -1
- 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 +12 -12
- 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 +8 -63
- 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/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 -516
- 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
|
@@ -34,6 +34,10 @@ class PoolRecallItem:
|
|
|
34
34
|
fact: PoolFact = field(default_factory=PoolFact)
|
|
35
35
|
score: float = 0.0
|
|
36
36
|
confidence: float = 0.0
|
|
37
|
+
relevance_score: float = 0.0
|
|
38
|
+
ranking_score: float | None = None
|
|
39
|
+
memory_confidence: float = 0.0
|
|
40
|
+
rank_position: int = 0
|
|
37
41
|
trust_score: float = 0.0
|
|
38
42
|
channel_scores: dict[str, float] = field(default_factory=dict)
|
|
39
43
|
evidence_chain: list[Any] = field(default_factory=list)
|
|
@@ -46,6 +50,12 @@ class PoolRecallResponse:
|
|
|
46
50
|
retrieval_time_ms: float = 0.0
|
|
47
51
|
channel_weights: dict[str, float] = field(default_factory=dict)
|
|
48
52
|
total_candidates: int = 0
|
|
53
|
+
score_contract_version: str = "2"
|
|
54
|
+
calibration_status: str = "uncalibrated"
|
|
55
|
+
calibration_id: str | None = None
|
|
56
|
+
answer_confidence: float | None = None
|
|
57
|
+
abstained: bool = False
|
|
58
|
+
abstention_reason: str | None = None
|
|
49
59
|
|
|
50
60
|
|
|
51
61
|
class PoolError(RuntimeError):
|
|
@@ -107,6 +117,15 @@ def pool_recall(query: str, limit: int = 10, **kwargs: Any) -> PoolRecallRespons
|
|
|
107
117
|
),
|
|
108
118
|
score=float(item.get("score", 0.0)),
|
|
109
119
|
confidence=float(item.get("confidence", 0.0)),
|
|
120
|
+
relevance_score=float(item.get("relevance_score", item.get("score", 0.0))),
|
|
121
|
+
ranking_score=(
|
|
122
|
+
float(item["ranking_score"])
|
|
123
|
+
if item.get("ranking_score") is not None else None
|
|
124
|
+
),
|
|
125
|
+
memory_confidence=float(
|
|
126
|
+
item.get("memory_confidence", item.get("confidence", 0.0))
|
|
127
|
+
),
|
|
128
|
+
rank_position=int(item.get("rank_position", 0)),
|
|
110
129
|
trust_score=float(item.get("trust_score", 0.0)),
|
|
111
130
|
channel_scores=item.get("channel_scores", {}) or {},
|
|
112
131
|
evidence_chain=list(item.get("evidence_chain", []) or []),
|
|
@@ -122,6 +141,14 @@ def pool_recall(query: str, limit: int = 10, **kwargs: Any) -> PoolRecallRespons
|
|
|
122
141
|
if isinstance(raw, dict) else {},
|
|
123
142
|
total_candidates=int(raw.get("total_candidates", 0))
|
|
124
143
|
if isinstance(raw, dict) else 0,
|
|
144
|
+
score_contract_version=str(raw.get("score_contract_version", "2"))
|
|
145
|
+
if isinstance(raw, dict) else "2",
|
|
146
|
+
calibration_status=str(raw.get("calibration_status", "uncalibrated"))
|
|
147
|
+
if isinstance(raw, dict) else "uncalibrated",
|
|
148
|
+
calibration_id=raw.get("calibration_id") if isinstance(raw, dict) else None,
|
|
149
|
+
answer_confidence=raw.get("answer_confidence") if isinstance(raw, dict) else None,
|
|
150
|
+
abstained=bool(raw.get("abstained", False)) if isinstance(raw, dict) else False,
|
|
151
|
+
abstention_reason=raw.get("abstention_reason") if isinstance(raw, dict) else None,
|
|
125
152
|
)
|
|
126
153
|
|
|
127
154
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under AGPL-3.0-or-later - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V3 | https://qualixar.com | https://varunpratap.com
|
|
4
|
+
|
|
5
|
+
"""Resource-safe FastMCP Streamable-HTTP integration.
|
|
6
|
+
|
|
7
|
+
MCP SDK 1.27.1 passes an AnyIO ``MemoryObjectReceiveStream`` directly to
|
|
8
|
+
``EventSourceResponse`` for every JSON-RPC POST. The response consumes that
|
|
9
|
+
stream but does not close it after normal iteration, leaving one receive
|
|
10
|
+
endpoint per request for the garbage collector. The response is the owner of
|
|
11
|
+
that per-request iterator, so SLM closes it at the response boundary.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from mcp.server.fastmcp import FastMCP
|
|
17
|
+
from sse_starlette.sse import EventSourceResponse
|
|
18
|
+
from starlette.types import Receive, Scope, Send
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ClosingEventSourceResponse(EventSourceResponse):
|
|
22
|
+
"""EventSourceResponse that closes the async iterator it consumes."""
|
|
23
|
+
|
|
24
|
+
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
|
25
|
+
try:
|
|
26
|
+
await super().__call__(scope, receive, send)
|
|
27
|
+
finally:
|
|
28
|
+
close = getattr(self.body_iterator, "aclose", None)
|
|
29
|
+
if close is not None:
|
|
30
|
+
await close()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def install_streamable_http_resource_guard() -> None:
|
|
34
|
+
"""Install the response owner used by MCP's Streamable-HTTP transport."""
|
|
35
|
+
from mcp.server import streamable_http
|
|
36
|
+
|
|
37
|
+
streamable_http.EventSourceResponse = ClosingEventSourceResponse
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class SLMFastMCP(FastMCP):
|
|
41
|
+
"""FastMCP with SLM release identity and deterministic SSE cleanup."""
|
|
42
|
+
|
|
43
|
+
def __init__(self, *args, product_version: str = "3.7.0", **kwargs) -> None:
|
|
44
|
+
super().__init__(*args, **kwargs)
|
|
45
|
+
# FastMCP delegates the initialize response to the low-level MCP
|
|
46
|
+
# server. Without an explicit value it reports the installed ``mcp``
|
|
47
|
+
# library version, which makes an SLM 3.7 server identify itself as
|
|
48
|
+
# (for example) 1.27.1 to every IDE client.
|
|
49
|
+
self._mcp_server.version = product_version
|
|
50
|
+
|
|
51
|
+
def streamable_http_app(self):
|
|
52
|
+
install_streamable_http_resource_guard()
|
|
53
|
+
return super().streamable_http_app()
|
|
@@ -27,11 +27,11 @@ _os.environ.setdefault('SLM_SKIP_DEP_CHECK', '1')
|
|
|
27
27
|
import logging
|
|
28
28
|
import sys
|
|
29
29
|
|
|
30
|
-
from mcp.
|
|
30
|
+
from superlocalmemory.mcp.http_transport import SLMFastMCP
|
|
31
31
|
|
|
32
32
|
logger = logging.getLogger(__name__)
|
|
33
33
|
|
|
34
|
-
server =
|
|
34
|
+
server = SLMFastMCP("SuperLocalMemory V3")
|
|
35
35
|
|
|
36
36
|
# Lazy engine singleton -------------------------------------------------------
|
|
37
37
|
|
|
@@ -175,6 +175,18 @@ _PROFILE_DEFINITIONS: dict[str, frozenset[str]] = {
|
|
|
175
175
|
"mesh": _PROFILE_MESH,
|
|
176
176
|
} # "whole" intentionally absent — maps to raw server (D-2 LOCKED)
|
|
177
177
|
|
|
178
|
+
# Compatibility aliases published by the v3.6 README. Keep these explicit so
|
|
179
|
+
# a stale client configuration has one deterministic meaning and emits a
|
|
180
|
+
# migration warning. Any other value is a configuration error (fail closed).
|
|
181
|
+
_PROFILE_ALIASES: dict[str, str] = {
|
|
182
|
+
"core14": "core",
|
|
183
|
+
"code20": "code",
|
|
184
|
+
"full38": "full",
|
|
185
|
+
"power50": "power",
|
|
186
|
+
"mesh8": "mesh",
|
|
187
|
+
"whole81": "whole",
|
|
188
|
+
}
|
|
189
|
+
|
|
178
190
|
_profile = _os_reg.environ.get("SLM_MCP_PROFILE", "").strip().lower()
|
|
179
191
|
|
|
180
192
|
|
|
@@ -188,19 +200,26 @@ def _resolve_profile_allowed(
|
|
|
188
200
|
Returns:
|
|
189
201
|
None — for "" (no selection) or "whole" (raw server, all tools).
|
|
190
202
|
frozenset — the profile's tool set for known profiles.
|
|
191
|
-
|
|
203
|
+
|
|
204
|
+
Raises:
|
|
205
|
+
ValueError: if ``profile`` is neither canonical nor an explicitly
|
|
206
|
+
supported compatibility alias.
|
|
192
207
|
"""
|
|
193
|
-
|
|
208
|
+
canonical = _PROFILE_ALIASES.get(profile, profile)
|
|
209
|
+
if canonical != profile:
|
|
210
|
+
logger.warning(
|
|
211
|
+
"SLM_MCP_PROFILE=%r is deprecated; use %r instead.",
|
|
212
|
+
profile,
|
|
213
|
+
canonical,
|
|
214
|
+
)
|
|
215
|
+
if not canonical or canonical == "whole":
|
|
194
216
|
return None
|
|
195
|
-
if
|
|
196
|
-
return definitions[
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
"
|
|
200
|
-
profile,
|
|
201
|
-
", ".join(sorted(definitions)),
|
|
217
|
+
if canonical in definitions:
|
|
218
|
+
return definitions[canonical]
|
|
219
|
+
valid = ", ".join((*sorted(definitions), "whole"))
|
|
220
|
+
raise ValueError(
|
|
221
|
+
f"SLM_MCP_PROFILE={profile!r} is not recognised; valid profiles: {valid}"
|
|
202
222
|
)
|
|
203
|
-
return essential
|
|
204
223
|
|
|
205
224
|
|
|
206
225
|
class _FilteredServer:
|
|
@@ -237,7 +256,14 @@ elif _user_allowlist_str:
|
|
|
237
256
|
elif _profile == "whole":
|
|
238
257
|
_target = server # NEW raw-server
|
|
239
258
|
elif _profile:
|
|
240
|
-
|
|
259
|
+
_profile_allowed = _resolve_profile_allowed(
|
|
260
|
+
_profile, _PROFILE_DEFINITIONS, _ESSENTIAL_TOOLS
|
|
261
|
+
)
|
|
262
|
+
_target = (
|
|
263
|
+
server
|
|
264
|
+
if _profile_allowed is None
|
|
265
|
+
else _FilteredServer(server, _profile_allowed)
|
|
266
|
+
)
|
|
241
267
|
else:
|
|
242
268
|
_target = _FilteredServer(server, _ESSENTIAL_TOOLS) # default (unchanged)
|
|
243
269
|
|
|
@@ -10,9 +10,10 @@ V3.3.12: Extracted _emit_event to eliminate code duplication.
|
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
|
-
from
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from typing import Any
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
from superlocalmemory.infra.data_root import state_path
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
def emit_event(event_type: str, payload: dict | None = None,
|
|
@@ -20,8 +21,73 @@ def emit_event(event_type: str, payload: dict | None = None,
|
|
|
20
21
|
"""Emit an event to the EventBus (best-effort, never raises)."""
|
|
21
22
|
try:
|
|
22
23
|
from superlocalmemory.infra.event_bus import EventBus
|
|
23
|
-
bus = EventBus.get_instance(
|
|
24
|
+
bus = EventBus.get_instance(state_path("memory.db"))
|
|
24
25
|
bus.emit(event_type, payload=payload, source_agent=source_agent,
|
|
25
26
|
source_protocol="mcp")
|
|
26
27
|
except Exception:
|
|
27
28
|
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class MCPMutationAuthorization:
|
|
33
|
+
"""One capability-owned MCP mutation authorized by engine policy."""
|
|
34
|
+
|
|
35
|
+
engine: Any
|
|
36
|
+
operation: str
|
|
37
|
+
context: dict[str, str]
|
|
38
|
+
_completed: bool = False
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def actor_id(self) -> str:
|
|
42
|
+
return self.context["agent_id"]
|
|
43
|
+
|
|
44
|
+
def complete(self) -> None:
|
|
45
|
+
"""Emit the matching post hook once the mutation succeeds."""
|
|
46
|
+
if self._completed:
|
|
47
|
+
return
|
|
48
|
+
self.engine._hooks.run_post(self.operation, self.context)
|
|
49
|
+
self._completed = True
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def authorize_mcp_mutation(
|
|
53
|
+
engine: Any,
|
|
54
|
+
operation: str,
|
|
55
|
+
*,
|
|
56
|
+
mutation_source: str,
|
|
57
|
+
profile_id: str | None = None,
|
|
58
|
+
fact_id: str = "",
|
|
59
|
+
content_preview: str = "",
|
|
60
|
+
) -> MCPMutationAuthorization:
|
|
61
|
+
"""Derive local authority and run policy before a direct MCP write.
|
|
62
|
+
|
|
63
|
+
MCP URL/env agent identifiers remain audit attribution only. The trusted
|
|
64
|
+
principal is derived from the private local install capability, matching
|
|
65
|
+
the canonical ingestion and recall-worker mutation boundaries.
|
|
66
|
+
"""
|
|
67
|
+
if operation not in {"update", "delete"}:
|
|
68
|
+
raise ValueError("MCP mutations must use update or delete policy")
|
|
69
|
+
|
|
70
|
+
from superlocalmemory.core.engine_ingestion import local_trusted_actor_id
|
|
71
|
+
from superlocalmemory.mcp.agent_context import get_current_agent_id
|
|
72
|
+
|
|
73
|
+
source_agent_id = get_current_agent_id(env_fallback=True)
|
|
74
|
+
context = {
|
|
75
|
+
"operation": operation,
|
|
76
|
+
"agent_id": local_trusted_actor_id("mcp"),
|
|
77
|
+
"source_agent_id": source_agent_id,
|
|
78
|
+
"mutation_source": mutation_source,
|
|
79
|
+
"profile_id": profile_id or engine.profile_id,
|
|
80
|
+
}
|
|
81
|
+
if fact_id:
|
|
82
|
+
context["fact_id"] = fact_id
|
|
83
|
+
if content_preview:
|
|
84
|
+
context["content_preview"] = content_preview[:100]
|
|
85
|
+
engine._hooks.run_pre(operation, context)
|
|
86
|
+
return MCPMutationAuthorization(engine, operation, context)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
__all__ = [
|
|
90
|
+
"MCPMutationAuthorization",
|
|
91
|
+
"authorize_mcp_mutation",
|
|
92
|
+
"emit_event",
|
|
93
|
+
]
|
|
@@ -19,16 +19,17 @@ from __future__ import annotations
|
|
|
19
19
|
import asyncio
|
|
20
20
|
import datetime
|
|
21
21
|
import logging
|
|
22
|
-
import os
|
|
23
22
|
import sqlite3
|
|
24
23
|
import uuid
|
|
25
|
-
from
|
|
26
|
-
from typing import Callable
|
|
24
|
+
from typing import TYPE_CHECKING, Callable
|
|
27
25
|
|
|
28
|
-
|
|
26
|
+
from superlocalmemory.infra.data_root import canonical_data_root, state_path
|
|
27
|
+
from superlocalmemory.mcp.shared import authorize_mcp_mutation
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from superlocalmemory.mcp._pool_adapter import PoolRecallResponse
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
DB_PATH = MEMORY_DIR / "memory.db"
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
def _sqlite_emergency_recall(
|
|
@@ -48,8 +49,9 @@ def _sqlite_emergency_recall(
|
|
|
48
49
|
Used ONLY when Tier-1 (full daemon recall) fails completely. Normal
|
|
49
50
|
path is full 6-channel; this is the fire-alarm.
|
|
50
51
|
"""
|
|
51
|
-
from superlocalmemory.mcp._pool_adapter import PoolFact, PoolRecallItem, PoolRecallResponse
|
|
52
52
|
import re
|
|
53
|
+
|
|
54
|
+
from superlocalmemory.mcp._pool_adapter import PoolFact, PoolRecallItem, PoolRecallResponse
|
|
53
55
|
try:
|
|
54
56
|
# FTS5 MATCH syntax: tokenize the query, drop special characters
|
|
55
57
|
# that confuse the parser (/, :, ., etc), and join with OR for
|
|
@@ -64,7 +66,7 @@ def _sqlite_emergency_recall(
|
|
|
64
66
|
f"AND f.created_at >= datetime('now', '-{int(max_age_days)} days') "
|
|
65
67
|
if max_age_days > 0 else ""
|
|
66
68
|
)
|
|
67
|
-
conn = sqlite3.connect(str(
|
|
69
|
+
conn = sqlite3.connect(str(state_path("memory.db")), timeout=5.0)
|
|
68
70
|
try:
|
|
69
71
|
rows = conn.execute(
|
|
70
72
|
f"""SELECT f.fact_id, f.content, f.memory_id, f.created_at,
|
|
@@ -129,24 +131,26 @@ def _emit_event(event_type: str, payload: dict | None = None,
|
|
|
129
131
|
resolved_agent = source_agent if source_agent is not None else _get_agent_id()
|
|
130
132
|
try:
|
|
131
133
|
from superlocalmemory.infra.event_bus import EventBus
|
|
132
|
-
bus = EventBus.get_instance(str(
|
|
134
|
+
bus = EventBus.get_instance(str(state_path("memory.db")))
|
|
133
135
|
bus.emit(event_type, payload=payload, source_agent=resolved_agent,
|
|
134
136
|
source_protocol="mcp")
|
|
135
137
|
except Exception as exc:
|
|
136
138
|
logger.warning("event emit failed: type=%s err=%s", event_type, exc)
|
|
137
139
|
|
|
138
140
|
|
|
139
|
-
def _register_agent(agent_id: str, profile_id: str) ->
|
|
141
|
+
def _register_agent(agent_id: str, profile_id: str) -> bool:
|
|
140
142
|
"""Register an agent in the AgentRegistry (best-effort)."""
|
|
141
143
|
try:
|
|
142
144
|
from superlocalmemory.core.registry import AgentRegistry
|
|
143
|
-
registry_path =
|
|
145
|
+
registry_path = canonical_data_root() / "agents.json"
|
|
144
146
|
registry = AgentRegistry(persist_path=registry_path)
|
|
145
147
|
registry.register_agent(agent_id, profile_id)
|
|
148
|
+
return True
|
|
146
149
|
except Exception as exc:
|
|
147
150
|
logger.warning(
|
|
148
151
|
"agent registry write failed: agent=%s err=%s", agent_id, exc,
|
|
149
152
|
)
|
|
153
|
+
return False
|
|
150
154
|
|
|
151
155
|
|
|
152
156
|
def register_active_tools(server, get_engine: Callable) -> None:
|
|
@@ -194,7 +198,12 @@ def register_active_tools(server, get_engine: Callable) -> None:
|
|
|
194
198
|
rules = RulesEngine()
|
|
195
199
|
|
|
196
200
|
if not rules.should_recall("session_start"):
|
|
197
|
-
return {
|
|
201
|
+
return {
|
|
202
|
+
"success": True,
|
|
203
|
+
"context": "",
|
|
204
|
+
"memories": [],
|
|
205
|
+
"message": "Auto-recall disabled",
|
|
206
|
+
}
|
|
198
207
|
|
|
199
208
|
recall_config = rules.get_recall_config()
|
|
200
209
|
relevance_threshold = recall_config.get("relevance_threshold", 0.3)
|
|
@@ -239,7 +248,8 @@ def register_active_tools(server, get_engine: Callable) -> None:
|
|
|
239
248
|
# Memories older than max_age_days are excluded unless their score
|
|
240
249
|
# exceeds 0.7 (high-relevance architectural decisions always surface).
|
|
241
250
|
# max_age_days=0 disables the gate entirely.
|
|
242
|
-
from datetime import UTC
|
|
251
|
+
from datetime import UTC
|
|
252
|
+
from datetime import datetime as _dt
|
|
243
253
|
_now = _dt.now(UTC)
|
|
244
254
|
|
|
245
255
|
def _age_days(created_at_str: str) -> float:
|
|
@@ -272,6 +282,7 @@ def register_active_tools(server, get_engine: Callable) -> None:
|
|
|
272
282
|
clamp_content,
|
|
273
283
|
is_low_quality,
|
|
274
284
|
render_context,
|
|
285
|
+
sanitize_untrusted_content,
|
|
275
286
|
)
|
|
276
287
|
|
|
277
288
|
pid = engine.profile_id
|
|
@@ -282,7 +293,6 @@ def register_active_tools(server, get_engine: Callable) -> None:
|
|
|
282
293
|
pinned_facts = engine.db.get_pinned(pid)
|
|
283
294
|
except Exception:
|
|
284
295
|
pinned_facts = []
|
|
285
|
-
pinned_ids = {f.fact_id for f in pinned_facts}
|
|
286
296
|
pinned_seen = set()
|
|
287
297
|
|
|
288
298
|
cfg_inj = getattr(getattr(engine, "config", None), "injection", None)
|
|
@@ -304,6 +314,7 @@ def register_active_tools(server, get_engine: Callable) -> None:
|
|
|
304
314
|
importance=getattr(pf, "importance", 0.0) or 0.0,
|
|
305
315
|
access_count=getattr(pf, "access_count", 0) or 0,
|
|
306
316
|
pinned=True,
|
|
317
|
+
source_type="pinned-fact",
|
|
307
318
|
))
|
|
308
319
|
pinned_seen.add(pf.fact_id)
|
|
309
320
|
|
|
@@ -317,17 +328,18 @@ def register_active_tools(server, get_engine: Callable) -> None:
|
|
|
317
328
|
fact_id=r.fact.fact_id,
|
|
318
329
|
importance=getattr(r.fact, "importance", 0.0) or 0.0,
|
|
319
330
|
access_count=getattr(r.fact, "access_count", 0) or 0,
|
|
331
|
+
source_type="recall",
|
|
320
332
|
))
|
|
321
333
|
|
|
322
334
|
mode_str = str(getattr(engine, "mode", "B")).upper()
|
|
323
335
|
try:
|
|
324
|
-
context = render_context(
|
|
336
|
+
context = render_context(
|
|
337
|
+
inj_mems, mode=mode_str, cfg=cfg_inj, wrap=True,
|
|
338
|
+
)
|
|
325
339
|
except Exception:
|
|
326
|
-
#
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
lines.append(f"- {m.content[:200]}")
|
|
330
|
-
context = "\n".join(lines)
|
|
340
|
+
# Fail closed: never serialize retrieved content through a
|
|
341
|
+
# weaker ad-hoc path when the mandatory renderer fails.
|
|
342
|
+
context = ""
|
|
331
343
|
|
|
332
344
|
# GAP-FIX (v3.4.65 delivery-lead): the memories[] array is part of
|
|
333
345
|
# the MCP response Claude Code ingests — it MUST be bounded too, not
|
|
@@ -335,16 +347,43 @@ def register_active_tools(server, get_engine: Callable) -> None:
|
|
|
335
347
|
# content shipped here (one fact was 131K chars → ~124K-token
|
|
336
348
|
# response, defeating the whole token budget). Clamp each content
|
|
337
349
|
# to per_memory_max_tokens, drop junk, and honour max_results.
|
|
338
|
-
|
|
339
|
-
|
|
350
|
+
contract_by_id = {
|
|
351
|
+
r.fact.fact_id: r for r in relevant[:max_results]
|
|
352
|
+
}
|
|
353
|
+
memories = []
|
|
354
|
+
for position, m in enumerate(inj_mems[:max_results], start=1):
|
|
355
|
+
if is_low_quality(m.content):
|
|
356
|
+
continue
|
|
357
|
+
result_contract = contract_by_id.get(m.fact_id)
|
|
358
|
+
relevance = (
|
|
359
|
+
getattr(result_contract, "relevance_score", m.score)
|
|
360
|
+
if result_contract is not None else m.score
|
|
361
|
+
)
|
|
362
|
+
memory_confidence = (
|
|
363
|
+
getattr(result_contract, "memory_confidence", None)
|
|
364
|
+
if result_contract is not None else None
|
|
365
|
+
)
|
|
366
|
+
memories.append({
|
|
340
367
|
"fact_id": m.fact_id,
|
|
341
|
-
"content": clamp_content(
|
|
368
|
+
"content": clamp_content(
|
|
369
|
+
sanitize_untrusted_content(m.content), cfg_inj,
|
|
370
|
+
),
|
|
342
371
|
"score": m.score,
|
|
372
|
+
"relevance_score": relevance,
|
|
373
|
+
"ranking_score": (
|
|
374
|
+
getattr(result_contract, "ranking_score", None)
|
|
375
|
+
if result_contract is not None else None
|
|
376
|
+
),
|
|
377
|
+
"confidence": memory_confidence,
|
|
378
|
+
"memory_confidence": memory_confidence,
|
|
379
|
+
"rank_position": (
|
|
380
|
+
getattr(result_contract, "rank_position", position)
|
|
381
|
+
if result_contract is not None else position
|
|
382
|
+
),
|
|
343
383
|
"is_core": m.is_core,
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
]
|
|
384
|
+
"untrusted": True,
|
|
385
|
+
"source_type": m.source_type,
|
|
386
|
+
})
|
|
348
387
|
|
|
349
388
|
# Get learning status
|
|
350
389
|
feedback_count = 0
|
|
@@ -366,7 +405,21 @@ def register_active_tools(server, get_engine: Callable) -> None:
|
|
|
366
405
|
|
|
367
406
|
# Register agent + emit event (v3.4.39: SLM_AGENT_ID env support)
|
|
368
407
|
agent_id = _get_agent_id()
|
|
369
|
-
|
|
408
|
+
if hasattr(engine, "_hooks"):
|
|
409
|
+
registration_auth = authorize_mcp_mutation(
|
|
410
|
+
engine,
|
|
411
|
+
"update",
|
|
412
|
+
mutation_source="mcp-agent-registration",
|
|
413
|
+
profile_id=pid,
|
|
414
|
+
)
|
|
415
|
+
if _register_agent(agent_id, pid):
|
|
416
|
+
registration_auth.complete()
|
|
417
|
+
else:
|
|
418
|
+
# A LIGHT client without the policy registry is read-capable,
|
|
419
|
+
# but must not fall back to an unauthorised registry write.
|
|
420
|
+
logger.info(
|
|
421
|
+
"agent registration skipped: policy hooks unavailable"
|
|
422
|
+
)
|
|
370
423
|
_emit_event("agent.connected", {
|
|
371
424
|
"agent_id": agent_id,
|
|
372
425
|
"project_path": project_path,
|
|
@@ -381,11 +434,29 @@ def register_active_tools(server, get_engine: Callable) -> None:
|
|
|
381
434
|
"memory_count": len(memories),
|
|
382
435
|
"core_memory": [m["content"] for m in memories if m.get("is_core")],
|
|
383
436
|
"degraded_mode": degraded_mode,
|
|
384
|
-
"retrieval_mode":
|
|
437
|
+
"retrieval_mode": (
|
|
438
|
+
"emergency_fts5_bm25"
|
|
439
|
+
if degraded_mode
|
|
440
|
+
else "hybrid_candidate_fusion"
|
|
441
|
+
),
|
|
442
|
+
"score_contract_version": getattr(
|
|
443
|
+
response, "score_contract_version", "2"
|
|
444
|
+
),
|
|
445
|
+
"calibration_status": getattr(
|
|
446
|
+
response, "calibration_status", "uncalibrated"
|
|
447
|
+
),
|
|
448
|
+
"calibration_id": getattr(response, "calibration_id", None),
|
|
449
|
+
"answer_confidence": getattr(response, "answer_confidence", None),
|
|
450
|
+
"abstained": getattr(response, "abstained", not bool(relevant)),
|
|
451
|
+
"abstention_reason": getattr(response, "abstention_reason", None),
|
|
385
452
|
"learning": {
|
|
386
453
|
"feedback_signals": feedback_count,
|
|
387
454
|
"phase": 1 if feedback_count < 50 else (2 if feedback_count < 200 else 3),
|
|
388
|
-
"status":
|
|
455
|
+
"status": (
|
|
456
|
+
"collecting"
|
|
457
|
+
if feedback_count < 50
|
|
458
|
+
else "learning" if feedback_count < 200 else "trained"
|
|
459
|
+
),
|
|
389
460
|
},
|
|
390
461
|
}
|
|
391
462
|
except Exception as exc:
|
|
@@ -496,8 +567,22 @@ def register_active_tools(server, get_engine: Callable) -> None:
|
|
|
496
567
|
pid = engine.profile_id
|
|
497
568
|
|
|
498
569
|
if feedback not in ("relevant", "irrelevant", "partial"):
|
|
499
|
-
return {
|
|
570
|
+
return {
|
|
571
|
+
"success": False,
|
|
572
|
+
"error": (
|
|
573
|
+
f"Invalid feedback: {feedback}. "
|
|
574
|
+
"Use relevant/irrelevant/partial"
|
|
575
|
+
),
|
|
576
|
+
}
|
|
500
577
|
|
|
578
|
+
authorization = authorize_mcp_mutation(
|
|
579
|
+
engine,
|
|
580
|
+
"update",
|
|
581
|
+
mutation_source="mcp-recall-feedback",
|
|
582
|
+
profile_id=pid,
|
|
583
|
+
fact_id=fact_id,
|
|
584
|
+
content_preview=feedback,
|
|
585
|
+
)
|
|
501
586
|
record = engine._adaptive_learner.record_feedback(
|
|
502
587
|
query=query,
|
|
503
588
|
fact_id=fact_id,
|
|
@@ -506,6 +591,7 @@ def register_active_tools(server, get_engine: Callable) -> None:
|
|
|
506
591
|
)
|
|
507
592
|
|
|
508
593
|
count = engine._adaptive_learner.get_feedback_count(pid)
|
|
594
|
+
authorization.complete()
|
|
509
595
|
|
|
510
596
|
_emit_event("pattern.learned", {
|
|
511
597
|
"fact_id": fact_id,
|
|
@@ -562,7 +648,15 @@ def register_active_tools(server, get_engine: Callable) -> None:
|
|
|
562
648
|
pass
|
|
563
649
|
if not sid:
|
|
564
650
|
return {"success": False, "error": "No session_id provided or found"}
|
|
651
|
+
authorization = authorize_mcp_mutation(
|
|
652
|
+
engine,
|
|
653
|
+
"update",
|
|
654
|
+
mutation_source="mcp-session-close",
|
|
655
|
+
profile_id=engine.profile_id,
|
|
656
|
+
content_preview=sid,
|
|
657
|
+
)
|
|
565
658
|
count = engine.close_session(sid)
|
|
659
|
+
authorization.complete()
|
|
566
660
|
return {
|
|
567
661
|
"success": True,
|
|
568
662
|
"session_id": sid,
|
|
@@ -596,13 +690,29 @@ def register_active_tools(server, get_engine: Callable) -> None:
|
|
|
596
690
|
if action == "pin":
|
|
597
691
|
if not fact_id:
|
|
598
692
|
return {"success": False, "error": "fact_id required for pin"}
|
|
693
|
+
authorization = authorize_mcp_mutation(
|
|
694
|
+
engine,
|
|
695
|
+
"update",
|
|
696
|
+
mutation_source="mcp-core-memory-pin",
|
|
697
|
+
profile_id=pid,
|
|
698
|
+
fact_id=fact_id,
|
|
699
|
+
)
|
|
599
700
|
db.set_pinned(fact_id, True)
|
|
701
|
+
authorization.complete()
|
|
600
702
|
return {"success": True, "action": "pin", "fact_id": fact_id}
|
|
601
703
|
|
|
602
704
|
if action == "unpin":
|
|
603
705
|
if not fact_id:
|
|
604
706
|
return {"success": False, "error": "fact_id required for unpin"}
|
|
707
|
+
authorization = authorize_mcp_mutation(
|
|
708
|
+
engine,
|
|
709
|
+
"update",
|
|
710
|
+
mutation_source="mcp-core-memory-unpin",
|
|
711
|
+
profile_id=pid,
|
|
712
|
+
fact_id=fact_id,
|
|
713
|
+
)
|
|
605
714
|
db.set_pinned(fact_id, False)
|
|
715
|
+
authorization.complete()
|
|
606
716
|
return {"success": True, "action": "unpin", "fact_id": fact_id}
|
|
607
717
|
|
|
608
718
|
if action == "list":
|