superlocalmemory 3.6.22 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/README.md +275 -72
- package/bin/slm-npm +43 -89
- package/docs/pi-dev-integration.md +43 -0
- package/ide/configs/antigravity-mcp.json +2 -2
- package/ide/configs/chatgpt-desktop-mcp.json +1 -1
- package/ide/configs/claude-desktop-mcp.json +2 -2
- package/ide/configs/windsurf-mcp.json +2 -2
- package/ide/hooks/context-hook.js +6 -2
- package/ide/hooks/post-recall-hook.js +7 -3
- package/ide/hooks/tool-event-hook.sh +2 -1
- package/package.json +19 -10
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/_GENERATED.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin-src/rules/AGENTS.md +1 -1
- package/pyproject.toml +40 -8
- package/scripts/postinstall-interactive.js +17 -94
- package/scripts/postinstall.js +185 -258
- package/scripts/preuninstall.js +9 -50
- package/src/superlocalmemory/__init__.py +2 -2
- package/src/superlocalmemory/attribution/mathematical_dna.py +1 -1
- package/src/superlocalmemory/attribution/signer.py +34 -19
- package/src/superlocalmemory/attribution/watermark.py +1 -1
- package/src/superlocalmemory/cli/_lazy_init.py +3 -5
- package/src/superlocalmemory/cli/commands.py +490 -195
- package/src/superlocalmemory/cli/context_commands.py +5 -4
- package/src/superlocalmemory/cli/daemon.py +282 -187
- package/src/superlocalmemory/cli/db_migrate.py +3 -1
- package/src/superlocalmemory/cli/diagnostics_cmd.py +28 -0
- package/src/superlocalmemory/cli/evidence_cmd.py +103 -0
- package/src/superlocalmemory/cli/ingest_cmd.py +7 -3
- package/src/superlocalmemory/cli/main.py +128 -31
- package/src/superlocalmemory/cli/pending_store.py +54 -38
- package/src/superlocalmemory/cli/scale_engine_cmd.py +37 -0
- package/src/superlocalmemory/cli/service_installer.py +57 -52
- package/src/superlocalmemory/cli/setup_wizard.py +142 -88
- package/src/superlocalmemory/cli/version_banner.py +2 -1
- package/src/superlocalmemory/code_graph/config.py +3 -1
- package/src/superlocalmemory/core/backend_orchestrator.py +81 -21
- package/src/superlocalmemory/core/config.py +65 -20
- package/src/superlocalmemory/core/consolidation_engine.py +9 -7
- package/src/superlocalmemory/core/context_cache.py +56 -8
- package/src/superlocalmemory/core/derivation_lineage.py +246 -0
- package/src/superlocalmemory/core/embedding_worker.py +32 -20
- package/src/superlocalmemory/core/embeddings.py +54 -18
- package/src/superlocalmemory/core/engine.py +150 -104
- package/src/superlocalmemory/core/engine_ingestion.py +513 -0
- package/src/superlocalmemory/core/engine_wiring.py +2 -0
- package/src/superlocalmemory/core/evidence_bundle.py +526 -0
- package/src/superlocalmemory/core/fact_consolidator.py +5 -11
- package/src/superlocalmemory/core/graph_analyzer.py +2 -2
- package/src/superlocalmemory/core/health_monitor.py +4 -2
- package/src/superlocalmemory/core/ingestion_command.py +636 -0
- package/src/superlocalmemory/core/injection.py +69 -18
- package/src/superlocalmemory/core/lifecycle_state.py +153 -0
- package/src/superlocalmemory/core/maintenance.py +23 -22
- package/src/superlocalmemory/core/maintenance_scheduler.py +51 -35
- package/src/superlocalmemory/core/mutations.py +143 -0
- package/src/superlocalmemory/core/platform_utils.py +7 -4
- package/src/superlocalmemory/core/ram_lock.py +16 -5
- package/src/superlocalmemory/core/rate_limit.py +1 -1
- package/src/superlocalmemory/core/recall_pipeline.py +60 -101
- package/src/superlocalmemory/core/recall_worker.py +76 -59
- package/src/superlocalmemory/core/registry.py +1 -1
- package/src/superlocalmemory/core/scale_engine.py +293 -0
- package/src/superlocalmemory/core/score_contract.py +62 -0
- package/src/superlocalmemory/core/security_primitives.py +3 -1
- package/src/superlocalmemory/core/slm_disabled.py +3 -5
- package/src/superlocalmemory/core/store_pipeline.py +172 -40
- package/src/superlocalmemory/core/tier_manager.py +32 -20
- package/src/superlocalmemory/core/worker_pool.py +13 -4
- package/src/superlocalmemory/dynamics/activation_guided_quantization.py +1 -1
- package/src/superlocalmemory/dynamics/eap_scheduler.py +10 -3
- package/src/superlocalmemory/dynamics/ebbinghaus_langevin_coupling.py +1 -1
- package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +1 -1
- package/src/superlocalmemory/encoding/auto_linker.py +1 -1
- package/src/superlocalmemory/encoding/cognitive_consolidator.py +7 -16
- package/src/superlocalmemory/encoding/consolidator.py +22 -5
- package/src/superlocalmemory/encoding/fact_extractor.py +1 -1
- package/src/superlocalmemory/encoding/foresight.py +2 -0
- package/src/superlocalmemory/encoding/graph_builder.py +1 -1
- package/src/superlocalmemory/encoding/temporal_parser.py +2 -0
- package/src/superlocalmemory/evaluation/__init__.py +13 -0
- package/src/superlocalmemory/evaluation/calibration.py +308 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +2 -1
- package/src/superlocalmemory/graph/cozo_backend.py +256 -23
- package/src/superlocalmemory/hooks/_outcome_common.py +21 -11
- package/src/superlocalmemory/hooks/antigravity_adapter.py +10 -31
- package/src/superlocalmemory/hooks/auto_invoker.py +25 -27
- package/src/superlocalmemory/hooks/auto_recall.py +31 -6
- package/src/superlocalmemory/hooks/auto_recall_hook.py +13 -33
- package/src/superlocalmemory/hooks/before_web_hook.py +9 -7
- package/src/superlocalmemory/hooks/claude_code_hooks.py +126 -39
- package/src/superlocalmemory/hooks/codex_assets.py +59 -0
- package/src/superlocalmemory/hooks/codex_hooks.py +186 -0
- package/src/superlocalmemory/hooks/context_payload.py +1 -1
- package/src/superlocalmemory/hooks/copilot_adapter.py +9 -24
- package/src/superlocalmemory/hooks/cursor_adapter.py +10 -32
- package/src/superlocalmemory/hooks/hook_daemon.py +4 -2
- package/src/superlocalmemory/hooks/hook_handlers.py +241 -55
- package/src/superlocalmemory/hooks/memory_protocol.py +5 -3
- package/src/superlocalmemory/hooks/post_tool_async_hook.py +4 -2
- package/src/superlocalmemory/hooks/session_registry.py +15 -8
- package/src/superlocalmemory/hooks/stop_outcome_hook.py +10 -6
- package/src/superlocalmemory/hooks/topic_shift_hook.py +42 -12
- package/src/superlocalmemory/hooks/user_prompt_hook.py +9 -14
- package/src/superlocalmemory/hooks/user_prompt_rehash_hook.py +19 -11
- package/src/superlocalmemory/infra/auth_middleware.py +38 -5
- package/src/superlocalmemory/infra/backup.py +7 -5
- package/src/superlocalmemory/infra/cloud_backup.py +18 -8
- package/src/superlocalmemory/infra/daemon_identity.py +248 -0
- package/src/superlocalmemory/infra/data_root.py +199 -0
- package/src/superlocalmemory/infra/event_bus.py +3 -1
- package/src/superlocalmemory/infra/local_diagnostics.py +327 -0
- package/src/superlocalmemory/infra/process_reaper.py +23 -0
- package/src/superlocalmemory/ingestion/adapter_manager.py +27 -9
- package/src/superlocalmemory/ingestion/base_adapter.py +25 -31
- package/src/superlocalmemory/ingestion/calendar_adapter.py +13 -4
- package/src/superlocalmemory/ingestion/credentials.py +14 -7
- package/src/superlocalmemory/ingestion/gmail_adapter.py +13 -4
- package/src/superlocalmemory/ingestion/transcript_adapter.py +7 -2
- package/src/superlocalmemory/learning/consolidation_quantization_worker.py +1 -1
- package/src/superlocalmemory/learning/ensemble.py +11 -0
- package/src/superlocalmemory/learning/entity_compiler.py +1 -1
- package/src/superlocalmemory/learning/feedback.py +1 -1
- package/src/superlocalmemory/learning/forgetting_scheduler.py +12 -7
- package/src/superlocalmemory/learning/quantization_scheduler.py +1 -1
- package/src/superlocalmemory/learning/ranker.py +4 -1
- package/src/superlocalmemory/learning/source_quality.py +1 -1
- package/src/superlocalmemory/learning/trigram_index.py +3 -2
- package/src/superlocalmemory/llm/backbone.py +13 -8
- package/src/superlocalmemory/math/ebbinghaus.py +1 -1
- package/src/superlocalmemory/math/fisher.py +1 -1
- package/src/superlocalmemory/math/fisher_quantized.py +1 -1
- package/src/superlocalmemory/math/hopfield.py +1 -1
- package/src/superlocalmemory/math/langevin.py +1 -1
- package/src/superlocalmemory/math/polar_quant.py +3 -4
- package/src/superlocalmemory/math/qjl.py +1 -1
- package/src/superlocalmemory/math/sheaf.py +1 -1
- package/src/superlocalmemory/math/turbo_quant.py +3 -2
- package/src/superlocalmemory/mcp/_daemon_proxy.py +12 -11
- package/src/superlocalmemory/mcp/_pool_adapter.py +27 -0
- package/src/superlocalmemory/mcp/http_transport.py +53 -0
- package/src/superlocalmemory/mcp/server.py +39 -13
- package/src/superlocalmemory/mcp/shared.py +69 -3
- package/src/superlocalmemory/mcp/tools_active.py +141 -31
- package/src/superlocalmemory/mcp/tools_core.py +128 -29
- package/src/superlocalmemory/mcp/tools_evolution.py +5 -7
- package/src/superlocalmemory/mcp/tools_learning.py +42 -2
- package/src/superlocalmemory/mcp/tools_mesh.py +7 -23
- package/src/superlocalmemory/mcp/tools_optimize.py +8 -1
- package/src/superlocalmemory/mcp/tools_v28.py +23 -2
- package/src/superlocalmemory/mcp/tools_v3.py +26 -1
- package/src/superlocalmemory/mcp/tools_v33.py +56 -17
- package/src/superlocalmemory/mesh/broker.py +2 -0
- package/src/superlocalmemory/mesh/remote_sync.py +50 -12
- package/src/superlocalmemory/optimize/cache/manager.py +77 -1
- package/src/superlocalmemory/optimize/cache/semantic.py +23 -3
- package/src/superlocalmemory/optimize/compress/ccr.py +4 -0
- package/src/superlocalmemory/optimize/compress/router.py +6 -1
- package/src/superlocalmemory/optimize/config/__init__.py +5 -0
- package/src/superlocalmemory/optimize/config/store.py +6 -4
- package/src/superlocalmemory/optimize/proxy/_helpers.py +15 -5
- package/src/superlocalmemory/optimize/proxy/capture.py +3 -2
- package/src/superlocalmemory/optimize/proxy/server.py +2 -2
- package/src/superlocalmemory/optimize/storage/db.py +14 -13
- package/src/superlocalmemory/retrieval/agentic.py +1 -1
- package/src/superlocalmemory/retrieval/ann_index.py +1 -1
- package/src/superlocalmemory/retrieval/bm25_channel.py +35 -11
- package/src/superlocalmemory/retrieval/bridge_discovery.py +73 -8
- package/src/superlocalmemory/retrieval/engine.py +169 -79
- package/src/superlocalmemory/retrieval/entity_channel.py +289 -67
- package/src/superlocalmemory/retrieval/forgetting_filter.py +1 -1
- package/src/superlocalmemory/retrieval/fusion.py +1 -1
- package/src/superlocalmemory/retrieval/hopfield_channel.py +118 -30
- package/src/superlocalmemory/retrieval/profile_channel.py +1 -1
- package/src/superlocalmemory/retrieval/quantization_aware_search.py +16 -10
- package/src/superlocalmemory/retrieval/reranker.py +56 -20
- package/src/superlocalmemory/retrieval/scope_policy.py +85 -0
- package/src/superlocalmemory/retrieval/semantic_channel.py +122 -14
- package/src/superlocalmemory/retrieval/spreading_activation.py +141 -25
- package/src/superlocalmemory/retrieval/strategy.py +1 -1
- package/src/superlocalmemory/retrieval/temporal_channel.py +30 -15
- package/src/superlocalmemory/retrieval/vector_store.py +1 -1
- package/src/superlocalmemory/server/api.py +10 -7
- package/src/superlocalmemory/server/bandit_loops.py +4 -2
- package/src/superlocalmemory/server/recall_serializer.py +24 -0
- package/src/superlocalmemory/server/route_mutations.py +84 -0
- package/src/superlocalmemory/server/routes/agents.py +8 -6
- package/src/superlocalmemory/server/routes/brain.py +14 -12
- package/src/superlocalmemory/server/routes/chat.py +29 -12
- package/src/superlocalmemory/server/routes/data_io.py +55 -24
- package/src/superlocalmemory/server/routes/helpers.py +29 -4
- package/src/superlocalmemory/server/routes/ingest.py +53 -36
- package/src/superlocalmemory/server/routes/memories.py +104 -43
- package/src/superlocalmemory/server/routes/mesh.py +31 -0
- package/src/superlocalmemory/server/routes/profiles.py +26 -4
- package/src/superlocalmemory/server/routes/tiers.py +43 -11
- package/src/superlocalmemory/server/routes/timeline.py +5 -1
- package/src/superlocalmemory/server/routes/v3_api.py +76 -21
- package/src/superlocalmemory/server/security_middleware.py +1 -1
- package/src/superlocalmemory/server/ui.py +6 -3
- package/src/superlocalmemory/server/unified_daemon.py +680 -293
- package/src/superlocalmemory/server/write_identity.py +147 -0
- package/src/superlocalmemory/storage/access_log.py +4 -3
- package/src/superlocalmemory/storage/database.py +118 -25
- package/src/superlocalmemory/storage/migration_runner.py +84 -1
- package/src/superlocalmemory/storage/migration_v33.py +1 -1
- package/src/superlocalmemory/storage/migrations/M002_model_state_history.py +6 -60
- package/src/superlocalmemory/storage/migrations/M018_ingestion_operations.py +120 -0
- package/src/superlocalmemory/storage/migrations/M019_derivation_lineage.py +54 -0
- package/src/superlocalmemory/storage/migrations/M020_model_state_integrity.py +52 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +5 -0
- package/src/superlocalmemory/storage/models.py +16 -0
- package/src/superlocalmemory/storage/quantized_store.py +20 -3
- package/src/superlocalmemory/storage/v2_migrator.py +5 -3
- package/src/superlocalmemory/ui/favicon.svg +5 -0
- package/src/superlocalmemory/ui/index.html +1 -0
- package/src/superlocalmemory/ui/js/compliance.js +1 -1
- package/src/superlocalmemory/ui/js/core.js +49 -8
- package/src/superlocalmemory/ui/js/dashboard.js +23 -2
- package/src/superlocalmemory/ui/js/feedback.js +1 -1
- package/src/superlocalmemory/ui/js/graph-filters.js +1 -1
- package/src/superlocalmemory/ui/js/graph-ui.js +1 -1
- package/src/superlocalmemory/ui/js/lifecycle.js +1 -1
- package/src/superlocalmemory/ui/js/ng-mesh.js +15 -49
- package/src/superlocalmemory/ui/js/settings.js +4 -2
- package/src/superlocalmemory/vector/lancedb_backend.py +57 -9
- package/bin/slm +0 -59
- package/bin/slm.bat +0 -77
- package/bin/slm.cmd +0 -5
- package/ide/integrations/langchain/README.md +0 -106
- package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +0 -9
- package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +0 -201
- package/ide/integrations/langchain/pyproject.toml +0 -38
- package/ide/integrations/langchain/tests/__init__.py +0 -3
- package/ide/integrations/langchain/tests/test_chat_message_history.py +0 -215
- package/ide/integrations/langchain/tests/test_security.py +0 -117
- package/ide/integrations/llamaindex/README.md +0 -81
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +0 -9
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +0 -316
- package/ide/integrations/llamaindex/pyproject.toml +0 -43
- package/ide/integrations/llamaindex/tests/__init__.py +0 -3
- package/ide/integrations/llamaindex/tests/test_chat_store.py +0 -294
- package/ide/integrations/llamaindex/tests/test_security.py +0 -241
- package/plugin-src/.mcp.json +0 -12
- package/plugin-src/agents/slm-memory-advisor.md +0 -44
- package/plugin-src/agents/slm-optimize-advisor.md +0 -38
- package/plugin-src/hooks/.gitkeep +0 -0
- package/plugin-src/hooks/hooks.json +0 -23
- package/plugin-src/manifest.json +0 -25
- package/plugin-src/requirements.txt +0 -1
- package/plugin-src/rules/CLAUDE.md.fragment +0 -44
- package/plugin-src/scripts/ensure-venv.bat +0 -122
- package/plugin-src/scripts/ensure-venv.sh +0 -105
- package/plugin-src/scripts/slm-launch +0 -15
- package/plugin-src/scripts/slm-launch.bat +0 -17
- package/plugin-src/settings.json +0 -16
- package/plugin-src/skills/slm-cache/SKILL.md +0 -140
- package/plugin-src/skills/slm-compress/SKILL.md +0 -143
- package/plugin-src/skills/slm-graph/SKILL.md +0 -300
- package/plugin-src/skills/slm-recall/SKILL.md +0 -204
- package/plugin-src/skills/slm-remember/SKILL.md +0 -194
- package/plugin-src/skills/slm-session/SKILL.md +0 -207
- package/plugin-src/skills/slm-status/SKILL.md +0 -149
- package/scripts/__tests__/build-plugin.test.mjs +0 -613
- package/scripts/_savings_math.py +0 -270
- package/scripts/build-dmg.sh +0 -417
- package/scripts/build-plugin.js +0 -742
- package/scripts/build-slm-hook.ps1 +0 -40
- package/scripts/build-slm-hook.sh +0 -45
- package/scripts/build_entry.py +0 -452
- package/scripts/ci/stage5b_gate.sh +0 -50
- package/scripts/dogfood_savings.py +0 -490
- package/scripts/generate-thumbnails.py +0 -218
- package/scripts/install-skills.ps1 +0 -4
- package/scripts/install-skills.sh +0 -5
- package/scripts/install.ps1 +0 -701
- package/scripts/install.sh +0 -1015
- package/scripts/postinstall_binary.js +0 -287
- package/scripts/prepack.js +0 -33
- package/scripts/release_manifest.py +0 -273
- package/scripts/slm-hook.spec +0 -56
- package/scripts/start-dashboard.ps1 +0 -52
- package/scripts/start-dashboard.sh +0 -41
- package/scripts/sync-wiki.ps1 +0 -127
- package/scripts/sync-wiki.sh +0 -82
- package/scripts/test-dmg.sh +0 -161
- package/scripts/test-npm-package.ps1 +0 -252
- package/scripts/test-npm-package.sh +0 -207
- package/scripts/verify-install.ps1 +0 -294
- package/scripts/verify-install.sh +0 -266
- package/scripts/verify-v27.ps1 +0 -301
- package/scripts/verify-v27.sh +0 -233
- package/src/superlocalmemory.egg-info/PKG-INFO +0 -513
- package/src/superlocalmemory.egg-info/SOURCES.txt +0 -529
- package/src/superlocalmemory.egg-info/dependency_links.txt +0 -1
- package/src/superlocalmemory.egg-info/entry_points.txt +0 -2
- package/src/superlocalmemory.egg-info/requires.txt +0 -71
- package/src/superlocalmemory.egg-info/top_level.txt +0 -1
|
@@ -13,25 +13,24 @@ Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
|
13
13
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
|
+
import hashlib
|
|
16
17
|
import logging
|
|
17
|
-
from pathlib import Path
|
|
18
18
|
from typing import Callable
|
|
19
19
|
|
|
20
20
|
from mcp.types import ToolAnnotations
|
|
21
21
|
|
|
22
22
|
from superlocalmemory.core.config import CANONICAL_RECALL_LIMIT
|
|
23
|
+
from superlocalmemory.infra.data_root import canonical_data_root, state_path
|
|
24
|
+
from superlocalmemory.mcp.shared import authorize_mcp_mutation
|
|
23
25
|
|
|
24
26
|
logger = logging.getLogger(__name__)
|
|
25
27
|
|
|
26
|
-
_DB_PATH = str(Path.home() / ".superlocalmemory" / "memory.db")
|
|
27
|
-
|
|
28
|
-
|
|
29
28
|
def _emit_event(event_type: str, payload: dict | None = None,
|
|
30
29
|
source_agent: str = "mcp_client") -> None:
|
|
31
30
|
"""Emit an event to the EventBus (best-effort, never raises)."""
|
|
32
31
|
try:
|
|
33
32
|
from superlocalmemory.infra.event_bus import EventBus
|
|
34
|
-
bus = EventBus.get_instance(
|
|
33
|
+
bus = EventBus.get_instance(str(state_path("memory.db")))
|
|
35
34
|
bus.emit(event_type, payload=payload, source_agent=source_agent,
|
|
36
35
|
source_protocol="mcp")
|
|
37
36
|
except Exception:
|
|
@@ -58,7 +57,6 @@ def _record_recall_hits(
|
|
|
58
57
|
signal quality is never load-bearing on recall correctness.
|
|
59
58
|
"""
|
|
60
59
|
try:
|
|
61
|
-
from pathlib import Path
|
|
62
60
|
from superlocalmemory.learning.signals import (
|
|
63
61
|
LearningSignals,
|
|
64
62
|
enqueue_shown_flip,
|
|
@@ -66,7 +64,7 @@ def _record_recall_hits(
|
|
|
66
64
|
|
|
67
65
|
engine = get_engine()
|
|
68
66
|
pid = engine.profile_id
|
|
69
|
-
slm_dir =
|
|
67
|
+
slm_dir = canonical_data_root()
|
|
70
68
|
|
|
71
69
|
shown_ids = [r.get("fact_id", "") for r in results[:10]
|
|
72
70
|
if r.get("fact_id")]
|
|
@@ -108,6 +106,7 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
108
106
|
agent_id: str = "mcp_client",
|
|
109
107
|
scope: str | None = None,
|
|
110
108
|
shared_with: str = "",
|
|
109
|
+
idempotency_key: str = "",
|
|
111
110
|
) -> dict:
|
|
112
111
|
"""Store content to memory with intelligent indexing.
|
|
113
112
|
|
|
@@ -128,13 +127,22 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
128
127
|
"agent_id": agent_id,
|
|
129
128
|
"session_id": session_id,
|
|
130
129
|
}
|
|
130
|
+
effective_idempotency_key = idempotency_key
|
|
131
|
+
if not effective_idempotency_key and session_id:
|
|
132
|
+
material = (
|
|
133
|
+
f"{agent_id}\0{session_id}\0{scope or ''}\0{shared_with}\0{content}"
|
|
134
|
+
)
|
|
135
|
+
effective_idempotency_key = "mcp:" + hashlib.sha256(
|
|
136
|
+
material.encode("utf-8")
|
|
137
|
+
).hexdigest()
|
|
131
138
|
# Parse shared_with from comma-separated string
|
|
132
139
|
_shared_list = [s.strip() for s in shared_with.split(",") if s.strip()] if shared_with else None
|
|
133
140
|
# v3.5.5 WRITE-THROUGH: route through the daemon's /remember, which does
|
|
134
141
|
# a synchronous verbatim insert (memory is keyword/BM25-recallable the
|
|
135
142
|
# instant this returns) and enqueues async enrichment. This closes the
|
|
136
143
|
# recall window so a parallel/next agent finds memories saved seconds ago.
|
|
137
|
-
# Falls back to
|
|
144
|
+
# Falls back to the capability-owned worker only if the daemon is
|
|
145
|
+
# unreachable. Raw pending.db writes are legacy replay input only.
|
|
138
146
|
try:
|
|
139
147
|
import asyncio as _asyncio
|
|
140
148
|
from superlocalmemory.cli.daemon import daemon_request, is_daemon_running
|
|
@@ -145,36 +153,87 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
145
153
|
resp = await _asyncio.to_thread(daemon_request, "POST", "/remember", {
|
|
146
154
|
"content": content, "tags": tags, "metadata": meta,
|
|
147
155
|
"scope": scope, "shared_with": _shared_list,
|
|
156
|
+
"session_id": session_id,
|
|
157
|
+
"idempotency_key": effective_idempotency_key or None,
|
|
148
158
|
})
|
|
149
159
|
if resp and (resp.get("fact_ids") is not None or resp.get("ok")):
|
|
150
160
|
fids = resp.get("fact_ids") or []
|
|
161
|
+
materialization_state = resp.get("materialization_state")
|
|
162
|
+
if materialization_state is None:
|
|
163
|
+
materialization_state = (
|
|
164
|
+
"complete" if resp.get("status") == "stored" else "queryable"
|
|
165
|
+
)
|
|
166
|
+
pending = materialization_state != "complete"
|
|
151
167
|
return {
|
|
152
168
|
"success": True,
|
|
153
|
-
"fact_ids": fids
|
|
154
|
-
"count": len(fids)
|
|
155
|
-
"pending":
|
|
156
|
-
"
|
|
169
|
+
"fact_ids": fids,
|
|
170
|
+
"count": int(resp.get("count", len(fids))),
|
|
171
|
+
"pending": pending,
|
|
172
|
+
"pending_id": resp.get("pending_id") if pending else None,
|
|
173
|
+
"operation_id": resp.get("operation_id"),
|
|
174
|
+
"materialization_state": materialization_state,
|
|
175
|
+
"message": (
|
|
176
|
+
"Stored through canonical daemon ingestion."
|
|
177
|
+
if not pending
|
|
178
|
+
else "Queryable now; canonical enrichment is still running."
|
|
179
|
+
),
|
|
157
180
|
}
|
|
158
181
|
except Exception as dexc:
|
|
159
182
|
logger.debug("MCP remember via daemon failed, pending fallback: %s", dexc)
|
|
160
183
|
|
|
161
184
|
try:
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
185
|
+
import asyncio as _asyncio
|
|
186
|
+
from superlocalmemory.mcp._daemon_proxy import choose_pool
|
|
187
|
+
|
|
188
|
+
worker_meta = {
|
|
189
|
+
**meta,
|
|
190
|
+
"tags": tags,
|
|
191
|
+
"scope": scope or "personal",
|
|
192
|
+
"shared_with": _shared_list or [],
|
|
193
|
+
"idempotency_key": (
|
|
194
|
+
effective_idempotency_key
|
|
195
|
+
or "mcp:" + hashlib.sha256(content.encode("utf-8")).hexdigest()
|
|
196
|
+
),
|
|
197
|
+
}
|
|
198
|
+
stored = await _asyncio.to_thread(
|
|
199
|
+
choose_pool().store,
|
|
200
|
+
content,
|
|
201
|
+
worker_meta,
|
|
202
|
+
)
|
|
203
|
+
if not isinstance(stored, dict) or not stored.get("ok"):
|
|
204
|
+
raise RuntimeError(
|
|
205
|
+
(stored or {}).get("error", "canonical worker store failed")
|
|
206
|
+
if isinstance(stored, dict)
|
|
207
|
+
else "canonical worker returned an invalid response"
|
|
208
|
+
)
|
|
209
|
+
fact_ids = list(stored.get("fact_ids") or [])
|
|
210
|
+
materialization_state = str(
|
|
211
|
+
stored.get("materialization_state") or "complete"
|
|
212
|
+
)
|
|
213
|
+
allowed_states = {"queryable", "enriching", "complete"}
|
|
214
|
+
if materialization_state not in allowed_states:
|
|
215
|
+
raise RuntimeError(
|
|
216
|
+
"canonical worker returned invalid materialization state: "
|
|
217
|
+
f"{materialization_state}"
|
|
218
|
+
)
|
|
219
|
+
pending = materialization_state != "complete"
|
|
220
|
+
operation_id = stored.get("operation_id")
|
|
221
|
+
pending_id = stored.get("pending_id")
|
|
222
|
+
if pending and pending_id is None:
|
|
223
|
+
pending_id = operation_id
|
|
171
224
|
return {
|
|
172
225
|
"success": True,
|
|
173
|
-
"fact_ids":
|
|
174
|
-
"count":
|
|
175
|
-
"pending":
|
|
176
|
-
"pending_id": pending_id,
|
|
177
|
-
"
|
|
226
|
+
"fact_ids": fact_ids,
|
|
227
|
+
"count": int(stored.get("count", len(fact_ids))),
|
|
228
|
+
"pending": pending,
|
|
229
|
+
"pending_id": pending_id if pending else None,
|
|
230
|
+
"operation_id": operation_id,
|
|
231
|
+
"materialization_state": materialization_state,
|
|
232
|
+
"message": (
|
|
233
|
+
"Stored through canonical local ingestion."
|
|
234
|
+
if not pending
|
|
235
|
+
else "Queryable now; canonical enrichment is still running."
|
|
236
|
+
),
|
|
178
237
|
}
|
|
179
238
|
except Exception as exc:
|
|
180
239
|
logger.exception("remember failed")
|
|
@@ -277,6 +336,12 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
277
336
|
"retrieval_time_ms": result.get("retrieval_time_ms", 0),
|
|
278
337
|
# v3.6.6: surface evidence-floor signal to MCP clients.
|
|
279
338
|
"no_confident_match": result.get("no_confident_match", False),
|
|
339
|
+
"score_contract_version": result.get("score_contract_version", "2"),
|
|
340
|
+
"calibration_status": result.get("calibration_status", "uncalibrated"),
|
|
341
|
+
"calibration_id": result.get("calibration_id"),
|
|
342
|
+
"answer_confidence": result.get("answer_confidence"),
|
|
343
|
+
"abstained": result.get("abstained", False),
|
|
344
|
+
"abstention_reason": result.get("abstention_reason"),
|
|
280
345
|
}
|
|
281
346
|
return {"success": False, "error": result.get("error", "Recall failed")}
|
|
282
347
|
except Exception as exc:
|
|
@@ -403,12 +468,19 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
403
468
|
try:
|
|
404
469
|
engine = get_engine()
|
|
405
470
|
pid = profile_id or engine.profile_id
|
|
471
|
+
authorization = authorize_mcp_mutation(
|
|
472
|
+
engine,
|
|
473
|
+
"update",
|
|
474
|
+
mutation_source="mcp-build-memory-graph",
|
|
475
|
+
profile_id=pid,
|
|
476
|
+
)
|
|
406
477
|
facts = engine._db.get_all_facts(pid)
|
|
407
478
|
edge_count = 0
|
|
408
479
|
for fact in facts:
|
|
409
480
|
if engine._graph_builder:
|
|
410
481
|
engine._graph_builder.build_edges(fact, pid)
|
|
411
482
|
edge_count += 1
|
|
483
|
+
authorization.complete()
|
|
412
484
|
return {
|
|
413
485
|
"success": True,
|
|
414
486
|
"facts_processed": len(facts),
|
|
@@ -424,6 +496,13 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
424
496
|
try:
|
|
425
497
|
engine = get_engine()
|
|
426
498
|
old = engine.profile_id
|
|
499
|
+
authorization = authorize_mcp_mutation(
|
|
500
|
+
engine,
|
|
501
|
+
"update",
|
|
502
|
+
mutation_source="mcp-switch-profile",
|
|
503
|
+
profile_id=profile_id,
|
|
504
|
+
content_preview=f"{old} -> {profile_id}",
|
|
505
|
+
)
|
|
427
506
|
engine.profile_id = profile_id
|
|
428
507
|
|
|
429
508
|
# Persist to both config stores so CLI and Dashboard stay in sync
|
|
@@ -446,6 +525,7 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
446
525
|
except Exception:
|
|
447
526
|
logger.debug("worker-pool recycle on profile switch skipped")
|
|
448
527
|
|
|
528
|
+
authorization.complete()
|
|
449
529
|
return {
|
|
450
530
|
"success": True,
|
|
451
531
|
"previous_profile": old,
|
|
@@ -513,6 +593,14 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
513
593
|
"""Correct or annotate a learned behavioral pattern to improve retrieval."""
|
|
514
594
|
try:
|
|
515
595
|
engine = get_engine()
|
|
596
|
+
authorization = authorize_mcp_mutation(
|
|
597
|
+
engine,
|
|
598
|
+
"update",
|
|
599
|
+
mutation_source="mcp-correct-pattern",
|
|
600
|
+
profile_id=engine.profile_id,
|
|
601
|
+
fact_id=pattern_id,
|
|
602
|
+
content_preview=correction,
|
|
603
|
+
)
|
|
516
604
|
from superlocalmemory.learning.behavioral import BehavioralPatternStore
|
|
517
605
|
store = BehavioralPatternStore(engine._db.db_path)
|
|
518
606
|
store.record(
|
|
@@ -521,6 +609,7 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
521
609
|
pattern_key=pattern_id,
|
|
522
610
|
metadata={"correction": correction},
|
|
523
611
|
)
|
|
612
|
+
authorization.complete()
|
|
524
613
|
return {"success": True, "pattern_id": pattern_id}
|
|
525
614
|
except Exception as exc:
|
|
526
615
|
logger.exception("correct_pattern failed")
|
|
@@ -548,7 +637,9 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
548
637
|
result = pool._send({
|
|
549
638
|
"cmd": "delete_memory",
|
|
550
639
|
"fact_id": fact_id,
|
|
551
|
-
|
|
640
|
+
# Informational IDE/client label only. The worker derives its
|
|
641
|
+
# authorization actor from the private local capability.
|
|
642
|
+
"source_agent_id": agent_id,
|
|
552
643
|
})
|
|
553
644
|
if result.get("ok"):
|
|
554
645
|
logger.info("Memory deleted: %s by agent: %s", fact_id[:16], agent_id)
|
|
@@ -589,7 +680,7 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
589
680
|
"cmd": "update_memory",
|
|
590
681
|
"fact_id": fact_id,
|
|
591
682
|
"content": content.strip(),
|
|
592
|
-
"
|
|
683
|
+
"source_agent_id": agent_id,
|
|
593
684
|
})
|
|
594
685
|
if result.get("ok"):
|
|
595
686
|
logger.info("Memory updated: %s by agent: %s", fact_id[:16], agent_id)
|
|
@@ -607,7 +698,7 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
607
698
|
"product": "SuperLocalMemory V3",
|
|
608
699
|
"author": "Varun Pratap Bhardwaj",
|
|
609
700
|
"organization": "Qualixar",
|
|
610
|
-
"license": "
|
|
701
|
+
"license": "AGPL-3.0-or-later",
|
|
611
702
|
"urls": {
|
|
612
703
|
"product": "https://superlocalmemory.com",
|
|
613
704
|
"author": "https://varunpratap.com",
|
|
@@ -627,6 +718,14 @@ def _format_results(results) -> list[dict]:
|
|
|
627
718
|
"content": r.fact.content,
|
|
628
719
|
"score": round(r.score, 3),
|
|
629
720
|
"confidence": round(r.confidence, 3),
|
|
721
|
+
"relevance_score": round(
|
|
722
|
+
getattr(r, "relevance_score", r.score) or 0.0, 3
|
|
723
|
+
),
|
|
724
|
+
"ranking_score": getattr(r, "ranking_score", None),
|
|
725
|
+
"memory_confidence": round(
|
|
726
|
+
getattr(r, "memory_confidence", r.confidence) or 0.0, 3
|
|
727
|
+
),
|
|
728
|
+
"rank_position": int(getattr(r, "rank_position", 0) or 0),
|
|
630
729
|
"trust_score": round(r.trust_score, 3),
|
|
631
730
|
"fact_type": r.fact.fact_type.value,
|
|
632
731
|
"channel_scores": {
|
|
@@ -22,12 +22,10 @@ from pathlib import Path
|
|
|
22
22
|
from typing import Callable
|
|
23
23
|
|
|
24
24
|
from mcp.types import ToolAnnotations
|
|
25
|
+
from superlocalmemory.infra.data_root import state_path
|
|
25
26
|
|
|
26
27
|
logger = logging.getLogger(__name__)
|
|
27
28
|
|
|
28
|
-
MEMORY_DB = Path.home() / ".superlocalmemory" / "memory.db"
|
|
29
|
-
|
|
30
|
-
|
|
31
29
|
def register_evolution_tools(server, get_engine: Callable) -> None:
|
|
32
30
|
"""Register evolution MCP tools for skill evolution intelligence."""
|
|
33
31
|
|
|
@@ -49,7 +47,7 @@ def register_evolution_tools(server, get_engine: Callable) -> None:
|
|
|
49
47
|
"""
|
|
50
48
|
try:
|
|
51
49
|
# Check if evolution is enabled in config
|
|
52
|
-
config_path =
|
|
50
|
+
config_path = state_path("config.json")
|
|
53
51
|
evo_cfg = {}
|
|
54
52
|
if config_path.exists():
|
|
55
53
|
with open(config_path) as f:
|
|
@@ -82,7 +80,7 @@ def register_evolution_tools(server, get_engine: Callable) -> None:
|
|
|
82
80
|
class _Cfg:
|
|
83
81
|
evolution = _EvoCfg()
|
|
84
82
|
|
|
85
|
-
db_path = str(
|
|
83
|
+
db_path = str(state_path("memory.db"))
|
|
86
84
|
evolver = SkillEvolver(db_path, _Cfg())
|
|
87
85
|
|
|
88
86
|
# Build candidate from manual trigger
|
|
@@ -145,7 +143,7 @@ def register_evolution_tools(server, get_engine: Callable) -> None:
|
|
|
145
143
|
try:
|
|
146
144
|
engine = get_engine()
|
|
147
145
|
profile_id = engine.profile_id if engine else "default"
|
|
148
|
-
db_path = str(
|
|
146
|
+
db_path = str(state_path("memory.db"))
|
|
149
147
|
|
|
150
148
|
conn = sqlite3.connect(db_path, timeout=10)
|
|
151
149
|
conn.row_factory = sqlite3.Row
|
|
@@ -273,7 +271,7 @@ def register_evolution_tools(server, get_engine: Callable) -> None:
|
|
|
273
271
|
skill_name: Specific skill name (empty = all skills)
|
|
274
272
|
"""
|
|
275
273
|
try:
|
|
276
|
-
db_path = str(
|
|
274
|
+
db_path = str(state_path("memory.db"))
|
|
277
275
|
conn = sqlite3.connect(db_path, timeout=10)
|
|
278
276
|
conn.row_factory = sqlite3.Row
|
|
279
277
|
|
|
@@ -24,6 +24,8 @@ from typing import Callable
|
|
|
24
24
|
|
|
25
25
|
from mcp.types import ToolAnnotations
|
|
26
26
|
|
|
27
|
+
from superlocalmemory.mcp.shared import authorize_mcp_mutation
|
|
28
|
+
|
|
27
29
|
logger = logging.getLogger(__name__)
|
|
28
30
|
|
|
29
31
|
_MAX_SUMMARY_LEN = 500 # Truncate input/output summaries
|
|
@@ -69,6 +71,13 @@ def register_learning_tools(server, get_engine: Callable) -> None:
|
|
|
69
71
|
output_clean = _scrub(output_summary[:_MAX_SUMMARY_LEN])
|
|
70
72
|
|
|
71
73
|
try:
|
|
74
|
+
authorization = authorize_mcp_mutation(
|
|
75
|
+
engine,
|
|
76
|
+
"update",
|
|
77
|
+
mutation_source="mcp-log-tool-event",
|
|
78
|
+
profile_id=engine.profile_id,
|
|
79
|
+
content_preview=tool_name,
|
|
80
|
+
)
|
|
72
81
|
engine._db.execute(
|
|
73
82
|
"INSERT INTO tool_events "
|
|
74
83
|
"(session_id, profile_id, project_path, tool_name, event_type, "
|
|
@@ -77,6 +86,7 @@ def register_learning_tools(server, get_engine: Callable) -> None:
|
|
|
77
86
|
(session_id, engine.profile_id, project_path, tool_name,
|
|
78
87
|
event_type, input_clean, output_clean, duration_ms, metadata, now),
|
|
79
88
|
)
|
|
89
|
+
authorization.complete()
|
|
80
90
|
return {"success": True, "tool": tool_name, "event": event_type}
|
|
81
91
|
except Exception as exc:
|
|
82
92
|
logger.debug("log_tool_event failed: %s", exc)
|
|
@@ -144,7 +154,22 @@ def register_learning_tools(server, get_engine: Callable) -> None:
|
|
|
144
154
|
assertion_id: The assertion ID to reinforce
|
|
145
155
|
"""
|
|
146
156
|
engine = get_engine()
|
|
147
|
-
|
|
157
|
+
try:
|
|
158
|
+
authorization = authorize_mcp_mutation(
|
|
159
|
+
engine,
|
|
160
|
+
"update",
|
|
161
|
+
mutation_source="mcp-reinforce-assertion",
|
|
162
|
+
profile_id=engine.profile_id,
|
|
163
|
+
fact_id=assertion_id,
|
|
164
|
+
)
|
|
165
|
+
result = _update_assertion_confidence(
|
|
166
|
+
engine._db, assertion_id, reinforce=True,
|
|
167
|
+
)
|
|
168
|
+
if result.get("success"):
|
|
169
|
+
authorization.complete()
|
|
170
|
+
return result
|
|
171
|
+
except Exception as exc:
|
|
172
|
+
return {"success": False, "error": str(exc)}
|
|
148
173
|
|
|
149
174
|
@server.tool(annotations=ToolAnnotations(idempotentHint=True))
|
|
150
175
|
async def contradict_assertion(assertion_id: str) -> dict:
|
|
@@ -157,7 +182,22 @@ def register_learning_tools(server, get_engine: Callable) -> None:
|
|
|
157
182
|
assertion_id: The assertion ID to contradict
|
|
158
183
|
"""
|
|
159
184
|
engine = get_engine()
|
|
160
|
-
|
|
185
|
+
try:
|
|
186
|
+
authorization = authorize_mcp_mutation(
|
|
187
|
+
engine,
|
|
188
|
+
"delete",
|
|
189
|
+
mutation_source="mcp-contradict-assertion",
|
|
190
|
+
profile_id=engine.profile_id,
|
|
191
|
+
fact_id=assertion_id,
|
|
192
|
+
)
|
|
193
|
+
result = _update_assertion_confidence(
|
|
194
|
+
engine._db, assertion_id, reinforce=False,
|
|
195
|
+
)
|
|
196
|
+
if result.get("success"):
|
|
197
|
+
authorization.complete()
|
|
198
|
+
return result
|
|
199
|
+
except Exception as exc:
|
|
200
|
+
return {"success": False, "error": str(exc)}
|
|
161
201
|
|
|
162
202
|
|
|
163
203
|
def _update_assertion_confidence(db, assertion_id: str, reinforce: bool) -> dict:
|
|
@@ -17,7 +17,6 @@ Auto-heartbeat keeps the session alive as long as the MCP server is running.
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
19
|
import asyncio
|
|
20
|
-
import json
|
|
21
20
|
import logging
|
|
22
21
|
import os
|
|
23
22
|
import threading
|
|
@@ -38,18 +37,6 @@ _HEARTBEAT_THREAD: threading.Thread | None = None
|
|
|
38
37
|
_REGISTERED = False
|
|
39
38
|
|
|
40
39
|
|
|
41
|
-
def _daemon_url() -> str:
|
|
42
|
-
"""Get the daemon base URL."""
|
|
43
|
-
port = 8765
|
|
44
|
-
try:
|
|
45
|
-
port_file = os.path.join(os.path.expanduser("~"), ".superlocalmemory", "daemon.port")
|
|
46
|
-
if os.path.exists(port_file):
|
|
47
|
-
port = int(open(port_file).read().strip())
|
|
48
|
-
except Exception:
|
|
49
|
-
pass
|
|
50
|
-
return f"http://127.0.0.1:{port}"
|
|
51
|
-
|
|
52
|
-
|
|
53
40
|
def _detect_project_path() -> str:
|
|
54
41
|
"""Detect current project path from env or cwd."""
|
|
55
42
|
return (
|
|
@@ -60,15 +47,11 @@ def _detect_project_path() -> str:
|
|
|
60
47
|
|
|
61
48
|
|
|
62
49
|
def _mesh_request(method: str, path: str, body: dict | None = None) -> dict | None:
|
|
63
|
-
"""Send
|
|
64
|
-
import urllib.request
|
|
65
|
-
url = f"{_daemon_url()}/mesh{path}"
|
|
50
|
+
"""Send an exact-instance, capability-authenticated mesh request."""
|
|
66
51
|
try:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
resp = urllib.request.urlopen(req, timeout=10)
|
|
71
|
-
return json.loads(resp.read().decode())
|
|
52
|
+
from superlocalmemory.cli.daemon import daemon_request
|
|
53
|
+
|
|
54
|
+
return daemon_request(method, f"/mesh{path}", body)
|
|
72
55
|
except Exception as exc:
|
|
73
56
|
logger.debug("Mesh request failed: %s %s — %s", method, path, exc)
|
|
74
57
|
return None
|
|
@@ -232,8 +215,9 @@ def register_mesh_tools(server, get_engine: Callable) -> None:
|
|
|
232
215
|
async def mesh_state(key: str = "", value: str = "", action: str = "get") -> dict:
|
|
233
216
|
"""Get or set shared state across all sessions.
|
|
234
217
|
|
|
235
|
-
Shared state is visible to
|
|
236
|
-
|
|
218
|
+
Shared state is visible to authenticated peers. Use it for non-secret
|
|
219
|
+
coordination metadata such as feature flags and task assignments.
|
|
220
|
+
Credentials, tokens, passwords, and API keys are rejected.
|
|
237
221
|
|
|
238
222
|
Args:
|
|
239
223
|
key: State key name
|
|
@@ -219,10 +219,17 @@ def register_optimize_tools(server) -> None:
|
|
|
219
219
|
norm_tid = _normalize_tenant_id(tenant)
|
|
220
220
|
ttl_exp = time.time() + ttl_seconds
|
|
221
221
|
|
|
222
|
-
CacheDB.get_default()
|
|
222
|
+
db = CacheDB.get_default()
|
|
223
|
+
db.set(
|
|
223
224
|
cache_key, norm_tid, value_bytes,
|
|
224
225
|
model="mcp-kv", ttl_expires=ttl_exp, tags=["mcp-kv"],
|
|
225
226
|
)
|
|
227
|
+
# ``tag_json`` is descriptive only; tag invalidation joins the
|
|
228
|
+
# normalized llmcache_tags index. CacheManager registers that
|
|
229
|
+
# index itself, whereas this direct MCP KV surface writes to
|
|
230
|
+
# CacheDB. Register it here so ``slm cache invalidate --tag
|
|
231
|
+
# mcp-kv`` actually removes entries created by slm_cache_set.
|
|
232
|
+
db.tag_register(cache_key, norm_tid, ["mcp-kv"])
|
|
226
233
|
return {"ok": True, "stored": True, "note": None}
|
|
227
234
|
|
|
228
235
|
except Exception as exc:
|
|
@@ -15,6 +15,8 @@ from __future__ import annotations
|
|
|
15
15
|
import logging
|
|
16
16
|
from typing import Callable
|
|
17
17
|
|
|
18
|
+
from superlocalmemory.mcp.shared import authorize_mcp_mutation
|
|
19
|
+
|
|
18
20
|
logger = logging.getLogger(__name__)
|
|
19
21
|
|
|
20
22
|
|
|
@@ -42,6 +44,11 @@ def register_v28_tools(server, get_engine: Callable) -> None:
|
|
|
42
44
|
"""
|
|
43
45
|
try:
|
|
44
46
|
engine = get_engine()
|
|
47
|
+
authorization = authorize_mcp_mutation(
|
|
48
|
+
engine,
|
|
49
|
+
"update",
|
|
50
|
+
mutation_source="mcp-report-outcome",
|
|
51
|
+
)
|
|
45
52
|
from superlocalmemory.learning.outcomes import OutcomeTracker
|
|
46
53
|
tracker = OutcomeTracker(engine._db)
|
|
47
54
|
ids = [mid.strip() for mid in memory_ids.split(",") if mid.strip()]
|
|
@@ -58,8 +65,8 @@ def register_v28_tools(server, get_engine: Callable) -> None:
|
|
|
58
65
|
# Previously, outcomes were stored but never created learning signals.
|
|
59
66
|
try:
|
|
60
67
|
from superlocalmemory.learning.feedback import FeedbackCollector
|
|
61
|
-
from
|
|
62
|
-
learning_db =
|
|
68
|
+
from superlocalmemory.infra.data_root import state_path
|
|
69
|
+
learning_db = state_path("learning.db")
|
|
63
70
|
collector = FeedbackCollector(learning_db)
|
|
64
71
|
signal_map = {"success": ("user_positive", 1.0),
|
|
65
72
|
"failure": ("user_negative", 0.0),
|
|
@@ -75,6 +82,7 @@ def register_v28_tools(server, get_engine: Callable) -> None:
|
|
|
75
82
|
except Exception as exc2:
|
|
76
83
|
logger.debug("Outcome→signal bridge: %s", exc2)
|
|
77
84
|
|
|
85
|
+
authorization.complete()
|
|
78
86
|
return {"success": True, "outcome_id": ao.outcome_id, "outcome": outcome}
|
|
79
87
|
except Exception as exc:
|
|
80
88
|
logger.exception("report_outcome failed")
|
|
@@ -134,8 +142,14 @@ def register_v28_tools(server, get_engine: Callable) -> None:
|
|
|
134
142
|
"""
|
|
135
143
|
try:
|
|
136
144
|
engine = get_engine()
|
|
145
|
+
authorization = authorize_mcp_mutation(
|
|
146
|
+
engine,
|
|
147
|
+
"update",
|
|
148
|
+
mutation_source="mcp-retention-policy",
|
|
149
|
+
)
|
|
137
150
|
engine._db.set_config("retention_cold_days", str(cold_after_days))
|
|
138
151
|
engine._db.set_config("retention_archive_days", str(archive_after_days))
|
|
152
|
+
authorization.complete()
|
|
139
153
|
return {
|
|
140
154
|
"success": True,
|
|
141
155
|
"cold_after_days": cold_after_days,
|
|
@@ -174,10 +188,17 @@ def register_v28_tools(server, get_engine: Callable) -> None:
|
|
|
174
188
|
"proposed": new_state.value,
|
|
175
189
|
})
|
|
176
190
|
if not dry_run:
|
|
191
|
+
authorization = authorize_mcp_mutation(
|
|
192
|
+
engine,
|
|
193
|
+
"update",
|
|
194
|
+
mutation_source="mcp-compact-memories",
|
|
195
|
+
profile_id=pid,
|
|
196
|
+
)
|
|
177
197
|
for c in candidates:
|
|
178
198
|
engine._db.update_fact(
|
|
179
199
|
c["fact_id"], {"lifecycle": c["proposed"]},
|
|
180
200
|
)
|
|
201
|
+
authorization.complete()
|
|
181
202
|
return {
|
|
182
203
|
"success": True,
|
|
183
204
|
"dry_run": dry_run,
|
|
@@ -14,6 +14,8 @@ from __future__ import annotations
|
|
|
14
14
|
import logging
|
|
15
15
|
from typing import Callable
|
|
16
16
|
|
|
17
|
+
from superlocalmemory.mcp.shared import authorize_mcp_mutation
|
|
18
|
+
|
|
17
19
|
logger = logging.getLogger(__name__)
|
|
18
20
|
|
|
19
21
|
|
|
@@ -64,6 +66,14 @@ def register_v3_tools(server, get_engine: Callable) -> None:
|
|
|
64
66
|
"success": False,
|
|
65
67
|
"error": f"Invalid mode '{mode}'. Use 'a', 'b', or 'c'.",
|
|
66
68
|
}
|
|
69
|
+
engine = get_engine()
|
|
70
|
+
authorization = authorize_mcp_mutation(
|
|
71
|
+
engine,
|
|
72
|
+
"update",
|
|
73
|
+
mutation_source="mcp-set-mode",
|
|
74
|
+
profile_id=engine.profile_id,
|
|
75
|
+
content_preview=mode_lower,
|
|
76
|
+
)
|
|
67
77
|
from superlocalmemory.core.config import SLMConfig
|
|
68
78
|
from superlocalmemory.mcp.server import reset_engine
|
|
69
79
|
|
|
@@ -80,6 +90,7 @@ def register_v3_tools(server, get_engine: Callable) -> None:
|
|
|
80
90
|
)
|
|
81
91
|
|
|
82
92
|
reset_engine()
|
|
93
|
+
authorization.complete()
|
|
83
94
|
|
|
84
95
|
return {
|
|
85
96
|
"success": True,
|
|
@@ -290,8 +301,16 @@ def register_v3_tools(server, get_engine: Callable) -> None:
|
|
|
290
301
|
results.append({
|
|
291
302
|
"fact_id": item.get("fact_id", ""),
|
|
292
303
|
"content": item.get("content", ""),
|
|
293
|
-
"
|
|
304
|
+
"score": round(float(item.get("score", 0.0)), 4),
|
|
305
|
+
"relevance_score": round(
|
|
306
|
+
float(item.get("relevance_score", item.get("score", 0.0))), 4
|
|
307
|
+
),
|
|
308
|
+
"ranking_score": item.get("ranking_score"),
|
|
294
309
|
"confidence": round(float(item.get("confidence", 0.0)), 3),
|
|
310
|
+
"memory_confidence": round(
|
|
311
|
+
float(item.get("memory_confidence", item.get("confidence", 0.0))), 3
|
|
312
|
+
),
|
|
313
|
+
"rank_position": int(item.get("rank_position", 0)),
|
|
295
314
|
"trust_score": round(float(item.get("trust_score", 0.0)), 3),
|
|
296
315
|
"channel_scores": item.get("channel_scores", {}) or {},
|
|
297
316
|
"evidence_chain": item.get("evidence_chain", []) or [],
|
|
@@ -307,6 +326,12 @@ def register_v3_tools(server, get_engine: Callable) -> None:
|
|
|
307
326
|
"channel_weights": raw.get("channel_weights", {}) if isinstance(raw, dict) else {},
|
|
308
327
|
"total_candidates": raw.get("total_candidates", 0) if isinstance(raw, dict) else 0,
|
|
309
328
|
"retrieval_time_ms": round(float(raw.get("retrieval_time_ms", 0.0)) if isinstance(raw, dict) else 0.0, 1),
|
|
329
|
+
"score_contract_version": raw.get("score_contract_version", "2") if isinstance(raw, dict) else "2",
|
|
330
|
+
"calibration_status": raw.get("calibration_status", "uncalibrated") if isinstance(raw, dict) else "uncalibrated",
|
|
331
|
+
"calibration_id": raw.get("calibration_id") if isinstance(raw, dict) else None,
|
|
332
|
+
"answer_confidence": raw.get("answer_confidence") if isinstance(raw, dict) else None,
|
|
333
|
+
"abstained": bool(raw.get("abstained", False)) if isinstance(raw, dict) else False,
|
|
334
|
+
"abstention_reason": raw.get("abstention_reason") if isinstance(raw, dict) else None,
|
|
310
335
|
}
|
|
311
336
|
except Exception as exc:
|
|
312
337
|
logger.exception("recall_trace failed")
|