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
|
@@ -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":
|