superlocalmemory 3.7.7 → 3.8.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/.claude-plugin/marketplace.json +1 -1
- package/ATTRIBUTION.md +1 -3
- package/CHANGELOG.md +85 -0
- package/README.md +199 -29
- package/package.json +4 -2
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/CLAUDE.md +8 -8
- package/plugin/agents/slm-governance-advisor.md +80 -0
- package/plugin/agents/slm-loop-runner.md +71 -0
- package/plugin/agents/slm-memory-advisor.md +10 -5
- package/plugin/agents/slm-optimize-advisor.md +9 -3
- package/plugin/commands/slm-loop.md +31 -0
- package/plugin/hooks/hooks.json +79 -0
- package/plugin/requirements.txt +1 -1
- package/plugin/scripts/slm-launch +46 -7
- package/plugin/settings.json +9 -0
- package/plugin/skills/slm-cache/SKILL.md +9 -1
- package/plugin/skills/slm-compress/SKILL.md +8 -1
- package/plugin/skills/slm-governance/SKILL.md +248 -0
- package/plugin/skills/slm-graph/SKILL.md +17 -3
- package/plugin/skills/slm-loop/SKILL.md +99 -0
- package/plugin/skills/slm-mesh/SKILL.md +282 -0
- package/plugin/skills/slm-profile/SKILL.md +148 -0
- package/plugin/skills/slm-recall/SKILL.md +46 -10
- package/plugin/skills/slm-remember/SKILL.md +48 -1
- package/plugin/skills/slm-scope/SKILL.md +176 -0
- package/plugin/skills/slm-session/SKILL.md +24 -1
- package/plugin/skills/slm-status/SKILL.md +18 -1
- package/plugin-src/agents/slm-governance-advisor.md +80 -0
- package/plugin-src/agents/slm-loop-runner.md +71 -0
- package/plugin-src/agents/slm-memory-advisor.md +10 -5
- package/plugin-src/agents/slm-optimize-advisor.md +9 -3
- package/plugin-src/commands/slm-loop.md +31 -0
- package/plugin-src/hooks/hooks.json +79 -0
- package/plugin-src/manifest.json +7 -2
- package/plugin-src/requirements.txt +1 -1
- package/plugin-src/rules/AGENTS.md +57 -18
- package/plugin-src/rules/CLAUDE.md.fragment +8 -8
- package/plugin-src/scripts/slm-launch +46 -7
- package/plugin-src/settings.json +9 -0
- package/plugin-src/skills/slm-cache/SKILL.md +9 -1
- package/plugin-src/skills/slm-compress/SKILL.md +8 -1
- package/plugin-src/skills/slm-governance/SKILL.md +248 -0
- package/plugin-src/skills/slm-graph/SKILL.md +17 -3
- package/plugin-src/skills/slm-loop/SKILL.md +99 -0
- package/plugin-src/skills/slm-mesh/SKILL.md +282 -0
- package/plugin-src/skills/slm-profile/SKILL.md +148 -0
- package/plugin-src/skills/slm-recall/SKILL.md +46 -10
- package/plugin-src/skills/slm-remember/SKILL.md +48 -1
- package/plugin-src/skills/slm-scope/SKILL.md +176 -0
- package/plugin-src/skills/slm-session/SKILL.md +24 -1
- package/plugin-src/skills/slm-status/SKILL.md +18 -1
- package/pyproject.toml +1 -2
- package/scripts/postinstall/validation.js +2 -0
- package/scripts/postinstall-interactive.js +74 -2
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/access/__init__.py +3 -0
- package/src/superlocalmemory/access/rbac.py +477 -0
- package/src/superlocalmemory/cli/commands.py +96 -12
- package/src/superlocalmemory/cli/compress_cmd.py +17 -7
- package/src/superlocalmemory/cli/loop_cmd.py +192 -0
- package/src/superlocalmemory/cli/main.py +39 -4
- package/src/superlocalmemory/cli/mesh_cmd.py +38 -0
- package/src/superlocalmemory/cli/optimize_cmd.py +3 -0
- package/src/superlocalmemory/cli/pending_store.py +49 -13
- package/src/superlocalmemory/cli/proxy_cmd.py +4 -0
- package/src/superlocalmemory/cli/scale_engine_cmd.py +6 -0
- package/src/superlocalmemory/cli/setup_wizard.py +22 -13
- package/src/superlocalmemory/compliance/audit.py +6 -0
- package/src/superlocalmemory/compliance/gdpr.py +128 -138
- package/src/superlocalmemory/compliance/retention.py +176 -45
- package/src/superlocalmemory/core/backend_orchestrator.py +5 -43
- package/src/superlocalmemory/core/community_summary.py +267 -0
- package/src/superlocalmemory/core/config.py +216 -3
- package/src/superlocalmemory/core/consolidation_engine.py +95 -22
- package/src/superlocalmemory/core/context_cache.py +61 -18
- package/src/superlocalmemory/core/embedding_worker.py +17 -2
- package/src/superlocalmemory/core/embeddings.py +12 -1
- package/src/superlocalmemory/core/engine.py +17 -1
- package/src/superlocalmemory/core/engine_ingestion.py +29 -0
- package/src/superlocalmemory/core/engine_wiring.py +13 -0
- package/src/superlocalmemory/core/entity_community.py +178 -0
- package/src/superlocalmemory/core/graph_analyzer.py +39 -2
- package/src/superlocalmemory/core/graph_pruner.py +13 -8
- package/src/superlocalmemory/core/key_expander.py +138 -0
- package/src/superlocalmemory/core/maintenance.py +23 -0
- package/src/superlocalmemory/core/modes.py +1 -1
- package/src/superlocalmemory/core/mutations.py +2 -2
- package/src/superlocalmemory/core/pii.py +105 -0
- package/src/superlocalmemory/core/progressive_abstraction.py +208 -0
- package/src/superlocalmemory/core/recall_pipeline.py +2 -0
- package/src/superlocalmemory/core/recall_worker.py +20 -6
- package/src/superlocalmemory/core/scale_engine.py +60 -1
- package/src/superlocalmemory/core/security_primitives.py +40 -2
- package/src/superlocalmemory/core/store_pipeline.py +35 -11
- package/src/superlocalmemory/core/worker_pool.py +21 -6
- package/src/superlocalmemory/encoding/entity_reflexion.py +200 -0
- package/src/superlocalmemory/encoding/entity_resolver.py +34 -24
- package/src/superlocalmemory/encoding/fact_extractor.py +26 -1
- package/src/superlocalmemory/encoding/temporal_validator.py +64 -1
- package/src/superlocalmemory/evolution/evolution_store.py +122 -45
- package/src/superlocalmemory/evolution/llm_dispatch.py +12 -1
- package/src/superlocalmemory/evolution/model_selection.py +160 -0
- package/src/superlocalmemory/evolution/mutation_generator.py +16 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +127 -42
- package/src/superlocalmemory/evolution/triggers.py +22 -13
- package/src/superlocalmemory/graph/cozo_backend.py +43 -20
- package/src/superlocalmemory/hooks/adapter_base.py +5 -1
- package/src/superlocalmemory/hooks/auto_recall.py +13 -1
- package/src/superlocalmemory/hooks/claude_code_hooks.py +11 -0
- package/src/superlocalmemory/hooks/codex_assets.py +64 -5
- package/src/superlocalmemory/hooks/hook_daemon.py +20 -3
- package/src/superlocalmemory/hooks/memory_protocol.py +54 -0
- package/src/superlocalmemory/hooks/portable_kit.py +114 -1
- package/src/superlocalmemory/infra/auth_middleware.py +28 -0
- package/src/superlocalmemory/infra/backup.py +12 -1
- package/src/superlocalmemory/infra/daemon_identity.py +40 -4
- package/src/superlocalmemory/infra/data_root.py +43 -4
- package/src/superlocalmemory/infra/event_bus.py +107 -24
- package/src/superlocalmemory/infra/rate_limiter.py +93 -0
- package/src/superlocalmemory/ingestion/adapter_manager.py +4 -1
- package/src/superlocalmemory/ingestion/credentials.py +1 -1
- package/src/superlocalmemory/learning/cross_project.py +28 -19
- package/src/superlocalmemory/learning/reward_proxy.py +42 -9
- package/src/superlocalmemory/loops/__init__.py +56 -0
- package/src/superlocalmemory/loops/budget.py +58 -0
- package/src/superlocalmemory/loops/engine.py +164 -0
- package/src/superlocalmemory/loops/ledger.py +243 -0
- package/src/superlocalmemory/loops/models.py +152 -0
- package/src/superlocalmemory/loops/rules.py +52 -0
- package/src/superlocalmemory/mcp/_daemon_proxy.py +3 -0
- package/src/superlocalmemory/mcp/_pool_adapter.py +2 -0
- package/src/superlocalmemory/mcp/profiles.py +103 -0
- package/src/superlocalmemory/mcp/server.py +21 -49
- package/src/superlocalmemory/mcp/tools_active.py +4 -7
- package/src/superlocalmemory/mcp/tools_code_graph.py +51 -5
- package/src/superlocalmemory/mcp/tools_core.py +50 -5
- package/src/superlocalmemory/mcp/tools_evolution.py +6 -3
- package/src/superlocalmemory/mcp/tools_loops.py +300 -0
- package/src/superlocalmemory/mcp/tools_mesh.py +140 -4
- package/src/superlocalmemory/mcp/tools_optimize.py +15 -8
- package/src/superlocalmemory/mesh/broker.py +237 -129
- package/src/superlocalmemory/mesh/remote_sync.py +50 -8
- package/src/superlocalmemory/optimize/NOTICE +1 -6
- package/src/superlocalmemory/optimize/adapters/anthropic_adapter.py +1 -4
- package/src/superlocalmemory/optimize/adapters/openai_adapter.py +1 -4
- package/src/superlocalmemory/optimize/cache/semantic.py +27 -19
- package/src/superlocalmemory/optimize/compress/align.py +32 -26
- package/src/superlocalmemory/optimize/compress/ccr.py +14 -71
- package/src/superlocalmemory/optimize/compress/router.py +105 -22
- package/src/superlocalmemory/optimize/config/defaults.py +1 -1
- package/src/superlocalmemory/optimize/config/schema.py +87 -4
- package/src/superlocalmemory/optimize/metrics/counters.py +13 -4
- package/src/superlocalmemory/optimize/metrics/estimator.py +0 -3
- package/src/superlocalmemory/optimize/proxy/_helpers.py +31 -4
- package/src/superlocalmemory/optimize/storage/db.py +38 -9
- package/src/superlocalmemory/optimize/storage/schema.py +10 -0
- package/src/superlocalmemory/parameterization/pattern_extractor.py +6 -3
- package/src/superlocalmemory/retrieval/agentic.py +1 -1
- package/src/superlocalmemory/retrieval/bm25_channel.py +68 -10
- package/src/superlocalmemory/retrieval/engine.py +168 -26
- package/src/superlocalmemory/retrieval/entity_channel.py +7 -5
- package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -2
- package/src/superlocalmemory/retrieval/semantic_channel.py +114 -21
- package/src/superlocalmemory/retrieval/spreading_activation.py +11 -2
- package/src/superlocalmemory/retrieval/temporal_channel.py +48 -9
- package/src/superlocalmemory/retrieval/temporal_frame.py +102 -0
- package/src/superlocalmemory/retrieval/temporal_validity_filter.py +135 -0
- package/src/superlocalmemory/retrieval/time_window.py +181 -0
- package/src/superlocalmemory/server/api.py +21 -4
- package/src/superlocalmemory/server/profile_runtime.py +125 -8
- package/src/superlocalmemory/server/rbac_enforce.py +142 -0
- package/src/superlocalmemory/server/recall_health.py +24 -3
- package/src/superlocalmemory/server/recall_serializer.py +19 -1
- package/src/superlocalmemory/server/routes/abstraction.py +115 -0
- package/src/superlocalmemory/server/routes/agents.py +128 -38
- package/src/superlocalmemory/server/routes/backup.py +34 -10
- package/src/superlocalmemory/server/routes/behavioral.py +13 -12
- package/src/superlocalmemory/server/routes/brain.py +21 -5
- package/src/superlocalmemory/server/routes/chat.py +72 -16
- package/src/superlocalmemory/server/routes/compliance.py +171 -21
- package/src/superlocalmemory/server/routes/config_api.py +436 -0
- package/src/superlocalmemory/server/routes/data_io.py +30 -8
- package/src/superlocalmemory/server/routes/entity.py +9 -4
- package/src/superlocalmemory/server/routes/events.py +24 -8
- package/src/superlocalmemory/server/routes/evolution.py +135 -17
- package/src/superlocalmemory/server/routes/helpers.py +16 -1
- package/src/superlocalmemory/server/routes/ingest.py +7 -4
- package/src/superlocalmemory/server/routes/insights.py +3 -3
- package/src/superlocalmemory/server/routes/learning.py +14 -14
- package/src/superlocalmemory/server/routes/lifecycle.py +59 -8
- package/src/superlocalmemory/server/routes/memories.py +221 -49
- package/src/superlocalmemory/server/routes/mesh.py +95 -15
- package/src/superlocalmemory/server/routes/optimize.py +33 -1
- package/src/superlocalmemory/server/routes/prewarm.py +2 -0
- package/src/superlocalmemory/server/routes/profiles.py +63 -17
- package/src/superlocalmemory/server/routes/ratelimit.py +124 -0
- package/src/superlocalmemory/server/routes/rbac.py +367 -0
- package/src/superlocalmemory/server/routes/stats.py +13 -6
- package/src/superlocalmemory/server/routes/tiers.py +11 -9
- package/src/superlocalmemory/server/routes/v3_api.py +194 -81
- package/src/superlocalmemory/server/routes/ws.py +5 -2
- package/src/superlocalmemory/server/security_middleware.py +12 -5
- package/src/superlocalmemory/server/ui.py +30 -5
- package/src/superlocalmemory/server/unified_daemon.py +431 -75
- package/src/superlocalmemory/server/write_identity.py +38 -8
- package/src/superlocalmemory/storage/database.py +265 -53
- package/src/superlocalmemory/storage/migration_runner.py +53 -0
- package/src/superlocalmemory/storage/migrations/M021_ingestion_log_profile.py +108 -0
- package/src/superlocalmemory/storage/migrations/M022_entity_aliases_profile.py +86 -0
- package/src/superlocalmemory/storage/migrations/M023_mesh_profile_isolation.py +194 -0
- package/src/superlocalmemory/storage/migrations/M024_rbac_users_roles.py +87 -0
- package/src/superlocalmemory/storage/migrations/M025_perf_indexes.py +90 -0
- package/src/superlocalmemory/storage/migrations/M026_rbac_memberships_fk.py +136 -0
- package/src/superlocalmemory/storage/migrations/M027_transferable_patterns_profile.py +163 -0
- package/src/superlocalmemory/storage/models.py +4 -0
- package/src/superlocalmemory/storage/schema.py +87 -0
- package/src/superlocalmemory/storage/schema_v32.py +0 -9
- package/src/superlocalmemory/storage/schema_v343.py +24 -12
- package/src/superlocalmemory/trust/gate.py +49 -8
- package/src/superlocalmemory/ui/assets/slm-icon-white.svg +64 -0
- package/src/superlocalmemory/ui/assets/slm-icon.svg +36 -0
- package/src/superlocalmemory/ui/css/design-system.css +621 -0
- package/src/superlocalmemory/ui/css/neural-glass.css +6 -0
- package/src/superlocalmemory/ui/css/od-bridge.css +158 -0
- package/src/superlocalmemory/ui/favicon.svg +35 -4
- package/src/superlocalmemory/ui/index.html +306 -173
- package/src/superlocalmemory/ui/js/brain.js +5 -20
- package/src/superlocalmemory/ui/js/core.js +47 -31
- package/src/superlocalmemory/ui/js/dashboard.js +314 -63
- package/src/superlocalmemory/ui/js/event-delegation.js +102 -0
- package/src/superlocalmemory/ui/js/knowledge-graph.js +11 -11
- package/src/superlocalmemory/ui/js/math-health.js +1 -1
- package/src/superlocalmemory/ui/js/memories.js +15 -4
- package/src/superlocalmemory/ui/js/memory-chat.js +7 -7
- package/src/superlocalmemory/ui/js/ng-entities.js +6 -8
- package/src/superlocalmemory/ui/js/ng-ingestion.js +4 -4
- package/src/superlocalmemory/ui/js/ng-mesh.js +4 -9
- package/src/superlocalmemory/ui/js/ng-shell.js +8 -8
- package/src/superlocalmemory/ui/js/ng-skills.js +54 -2
- package/src/superlocalmemory/ui/js/od-agents.js +544 -0
- package/src/superlocalmemory/ui/js/od-auth-gate.js +257 -0
- package/src/superlocalmemory/ui/js/od-backup.js +780 -0
- package/src/superlocalmemory/ui/js/od-brain.js +779 -0
- package/src/superlocalmemory/ui/js/od-entities.js +579 -0
- package/src/superlocalmemory/ui/js/od-graph.js +593 -0
- package/src/superlocalmemory/ui/js/od-health.js +539 -0
- package/src/superlocalmemory/ui/js/od-mcp.js +508 -0
- package/src/superlocalmemory/ui/js/od-memories.js +887 -0
- package/src/superlocalmemory/ui/js/od-mesh.js +539 -0
- package/src/superlocalmemory/ui/js/od-operations.js +1250 -0
- package/src/superlocalmemory/ui/js/od-optimize.js +787 -0
- package/src/superlocalmemory/ui/js/od-settings.js +1053 -0
- package/src/superlocalmemory/ui/js/od-shell.js +593 -0
- package/src/superlocalmemory/ui/js/od-skills.js +573 -0
- package/src/superlocalmemory/ui/js/od-team.js +258 -0
- package/src/superlocalmemory/ui/js/profiles.js +159 -46
- package/src/superlocalmemory/ui/js/settings.js +2 -2
- package/src/superlocalmemory/ui/js/timeline.js +34 -5
- package/src/superlocalmemory/ui/js/trust-dashboard.js +2 -2
- package/src/superlocalmemory/vector/lancedb_backend.py +8 -6
- package/src/superlocalmemory/learning/behavioral_listener.py +0 -94
|
@@ -104,6 +104,8 @@ def pool_recall(query: str, limit: int = 10, **kwargs: Any) -> PoolRecallRespons
|
|
|
104
104
|
_recall_kwargs["include_global"] = kwargs["include_global"]
|
|
105
105
|
if "include_shared" in kwargs:
|
|
106
106
|
_recall_kwargs["include_shared"] = kwargs["include_shared"]
|
|
107
|
+
if kwargs.get("window"):
|
|
108
|
+
_recall_kwargs["window"] = kwargs["window"]
|
|
107
109
|
raw = _pool().recall(**_recall_kwargs)
|
|
108
110
|
_unwrap_error(raw, "recall")
|
|
109
111
|
items = raw.get("results", []) if isinstance(raw, dict) else []
|
|
@@ -0,0 +1,103 @@
|
|
|
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
|
+
"""MCP profile definitions — pure data, no side effects.
|
|
6
|
+
|
|
7
|
+
Extracted from mcp/server.py (v3.8.0) so the daemon can import profile
|
|
8
|
+
metadata without triggering FastMCP tool registration or engine warmup.
|
|
9
|
+
|
|
10
|
+
server.py re-exports all names from this module for backward compatibility.
|
|
11
|
+
Do NOT import FastMCP, MemoryEngine, or any heavy dependency here.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
# v3.6.14 WP-01: Named profile definitions
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
_PROFILE_CORE: frozenset[str] = frozenset({ # 14
|
|
21
|
+
"remember", "recall", "search", "fetch", "list_recent", "update_memory", "forget",
|
|
22
|
+
"session_init", "close_session",
|
|
23
|
+
"slm_compress", "slm_retrieve", "slm_cache_set", "slm_cache_get", "slm_optimize_stats",
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
_PROFILE_CODE: frozenset[str] = _PROFILE_CORE | frozenset({ # 24
|
|
27
|
+
"build_code_graph", "get_blast_radius", "query_graph",
|
|
28
|
+
"semantic_search_code", "get_review_context", "detect_changes",
|
|
29
|
+
# switch_profile lets a plugin/IDE session change the active workspace over
|
|
30
|
+
# MCP (the plugin ships SLM_MCP_PROFILE=code, so it must be here). The
|
|
31
|
+
# underlying route is RBAC member-gated, so company-mode isolation holds.
|
|
32
|
+
"switch_profile",
|
|
33
|
+
# v3.8.0: bounded loops on the MCP surface. Coding agents (the /slm-loop
|
|
34
|
+
# command's audience) run gated, bounded loops and inspect the ledger.
|
|
35
|
+
"slm_loop_run", "slm_loop_history", "slm_loop_show",
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
_PROFILE_FULL_MESH: frozenset[str] = frozenset({ # 8
|
|
39
|
+
"mesh_summary", "mesh_peers", "mesh_send", "mesh_inbox",
|
|
40
|
+
"mesh_state", "mesh_lock", "mesh_events", "mesh_status",
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
_PROFILE_FULL: frozenset[str] = frozenset({ # 34 base — EXPLICIT literal, NOT runtime _ESSENTIAL_TOOLS (OQ-2)
|
|
44
|
+
"remember", "recall", "search", "fetch", "list_recent", "delete_memory", "update_memory",
|
|
45
|
+
"get_status", "session_init", "observe", "close_session", "report_feedback", "forget",
|
|
46
|
+
"run_maintenance", "consolidate_cognitive", "get_soft_prompts", "set_mode", "report_outcome",
|
|
47
|
+
"log_tool_event", "get_assertions", "reinforce_assertion", "contradict_assertion",
|
|
48
|
+
"evolve_skill", "skill_health", "skill_lineage", "switch_profile",
|
|
49
|
+
"slm_compress", "slm_retrieve", "slm_cache_set", "slm_cache_get", "slm_optimize_stats",
|
|
50
|
+
# v3.8.0: bounded-loop tools (CLI + /slm-loop command + MCP).
|
|
51
|
+
"slm_loop_run", "slm_loop_history", "slm_loop_show",
|
|
52
|
+
}) | _PROFILE_FULL_MESH # 42
|
|
53
|
+
|
|
54
|
+
_PROFILE_POWER: frozenset[str] = _PROFILE_FULL | frozenset({ # 54
|
|
55
|
+
"get_version", "get_mode", "health", "consistency_check", "recall_trace",
|
|
56
|
+
"get_lifecycle_status", "set_retention_policy", "compact_memories",
|
|
57
|
+
"get_behavioral_patterns", "audit_trail", "quantize", "get_retention_stats",
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
_PROFILE_MESH: frozenset[str] = _PROFILE_FULL_MESH # 8
|
|
61
|
+
|
|
62
|
+
# Canonical name → frozenset mapping. "whole" is intentionally absent —
|
|
63
|
+
# it maps to the raw server (all tools, D-2 LOCKED).
|
|
64
|
+
_PROFILE_DEFINITIONS: dict[str, frozenset[str]] = {
|
|
65
|
+
"core": _PROFILE_CORE,
|
|
66
|
+
"code": _PROFILE_CODE,
|
|
67
|
+
"full": _PROFILE_FULL,
|
|
68
|
+
"power": _PROFILE_POWER,
|
|
69
|
+
"mesh": _PROFILE_MESH,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Compatibility aliases published by the v3.6 README. Stale client
|
|
73
|
+
# configurations have one deterministic meaning; migration warnings fire
|
|
74
|
+
# at server startup. Any other value is a configuration error (fail closed).
|
|
75
|
+
_PROFILE_ALIASES: dict[str, str] = {
|
|
76
|
+
"core14": "core",
|
|
77
|
+
# 3.8.0: switch_profile (+1) then bounded-loop tools (+3) grew code/full/
|
|
78
|
+
# power. Every historical count-suffixed name is kept so a v3.6/3.7/early-
|
|
79
|
+
# 3.8 config still resolves (back-compat); new 3.8.0 counts added alongside.
|
|
80
|
+
"code20": "code",
|
|
81
|
+
"code21": "code",
|
|
82
|
+
"code24": "code",
|
|
83
|
+
"full38": "full",
|
|
84
|
+
"full39": "full",
|
|
85
|
+
"full42": "full",
|
|
86
|
+
"power50": "power",
|
|
87
|
+
"power51": "power",
|
|
88
|
+
"power54": "power",
|
|
89
|
+
"mesh8": "mesh",
|
|
90
|
+
"whole81": "whole",
|
|
91
|
+
"whole84": "whole",
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# Plain-English descriptions for UI display.
|
|
95
|
+
# Rules: no internal jargon (no POMDP, Fisher-Rao, TurboQuant, etc.),
|
|
96
|
+
# one sentence, user-facing language only.
|
|
97
|
+
PROFILE_DESCRIPTIONS: dict[str, str] = {
|
|
98
|
+
"core": "Essential memory: store, recall, search, sessions",
|
|
99
|
+
"code": "Core + code-graph tools + profile switching (default for IDE coding agents)",
|
|
100
|
+
"full": "All everyday memory, optimization, and mesh tools",
|
|
101
|
+
"power": "Everything in full plus advanced governance/behavioral tools",
|
|
102
|
+
"mesh": "Cross-device mesh coordination only",
|
|
103
|
+
}
|
|
@@ -75,13 +75,13 @@ def reset_engine():
|
|
|
75
75
|
|
|
76
76
|
# Register tools and resources -------------------------------------------------
|
|
77
77
|
#
|
|
78
|
-
# Essential-only default:
|
|
78
|
+
# Essential-only default: 34 base tools + 8 mesh tools = 42 registered
|
|
79
79
|
# when mesh is enabled. Set ``SLM_MCP_ALL_TOOLS=1`` to expose the full
|
|
80
80
|
# toolset. Rationale: IDEs cap at 50-100 tools total (Cursor,
|
|
81
81
|
# Antigravity, Windsurf) and a maximal SLM registration crowds out
|
|
82
82
|
# other MCP servers the user may have installed.
|
|
83
83
|
# Admin/diagnostics tools remain available via CLI (`slm <command>`).
|
|
84
|
-
# Set SLM_MCP_ALL_TOOLS=1 to enable all
|
|
84
|
+
# Set SLM_MCP_ALL_TOOLS=1 to enable all 84 tools (power users).
|
|
85
85
|
|
|
86
86
|
import os as _os_reg
|
|
87
87
|
|
|
@@ -107,6 +107,10 @@ _ESSENTIAL_TOOLS: set[str] = {
|
|
|
107
107
|
"evolve_skill", "skill_health", "skill_lineage",
|
|
108
108
|
# v3.6.11: Surface B Optimize tools (5)
|
|
109
109
|
"slm_compress", "slm_retrieve", "slm_cache_set", "slm_cache_get", "slm_optimize_stats",
|
|
110
|
+
# v3.8.0: bounded-loop tools (3) — CLI + /slm-loop command + MCP. Kept in
|
|
111
|
+
# the default exposure so any MCP client discovers gated loops, not just
|
|
112
|
+
# profile=code/full/power sessions.
|
|
113
|
+
"slm_loop_run", "slm_loop_history", "slm_loop_show",
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
# v3.4.4: Mesh tools — enabled if mesh_enabled in config or SLM_MCP_MESH_TOOLS=1
|
|
@@ -138,55 +142,21 @@ _user_allowlist_str = _os_reg.environ.get("SLM_MCP_TOOLS", "").strip()
|
|
|
138
142
|
|
|
139
143
|
# ---------------------------------------------------------------------------
|
|
140
144
|
# v3.6.14 WP-01: Named profile definitions
|
|
145
|
+
# Extracted to mcp/profiles.py (v3.8.0) — pure data, no side effects.
|
|
146
|
+
# All names re-exported here for backward compatibility with existing tests
|
|
147
|
+
# and any code that imports them from this module.
|
|
141
148
|
# ---------------------------------------------------------------------------
|
|
142
149
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
"mesh_summary", "mesh_peers", "mesh_send", "mesh_inbox",
|
|
154
|
-
"mesh_state", "mesh_lock", "mesh_events", "mesh_status",
|
|
155
|
-
})
|
|
156
|
-
_PROFILE_FULL = frozenset({ # 30 base — EXPLICIT literal, NOT runtime _ESSENTIAL_TOOLS (OQ-2)
|
|
157
|
-
"remember", "recall", "search", "fetch", "list_recent", "delete_memory", "update_memory",
|
|
158
|
-
"get_status", "session_init", "observe", "close_session", "report_feedback", "forget",
|
|
159
|
-
"run_maintenance", "consolidate_cognitive", "get_soft_prompts", "set_mode", "report_outcome",
|
|
160
|
-
"log_tool_event", "get_assertions", "reinforce_assertion", "contradict_assertion",
|
|
161
|
-
"evolve_skill", "skill_health", "skill_lineage",
|
|
162
|
-
"slm_compress", "slm_retrieve", "slm_cache_set", "slm_cache_get", "slm_optimize_stats",
|
|
163
|
-
}) | _PROFILE_FULL_MESH # 38
|
|
164
|
-
_PROFILE_POWER = _PROFILE_FULL | frozenset({ # 50
|
|
165
|
-
"get_version", "get_mode", "health", "consistency_check", "recall_trace",
|
|
166
|
-
"get_lifecycle_status", "set_retention_policy", "compact_memories",
|
|
167
|
-
"get_behavioral_patterns", "audit_trail", "quantize", "get_retention_stats",
|
|
168
|
-
})
|
|
169
|
-
_PROFILE_MESH = _PROFILE_FULL_MESH # 8
|
|
170
|
-
|
|
171
|
-
_PROFILE_DEFINITIONS: dict[str, frozenset[str]] = {
|
|
172
|
-
"core": _PROFILE_CORE,
|
|
173
|
-
"code": _PROFILE_CODE,
|
|
174
|
-
"full": _PROFILE_FULL,
|
|
175
|
-
"power": _PROFILE_POWER,
|
|
176
|
-
"mesh": _PROFILE_MESH,
|
|
177
|
-
} # "whole" intentionally absent — maps to raw server (D-2 LOCKED)
|
|
178
|
-
|
|
179
|
-
# Compatibility aliases published by the v3.6 README. Keep these explicit so
|
|
180
|
-
# a stale client configuration has one deterministic meaning and emits a
|
|
181
|
-
# migration warning. Any other value is a configuration error (fail closed).
|
|
182
|
-
_PROFILE_ALIASES: dict[str, str] = {
|
|
183
|
-
"core14": "core",
|
|
184
|
-
"code20": "code",
|
|
185
|
-
"full38": "full",
|
|
186
|
-
"power50": "power",
|
|
187
|
-
"mesh8": "mesh",
|
|
188
|
-
"whole81": "whole",
|
|
189
|
-
}
|
|
150
|
+
from superlocalmemory.mcp.profiles import ( # noqa: E402 (after env setups above)
|
|
151
|
+
_PROFILE_CORE,
|
|
152
|
+
_PROFILE_CODE,
|
|
153
|
+
_PROFILE_FULL_MESH,
|
|
154
|
+
_PROFILE_FULL,
|
|
155
|
+
_PROFILE_POWER,
|
|
156
|
+
_PROFILE_MESH,
|
|
157
|
+
_PROFILE_DEFINITIONS,
|
|
158
|
+
_PROFILE_ALIASES,
|
|
159
|
+
)
|
|
190
160
|
|
|
191
161
|
_profile = _os_reg.environ.get("SLM_MCP_PROFILE", "").strip().lower()
|
|
192
162
|
|
|
@@ -291,6 +261,8 @@ register_learning_tools(_target, get_engine) # v3.4.7: Two-way learning tools
|
|
|
291
261
|
register_evolution_tools(_target, get_engine) # v3.4.11: Skill evolution tools
|
|
292
262
|
from superlocalmemory.mcp.tools_optimize import register_optimize_tools
|
|
293
263
|
register_optimize_tools(_target) # v3.6.11: Surface B Optimize tools (proxy-free)
|
|
264
|
+
from superlocalmemory.mcp.tools_loops import register_loop_tools
|
|
265
|
+
register_loop_tools(_target, get_engine) # v3.8.0: bounded-loop tools (CLI+command+MCP)
|
|
294
266
|
|
|
295
267
|
|
|
296
268
|
# V3.3.21: Eager engine warmup — start initializing BEFORE first tool call.
|
|
@@ -186,12 +186,6 @@ def register_active_tools(server, get_engine: Callable) -> None:
|
|
|
186
186
|
relevance score is ≥ 0.70 (architectural decisions that remain
|
|
187
187
|
permanently relevant still surface). Default: 30.
|
|
188
188
|
Set to 0 to disable the age gate entirely.
|
|
189
|
-
|
|
190
|
-
Scoring: five candidate producers (semantic + BM25 + temporal +
|
|
191
|
-
spreading_activation + hopfield) feed RRF fusion; the entity graph then
|
|
192
|
-
applies an optional post-fusion score enhancement. Combined with
|
|
193
|
-
Ebbinghaus exponential recency decay and FSRS stability strengthening by
|
|
194
|
-
access frequency.
|
|
195
189
|
"""
|
|
196
190
|
try:
|
|
197
191
|
from superlocalmemory.hooks.rules_engine import RulesEngine
|
|
@@ -689,7 +683,10 @@ def register_active_tools(server, get_engine: Callable) -> None:
|
|
|
689
683
|
try:
|
|
690
684
|
engine = get_engine()
|
|
691
685
|
db = engine.db
|
|
692
|
-
|
|
686
|
+
# Isolation: the tenant is ALWAYS the engine's active profile. The
|
|
687
|
+
# caller-supplied profile_id is ignored — honoring it let any MCP
|
|
688
|
+
# client read/pin another profile's facts by passing profile_id.
|
|
689
|
+
pid = engine.profile_id
|
|
693
690
|
|
|
694
691
|
if action == "pin":
|
|
695
692
|
if not fact_id:
|
|
@@ -14,10 +14,16 @@ All tools return {"success": bool, ...} envelope. Never raise.
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
16
|
import logging
|
|
17
|
+
import os
|
|
17
18
|
import time
|
|
18
19
|
from pathlib import Path
|
|
19
20
|
from typing import Any, Callable
|
|
20
21
|
|
|
22
|
+
from superlocalmemory.core.security_primitives import (
|
|
23
|
+
PathTraversalError,
|
|
24
|
+
safe_resolve,
|
|
25
|
+
)
|
|
26
|
+
|
|
21
27
|
logger = logging.getLogger(__name__)
|
|
22
28
|
|
|
23
29
|
# ---------------------------------------------------------------------------
|
|
@@ -118,7 +124,14 @@ def register_code_graph_tools(server, get_engine: Callable) -> None:
|
|
|
118
124
|
exclude_patterns: Comma-separated glob patterns to exclude.
|
|
119
125
|
"""
|
|
120
126
|
try:
|
|
121
|
-
|
|
127
|
+
# SEC: contain the untrusted repo_path under $HOME (same guard as
|
|
128
|
+
# update_code_graph) so repo_path="/" cannot ingest the whole
|
|
129
|
+
# filesystem into the code-graph DB.
|
|
130
|
+
from superlocalmemory.core.security_primitives import PathTraversalError
|
|
131
|
+
try:
|
|
132
|
+
repo = safe_resolve(Path.home(), repo_path)
|
|
133
|
+
except (PathTraversalError, OSError, ValueError) as exc:
|
|
134
|
+
return _error_response(f"Invalid repo_path: {exc}")
|
|
122
135
|
if not repo.exists():
|
|
123
136
|
return _error_response(
|
|
124
137
|
f"Repository path does not exist: {repo_path}"
|
|
@@ -250,6 +263,22 @@ def register_code_graph_tools(server, get_engine: Callable) -> None:
|
|
|
250
263
|
svc = _get_service()
|
|
251
264
|
db = svc.db
|
|
252
265
|
|
|
266
|
+
# SEC 3.7.9 (B1): contain untrusted repo_path under $HOME and confirm
|
|
267
|
+
# it is a real git repo BEFORE any git subprocess runs with it as cwd.
|
|
268
|
+
# Blocks RCE via attacker-controlled .git config/hooks (core.fsmonitor,
|
|
269
|
+
# core.hooksPath) that git would otherwise execute in a hostile repo.
|
|
270
|
+
if repo_path:
|
|
271
|
+
try:
|
|
272
|
+
repo = safe_resolve(Path.home(), repo_path)
|
|
273
|
+
except PathTraversalError as exc:
|
|
274
|
+
return _error_response(f"Invalid repo_path: {exc}")
|
|
275
|
+
if not (repo / ".git").exists():
|
|
276
|
+
return _error_response(
|
|
277
|
+
f"Not a git repository (no .git dir): {repo_path}"
|
|
278
|
+
)
|
|
279
|
+
else:
|
|
280
|
+
repo = svc.config.repo_root
|
|
281
|
+
|
|
253
282
|
files_list = [
|
|
254
283
|
f.strip() for f in changed_files.split(",") if f.strip()
|
|
255
284
|
] if changed_files else []
|
|
@@ -258,11 +287,22 @@ def register_code_graph_tools(server, get_engine: Callable) -> None:
|
|
|
258
287
|
# Auto-detect via git
|
|
259
288
|
try:
|
|
260
289
|
import subprocess
|
|
261
|
-
repo
|
|
290
|
+
# repo is the safe_resolve-contained path computed above.
|
|
291
|
+
# Harden git: disable hooks + fsmonitor + system config so a
|
|
292
|
+
# hostile repo cannot execute code via the git invocation (B1).
|
|
262
293
|
result = subprocess.run(
|
|
263
|
-
[
|
|
294
|
+
[
|
|
295
|
+
"git", "-c", "core.hooksPath=/dev/null",
|
|
296
|
+
"-c", "core.fsmonitor=",
|
|
297
|
+
"diff", "--name-only", "HEAD~1", "HEAD",
|
|
298
|
+
],
|
|
264
299
|
capture_output=True, text=True, timeout=30,
|
|
265
300
|
cwd=str(repo),
|
|
301
|
+
env={
|
|
302
|
+
**os.environ,
|
|
303
|
+
"GIT_CONFIG_NOSYSTEM": "1",
|
|
304
|
+
"GIT_TERMINAL_PROMPT": "0",
|
|
305
|
+
},
|
|
266
306
|
)
|
|
267
307
|
files_list = [
|
|
268
308
|
f.strip() for f in result.stdout.strip().split("\n")
|
|
@@ -289,13 +329,19 @@ def register_code_graph_tools(server, get_engine: Callable) -> None:
|
|
|
289
329
|
config = svc.config
|
|
290
330
|
parser = CodeParser(config)
|
|
291
331
|
store = GraphStore(db)
|
|
292
|
-
repo
|
|
332
|
+
# repo already contained via safe_resolve above (B1).
|
|
293
333
|
|
|
294
334
|
nodes_before = db.get_node_count()
|
|
295
335
|
edges_before = db.get_edge_count()
|
|
296
336
|
|
|
297
337
|
for fp in files_list:
|
|
298
|
-
|
|
338
|
+
# fp comes from caller-supplied changed_files; contain it under
|
|
339
|
+
# the (already safe-resolved) repo so "../../.ssh/id_rsa" cannot
|
|
340
|
+
# be read into the graph.
|
|
341
|
+
try:
|
|
342
|
+
full = safe_resolve(repo, fp)
|
|
343
|
+
except Exception:
|
|
344
|
+
continue
|
|
299
345
|
if not full.exists():
|
|
300
346
|
store.remove_file(fp)
|
|
301
347
|
continue
|
|
@@ -284,10 +284,11 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
284
284
|
session_id: str = "", fast: bool = False,
|
|
285
285
|
include_global: bool | None = None,
|
|
286
286
|
include_shared: bool | None = None,
|
|
287
|
+
window: str = "",
|
|
287
288
|
) -> dict:
|
|
288
289
|
"""Search memories through hybrid retrieval, RRF fusion, and reranking.
|
|
289
290
|
|
|
290
|
-
|
|
291
|
+
Optional ``session_id`` threads through to the
|
|
291
292
|
engine's outcome-queue so PostToolUse / Stop hooks can attach
|
|
292
293
|
engagement signals to this recall. Claude Code should pass its
|
|
293
294
|
``CLAUDE_SESSION_ID``. Omitting it degrades to "no closed-loop
|
|
@@ -297,6 +298,11 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
297
298
|
scopes participate in retrieval. Leave them unset (``None``) to use the
|
|
298
299
|
configured default — shared memory is OPT-IN, so by default recall
|
|
299
300
|
returns only this profile's own facts. Pass ``True`` to opt in per call.
|
|
301
|
+
|
|
302
|
+
Time window: optional ``window`` restricts results to a event-time
|
|
303
|
+
range. Accepts a relative span (``"24h"``, ``"7d"``, ``"30d"``,
|
|
304
|
+
``"1y"``) or an explicit range (``"2026-07-01..2026-07-31"``). Empty =
|
|
305
|
+
no time filter.
|
|
300
306
|
"""
|
|
301
307
|
# v3.6.10: resolve "mcp_client" sentinel → URL path (HTTP) or env var (stdio)
|
|
302
308
|
if agent_id == "mcp_client":
|
|
@@ -353,6 +359,7 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
353
359
|
fast=bool(fast),
|
|
354
360
|
include_global=include_global,
|
|
355
361
|
include_shared=include_shared,
|
|
362
|
+
window=window or None,
|
|
356
363
|
)
|
|
357
364
|
if result.get("ok"):
|
|
358
365
|
# Record implicit feedback: every returned result is a recall_hit
|
|
@@ -587,6 +594,10 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
587
594
|
)
|
|
588
595
|
|
|
589
596
|
generation = 0
|
|
597
|
+
# Only the profile_id explicitly confirmed by this process
|
|
598
|
+
# (daemon-acknowledged + locally-validated, or locally
|
|
599
|
+
# validated directly) is ever synced into engine state.
|
|
600
|
+
confirmed_profile_id = None
|
|
590
601
|
if await asyncio.to_thread(is_daemon_running):
|
|
591
602
|
result = await asyncio.to_thread(
|
|
592
603
|
daemon_request,
|
|
@@ -599,12 +610,35 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
599
610
|
"error": "resident daemon rejected the profile switch",
|
|
600
611
|
}
|
|
601
612
|
acknowledged = str(result.get("active_profile", ""))
|
|
602
|
-
if acknowledged != profile_id:
|
|
613
|
+
if not acknowledged or acknowledged != profile_id:
|
|
603
614
|
return {
|
|
604
615
|
"success": False,
|
|
605
616
|
"error": "resident daemon acknowledged a different profile",
|
|
606
617
|
}
|
|
618
|
+
# Local consistency guard (SEC-H-01): the daemon's HTTP
|
|
619
|
+
# acknowledgement alone is not sufficient — this MCP
|
|
620
|
+
# process must also confirm the profile exists in its
|
|
621
|
+
# own local DB handle before syncing local state to it.
|
|
622
|
+
# Mirrors the existence check the no-daemon branch already
|
|
623
|
+
# performs below.
|
|
624
|
+
local_rows = engine._db.execute(
|
|
625
|
+
"SELECT 1 FROM profiles WHERE profile_id = ?",
|
|
626
|
+
(profile_id,),
|
|
627
|
+
)
|
|
628
|
+
if not local_rows:
|
|
629
|
+
return {
|
|
630
|
+
"success": False,
|
|
631
|
+
"error": (
|
|
632
|
+
f"resident daemon acknowledged profile "
|
|
633
|
+
f"'{acknowledged}' but it does not exist in "
|
|
634
|
+
f"this process's local profile store"
|
|
635
|
+
),
|
|
636
|
+
}
|
|
607
637
|
generation = int(result.get("generation", 0))
|
|
638
|
+
# Sync target is the DAEMON-CONFIRMED value, never the raw
|
|
639
|
+
# caller-supplied profile_id, even though they are equal
|
|
640
|
+
# here by construction (checked above).
|
|
641
|
+
confirmed_profile_id = acknowledged
|
|
608
642
|
else:
|
|
609
643
|
rows = engine._db.execute(
|
|
610
644
|
"SELECT 1 FROM profiles WHERE profile_id = ?",
|
|
@@ -628,10 +662,21 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
628
662
|
engine._config.active_profile = old
|
|
629
663
|
persistence.rollback()
|
|
630
664
|
raise
|
|
665
|
+
confirmed_profile_id = profile_id
|
|
666
|
+
|
|
667
|
+
if not confirmed_profile_id:
|
|
668
|
+
# Defensive: should be unreachable — every path above
|
|
669
|
+
# either returns an error or sets confirmed_profile_id.
|
|
670
|
+
return {
|
|
671
|
+
"success": False,
|
|
672
|
+
"error": "profile switch could not be confirmed",
|
|
673
|
+
}
|
|
631
674
|
|
|
632
|
-
# Synchronize this MCP process only after
|
|
633
|
-
|
|
634
|
-
|
|
675
|
+
# Synchronize this MCP process only after confirmation
|
|
676
|
+
# (daemon-acknowledged + locally-validated, or directly
|
|
677
|
+
# locally-validated in the no-daemon branch above).
|
|
678
|
+
engine.profile_id = confirmed_profile_id
|
|
679
|
+
engine._config.active_profile = confirmed_profile_id
|
|
635
680
|
|
|
636
681
|
# v3.6.12 (search-3): recall/delete run in a separate worker
|
|
637
682
|
# subprocess that caches its engine (and profile_id) at init. Recycle
|
|
@@ -98,11 +98,14 @@ def register_evolution_tools(server, get_engine: Callable) -> None:
|
|
|
98
98
|
engine = get_engine()
|
|
99
99
|
profile_id = engine.profile_id if engine else "default"
|
|
100
100
|
|
|
101
|
-
evolver._store.reset_cycle()
|
|
102
|
-
|
|
101
|
+
evolver._store.reset_cycle(profile_id)
|
|
102
|
+
# audit-10 fix: go through evolve_candidate so the manual/MCP path
|
|
103
|
+
# runs under a budget cycle and honours the LLM-call / wall-time /
|
|
104
|
+
# per-day caps (previously _process_candidate ran uncapped here).
|
|
105
|
+
outcome = evolver.evolve_candidate(candidate, profile_id)
|
|
103
106
|
|
|
104
107
|
# Fetch the latest record for this skill to return details
|
|
105
|
-
recent = evolver._store.get_skill_history(skill_name, limit=1)
|
|
108
|
+
recent = evolver._store.get_skill_history(skill_name, profile_id, limit=1)
|
|
106
109
|
record_info = {}
|
|
107
110
|
if recent:
|
|
108
111
|
r = recent[0]
|