superlocalmemory 3.7.8 → 3.8.1
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 +129 -0
- package/README.md +205 -123
- package/package.json +12 -3
- package/plugin/.claude-plugin/plugin.json +2 -3
- 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/rules/AGENTS.md +57 -18
- 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-graph/SKILL.md +17 -3
- 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-session/SKILL.md +24 -1
- package/plugin-src/skills/slm-status/SKILL.md +18 -1
- package/pyproject.toml +2 -1
- 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 +228 -17
- package/src/superlocalmemory/cli/compress_cmd.py +17 -7
- package/src/superlocalmemory/cli/daemon.py +7 -0
- package/src/superlocalmemory/cli/loop_cmd.py +187 -0
- package/src/superlocalmemory/cli/main.py +49 -8
- 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/cli/version_banner.py +17 -3
- 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 +23 -59
- 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 +21 -7
- package/src/superlocalmemory/core/embeddings.py +131 -46
- package/src/superlocalmemory/core/engine.py +41 -22
- package/src/superlocalmemory/core/engine_ingestion.py +359 -43
- 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/ingestion_command.py +134 -25
- package/src/superlocalmemory/core/injection.py +12 -7
- package/src/superlocalmemory/core/key_expander.py +138 -0
- package/src/superlocalmemory/core/maintenance.py +23 -0
- package/src/superlocalmemory/core/maintenance_scheduler.py +17 -7
- 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 +7 -3
- 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 +186 -29
- 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/hook_handlers.py +6 -1
- package/src/superlocalmemory/hooks/memory_protocol.py +54 -0
- package/src/superlocalmemory/hooks/portable_kit.py +148 -3
- 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/model_rollback.py +3 -0
- package/src/superlocalmemory/learning/ranker_retrain_online.py +2 -0
- package/src/superlocalmemory/learning/reward.py +50 -0
- package/src/superlocalmemory/learning/reward_proxy.py +42 -9
- package/src/superlocalmemory/learning/source_quality.py +523 -1
- 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 +263 -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 +32 -79
- 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 +12 -4
- package/src/superlocalmemory/mcp/tools_evolution.py +6 -3
- package/src/superlocalmemory/mcp/tools_learning.py +2 -2
- 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 +221 -47
- package/src/superlocalmemory/retrieval/entity_channel.py +7 -5
- package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -2
- package/src/superlocalmemory/retrieval/reranker.py +3 -4
- 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 +4 -4
- package/src/superlocalmemory/server/config_file.py +90 -0
- package/src/superlocalmemory/server/origin.py +50 -0
- 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 +317 -70
- package/src/superlocalmemory/server/routes/behavioral.py +349 -71
- package/src/superlocalmemory/server/routes/brain.py +69 -12
- package/src/superlocalmemory/server/routes/chat.py +10 -5
- package/src/superlocalmemory/server/routes/compliance.py +171 -21
- package/src/superlocalmemory/server/routes/config_api.py +438 -0
- package/src/superlocalmemory/server/routes/data_io.py +30 -8
- package/src/superlocalmemory/server/routes/entity.py +108 -26
- package/src/superlocalmemory/server/routes/events.py +24 -8
- package/src/superlocalmemory/server/routes/evolution.py +189 -68
- 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 +289 -118
- package/src/superlocalmemory/server/routes/learning_telemetry.py +153 -0
- package/src/superlocalmemory/server/routes/lifecycle.py +59 -8
- package/src/superlocalmemory/server/routes/memories.py +182 -57
- package/src/superlocalmemory/server/routes/mesh.py +200 -31
- 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 +132 -0
- package/src/superlocalmemory/server/routes/rbac.py +367 -0
- package/src/superlocalmemory/server/routes/stats.py +103 -158
- package/src/superlocalmemory/server/routes/tiers.py +11 -9
- package/src/superlocalmemory/server/routes/token.py +3 -13
- package/src/superlocalmemory/server/routes/v3_api.py +247 -89
- package/src/superlocalmemory/server/routes/ws.py +5 -2
- package/src/superlocalmemory/server/security_middleware.py +12 -5
- package/src/superlocalmemory/server/ui.py +20 -5
- package/src/superlocalmemory/server/unified_daemon.py +827 -72
- package/src/superlocalmemory/server/write_identity.py +38 -8
- package/src/superlocalmemory/storage/database.py +265 -53
- package/src/superlocalmemory/storage/migration_runner.py +132 -1
- package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +5 -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/migrations/M028_fact_entity_associations.py +270 -0
- package/src/superlocalmemory/storage/migrations/M029_behavioral_history_indexes.py +137 -0
- package/src/superlocalmemory/storage/migrations/M030_entity_explorer_indexes.py +93 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +4 -0
- package/src/superlocalmemory/storage/models.py +4 -0
- package/src/superlocalmemory/storage/schema.py +136 -1
- package/src/superlocalmemory/storage/schema_v32.py +2 -0
- package/src/superlocalmemory/storage/schema_v343.py +24 -12
- package/src/superlocalmemory/storage/schema_v347.py +4 -0
- 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 +303 -173
- package/src/superlocalmemory/ui/js/brain.js +5 -20
- package/src/superlocalmemory/ui/js/core.js +100 -41
- package/src/superlocalmemory/ui/js/dashboard.js +403 -65
- 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 +871 -0
- package/src/superlocalmemory/ui/js/od-brain.js +816 -0
- package/src/superlocalmemory/ui/js/od-entities.js +579 -0
- package/src/superlocalmemory/ui/js/od-graph.js +600 -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 +929 -0
- package/src/superlocalmemory/ui/js/od-mesh.js +553 -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 +1107 -0
- package/src/superlocalmemory/ui/js/od-shell.js +809 -0
- package/src/superlocalmemory/ui/js/od-skills.js +600 -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 +17 -3
- 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/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 -23
- package/plugin-src/scripts/slm-launch.bat +0 -23
- package/plugin-src/settings.json +0 -16
- package/src/superlocalmemory/learning/behavioral_listener.py +0 -94
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Pure decision predicates for the bounded-loop engine.
|
|
2
|
+
|
|
3
|
+
Every function here is a pure function of its arguments: same inputs give the
|
|
4
|
+
same output, and nothing is mutated. The engine calls these to decide whether
|
|
5
|
+
to stop, halt, or keep going; adapters never call them directly.
|
|
6
|
+
|
|
7
|
+
Imports are limited to the standard library and the loop models.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Sequence
|
|
13
|
+
|
|
14
|
+
from superlocalmemory.loops.models import Bounds, Rung, Verdict
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def stop_condition_met(verdict: Verdict) -> bool:
|
|
18
|
+
"""Return True when the gate verdict means the loop may exit.
|
|
19
|
+
|
|
20
|
+
The rule is deliberately conservative: a loop is eligible to finish only
|
|
21
|
+
when the independent gate reports ``passed``. The agent's own opinion is
|
|
22
|
+
never consulted here — it is not even an argument.
|
|
23
|
+
"""
|
|
24
|
+
return verdict.passed
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def no_progress(lap_changes: Sequence[bool], window: int) -> bool:
|
|
28
|
+
"""Return True when the last ``window`` laps all made no change.
|
|
29
|
+
|
|
30
|
+
``lap_changes`` is the ordered history of each lap's ``changed`` flag,
|
|
31
|
+
most-recent last. A window of ``0`` disables the check (a spinning agent
|
|
32
|
+
is then bounded only by the iteration cap). Fewer laps than the window
|
|
33
|
+
means "not enough evidence yet" and returns False.
|
|
34
|
+
"""
|
|
35
|
+
if window <= 0:
|
|
36
|
+
return False
|
|
37
|
+
tail = lap_changes[-window:]
|
|
38
|
+
if len(tail) < window:
|
|
39
|
+
return False
|
|
40
|
+
return all(changed is False for changed in tail)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def rung_requires_approval(rung: Rung, bounds: Bounds) -> bool:
|
|
44
|
+
"""Return True when a human must approve a passing gate before DONE.
|
|
45
|
+
|
|
46
|
+
An explicit ``bounds.require_approval`` wins outright. When it is ``None``
|
|
47
|
+
the posture is derived from the rung: L1 exits without approval, while
|
|
48
|
+
L2 and L3 require it.
|
|
49
|
+
"""
|
|
50
|
+
if bounds.require_approval is not None:
|
|
51
|
+
return bounds.require_approval
|
|
52
|
+
return rung in (Rung.L2, Rung.L3)
|
|
@@ -43,6 +43,7 @@ class DaemonPoolProxy:
|
|
|
43
43
|
fast: bool = False,
|
|
44
44
|
include_global: bool | None = None,
|
|
45
45
|
include_shared: bool | None = None,
|
|
46
|
+
window: str | None = None,
|
|
46
47
|
) -> dict[str, Any]:
|
|
47
48
|
_params: dict[str, Any] = {
|
|
48
49
|
"q": query,
|
|
@@ -57,6 +58,8 @@ class DaemonPoolProxy:
|
|
|
57
58
|
_params["include_global"] = "true" if include_global else "false"
|
|
58
59
|
if include_shared is not None:
|
|
59
60
|
_params["include_shared"] = "true" if include_shared else "false"
|
|
61
|
+
if window:
|
|
62
|
+
_params["window"] = window
|
|
60
63
|
params = urllib.parse.urlencode(_params)
|
|
61
64
|
try:
|
|
62
65
|
from superlocalmemory.cli.daemon import daemon_request
|
|
@@ -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
|
|
|
@@ -233,7 +203,7 @@ class _FilteredServer:
|
|
|
233
203
|
"""
|
|
234
204
|
__slots__ = ("_server", "_allowed")
|
|
235
205
|
|
|
236
|
-
def __init__(self, real_server:
|
|
206
|
+
def __init__(self, real_server: SLMFastMCP, allowed: frozenset[str]) -> None:
|
|
237
207
|
self._server = real_server
|
|
238
208
|
self._allowed = allowed
|
|
239
209
|
|
|
@@ -291,31 +261,24 @@ 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
|
-
#
|
|
297
|
-
#
|
|
298
|
-
#
|
|
299
|
-
#
|
|
300
|
-
# the first tool call arrives (1-2s later), the engine is already warm.
|
|
301
|
-
# This applies to ALL IDEs: Claude Code, Cursor, Antigravity, Gemini CLI, etc.
|
|
268
|
+
# Keep stdio MCP processes thin until a tool truly needs a local LIGHT engine.
|
|
269
|
+
# Every open IDE/task owns a stdio process; eagerly opening memory.db in all of
|
|
270
|
+
# them multiplied RAM and SQLite writers on machines with many long-lived
|
|
271
|
+
# sessions. The shared daemon owns model warmup and common remember/recall work.
|
|
302
272
|
def _eager_warmup() -> None:
|
|
303
|
-
"""
|
|
273
|
+
"""Ensure the shared daemon is running without opening a per-client engine.
|
|
304
274
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
275
|
+
Mesh registration is intentionally lazy: a local stdio session is not a
|
|
276
|
+
remote peer, and heartbeat writes should begin only after a mesh tool is
|
|
277
|
+
actually used.
|
|
308
278
|
"""
|
|
309
|
-
import logging
|
|
310
279
|
_logger = logging.getLogger(__name__)
|
|
311
|
-
try:
|
|
312
|
-
get_engine()
|
|
313
|
-
_logger.info("MCP engine pre-warmed successfully")
|
|
314
|
-
except Exception as exc:
|
|
315
|
-
_logger.warning("MCP engine pre-warmup failed: %s", exc)
|
|
316
280
|
|
|
317
|
-
# Measurement / test harnesses set this to skip daemon-start
|
|
318
|
-
# mesh-register. The LIGHT engine init above still runs.
|
|
281
|
+
# Measurement / test harnesses set this to skip daemon-start.
|
|
319
282
|
if _os.environ.get("SLM_DISABLE_WARMUP_SIDE_EFFECTS") == "1":
|
|
320
283
|
return
|
|
321
284
|
|
|
@@ -328,22 +291,12 @@ def _eager_warmup() -> None:
|
|
|
328
291
|
except Exception as exc:
|
|
329
292
|
_logger.warning("Daemon auto-start failed: %s", exc)
|
|
330
293
|
|
|
331
|
-
# V3.4.6: Auto-register this MCP session as a mesh peer immediately.
|
|
332
|
-
# Previously, registration was lazy (only on first mesh tool call).
|
|
333
|
-
# Now every Claude session appears on the mesh from startup.
|
|
334
|
-
try:
|
|
335
|
-
from superlocalmemory.mcp.tools_mesh import auto_register_mesh
|
|
336
|
-
auto_register_mesh()
|
|
337
|
-
_logger.info("Mesh peer auto-registered at startup")
|
|
338
|
-
except Exception as exc:
|
|
339
|
-
_logger.warning("Mesh auto-register failed: %s", exc)
|
|
340
|
-
|
|
341
294
|
import threading
|
|
342
295
|
|
|
343
296
|
# v3.6.7: Suppress standalone-process behaviours when the MCP server is
|
|
344
297
|
# imported inside the daemon (SLM_MCP_EMBEDDED=1). Three threads are safe
|
|
345
298
|
# to run in a dedicated `slm mcp` subprocess but harmful inside the daemon:
|
|
346
|
-
# mcp-warmup —
|
|
299
|
+
# mcp-warmup — ensures the shared daemon only; never creates an engine.
|
|
347
300
|
# parent-watchdog — calls os._exit(0) if its parent IDE quits, which would
|
|
348
301
|
# kill the daemon along with it.
|
|
349
302
|
# stdin-eof-monitor — monitors stdin pipe; meaningless inside the daemon.
|
|
@@ -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
|
|
@@ -84,8 +84,9 @@ def _record_recall_hits(
|
|
|
84
84
|
enqueue_shown_flip,
|
|
85
85
|
)
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
pid = profile_id
|
|
88
|
+
if not pid:
|
|
89
|
+
pid = get_engine().profile_id
|
|
89
90
|
slm_dir = canonical_data_root()
|
|
90
91
|
|
|
91
92
|
shown_ids = [r.get("fact_id", "") for r in results[:10]
|
|
@@ -121,7 +122,7 @@ def _record_recall_hits(
|
|
|
121
122
|
def register_core_tools(server, get_engine: Callable) -> None:
|
|
122
123
|
"""Register the 13 core MCP tools on *server*."""
|
|
123
124
|
|
|
124
|
-
@server.tool(
|
|
125
|
+
@server.tool()
|
|
125
126
|
async def remember(
|
|
126
127
|
content: str, tags: str = "", project: str = "",
|
|
127
128
|
importance: int = 5, session_id: str = "",
|
|
@@ -284,10 +285,11 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
284
285
|
session_id: str = "", fast: bool = False,
|
|
285
286
|
include_global: bool | None = None,
|
|
286
287
|
include_shared: bool | None = None,
|
|
288
|
+
window: str = "",
|
|
287
289
|
) -> dict:
|
|
288
290
|
"""Search memories through hybrid retrieval, RRF fusion, and reranking.
|
|
289
291
|
|
|
290
|
-
|
|
292
|
+
Optional ``session_id`` threads through to the
|
|
291
293
|
engine's outcome-queue so PostToolUse / Stop hooks can attach
|
|
292
294
|
engagement signals to this recall. Claude Code should pass its
|
|
293
295
|
``CLAUDE_SESSION_ID``. Omitting it degrades to "no closed-loop
|
|
@@ -297,6 +299,11 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
297
299
|
scopes participate in retrieval. Leave them unset (``None``) to use the
|
|
298
300
|
configured default — shared memory is OPT-IN, so by default recall
|
|
299
301
|
returns only this profile's own facts. Pass ``True`` to opt in per call.
|
|
302
|
+
|
|
303
|
+
Time window: optional ``window`` restricts results to a event-time
|
|
304
|
+
range. Accepts a relative span (``"24h"``, ``"7d"``, ``"30d"``,
|
|
305
|
+
``"1y"``) or an explicit range (``"2026-07-01..2026-07-31"``). Empty =
|
|
306
|
+
no time filter.
|
|
300
307
|
"""
|
|
301
308
|
# v3.6.10: resolve "mcp_client" sentinel → URL path (HTTP) or env var (stdio)
|
|
302
309
|
if agent_id == "mcp_client":
|
|
@@ -353,6 +360,7 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
353
360
|
fast=bool(fast),
|
|
354
361
|
include_global=include_global,
|
|
355
362
|
include_shared=include_shared,
|
|
363
|
+
window=window or None,
|
|
356
364
|
)
|
|
357
365
|
if result.get("ok"):
|
|
358
366
|
# Record implicit feedback: every returned result is a recall_hit
|
|
@@ -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]
|
|
@@ -143,7 +143,7 @@ def register_learning_tools(server, get_engine: Callable) -> None:
|
|
|
143
143
|
logger.debug("get_assertions failed: %s", exc)
|
|
144
144
|
return {"assertions": [], "count": 0, "error": str(exc)}
|
|
145
145
|
|
|
146
|
-
@server.tool(
|
|
146
|
+
@server.tool()
|
|
147
147
|
async def reinforce_assertion(assertion_id: str) -> dict:
|
|
148
148
|
"""Reinforce a behavioral assertion (increase confidence).
|
|
149
149
|
|
|
@@ -171,7 +171,7 @@ def register_learning_tools(server, get_engine: Callable) -> None:
|
|
|
171
171
|
except Exception as exc:
|
|
172
172
|
return {"success": False, "error": str(exc)}
|
|
173
173
|
|
|
174
|
-
@server.tool(
|
|
174
|
+
@server.tool()
|
|
175
175
|
async def contradict_assertion(assertion_id: str) -> dict:
|
|
176
176
|
"""Contradict a behavioral assertion (decrease confidence).
|
|
177
177
|
|