superlocalmemory 3.8.13 → 4.0.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/ATTRIBUTION.md +4 -4
- package/CHANGELOG.md +113 -121
- package/README.md +65 -63
- package/docs/pi-dev-integration.md +1 -1
- package/package.json +6 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/CLAUDE.md +3 -3
- package/plugin/agents/slm-governance-advisor.md +1 -1
- package/plugin/agents/slm-loop-runner.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/agents/slm-optimize-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-cache/SKILL.md +1 -1
- package/plugin/skills/slm-compress/SKILL.md +1 -1
- package/plugin/skills/slm-governance/SKILL.md +1 -1
- package/plugin/skills/slm-graph/SKILL.md +1 -1
- package/plugin/skills/slm-loop/SKILL.md +1 -1
- package/plugin/skills/slm-mesh/SKILL.md +1 -1
- package/plugin/skills/slm-profile/SKILL.md +1 -1
- package/plugin/skills/slm-recall/SKILL.md +1 -1
- package/plugin/skills/slm-remember/SKILL.md +1 -1
- package/plugin/skills/slm-scope/SKILL.md +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin/skills/slm-status/SKILL.md +1 -1
- package/plugin-src/rules/AGENTS.md +1 -1
- package/plugin-src/skills/slm-cache/SKILL.md +1 -1
- package/plugin-src/skills/slm-compress/SKILL.md +1 -1
- package/plugin-src/skills/slm-governance/SKILL.md +248 -0
- package/plugin-src/skills/slm-graph/SKILL.md +1 -1
- 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 +1 -1
- package/plugin-src/skills/slm-remember/SKILL.md +1 -1
- package/plugin-src/skills/slm-scope/SKILL.md +176 -0
- package/plugin-src/skills/slm-session/SKILL.md +1 -1
- package/plugin-src/skills/slm-status/SKILL.md +1 -1
- package/pyproject.toml +11 -4
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/commands.py +125 -11
- package/src/superlocalmemory/cli/daemon.py +5 -1
- package/src/superlocalmemory/cli/main.py +35 -2
- package/src/superlocalmemory/cli/ops_cmd.py +281 -0
- package/src/superlocalmemory/cli/setup_wizard.py +1 -1
- package/src/superlocalmemory/compliance/audit.py +65 -0
- package/src/superlocalmemory/compliance/eu_ai_act.py +27 -57
- package/src/superlocalmemory/compliance/gdpr.py +416 -20
- package/src/superlocalmemory/compliance/retention.py +74 -22
- package/src/superlocalmemory/compliance/scheduler.py +78 -9
- package/src/superlocalmemory/core/actor_context.py +166 -0
- package/src/superlocalmemory/core/admission.py +549 -0
- package/src/superlocalmemory/core/backend_orchestrator.py +23 -10
- package/src/superlocalmemory/core/config.py +202 -24
- package/src/superlocalmemory/core/consolidation_engine.py +13 -13
- package/src/superlocalmemory/core/context_cache.py +28 -0
- package/src/superlocalmemory/core/embeddings.py +64 -2
- package/src/superlocalmemory/core/engine.py +7 -2
- package/src/superlocalmemory/core/engine_ingestion.py +65 -3
- package/src/superlocalmemory/core/engine_wiring.py +36 -9
- package/src/superlocalmemory/core/ingest_policy.py +38 -0
- package/src/superlocalmemory/core/maintenance.py +255 -0
- package/src/superlocalmemory/core/modes.py +40 -13
- package/src/superlocalmemory/core/mutations.py +437 -44
- package/src/superlocalmemory/core/operation_policy.py +92 -0
- package/src/superlocalmemory/core/operation_policy_registry.py +542 -0
- package/src/superlocalmemory/core/operation_request.py +127 -0
- package/src/superlocalmemory/core/ops_remediation.py +542 -0
- package/src/superlocalmemory/core/recall_pipeline.py +7 -0
- package/src/superlocalmemory/core/remember_runtime.py +202 -4
- package/src/superlocalmemory/core/remote_mode.py +20 -5
- package/src/superlocalmemory/core/store_pipeline.py +150 -0
- package/src/superlocalmemory/core/topic_signature.py +19 -4
- package/src/superlocalmemory/core/transactions/__init__.py +78 -0
- package/src/superlocalmemory/core/transactions/concrete_owners.py +597 -0
- package/src/superlocalmemory/core/transactions/erasure.py +825 -0
- package/src/superlocalmemory/core/transactions/manifest.py +255 -0
- package/src/superlocalmemory/core/transactions/manifest_key.py +155 -0
- package/src/superlocalmemory/core/transactions/obligations.py +272 -0
- package/src/superlocalmemory/core/transactions/owners.py +114 -0
- package/src/superlocalmemory/core/transactions/reconciler.py +285 -0
- package/src/superlocalmemory/core/transactions/service.py +330 -0
- package/src/superlocalmemory/core/worker_pool.py +33 -5
- package/src/superlocalmemory/encoding/cognitive_consolidator.py +70 -28
- package/src/superlocalmemory/encoding/emotional.py +75 -14
- package/src/superlocalmemory/encoding/scene_builder.py +115 -13
- package/src/superlocalmemory/encoding/temporal_parser.py +4 -0
- package/src/superlocalmemory/evolution/blind_verifier.py +11 -4
- package/src/superlocalmemory/evolution/evolution_store.py +244 -4
- package/src/superlocalmemory/evolution/llm_dispatch.py +40 -0
- package/src/superlocalmemory/evolution/model_selection.py +18 -3
- package/src/superlocalmemory/evolution/mutation_generator.py +3 -0
- package/src/superlocalmemory/evolution/skill_activator.py +270 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +281 -59
- package/src/superlocalmemory/evolution/types.py +30 -8
- package/src/superlocalmemory/graph/cozo_backend.py +17 -9
- package/src/superlocalmemory/hooks/auto_invoker.py +2 -1
- package/src/superlocalmemory/hooks/auto_recall.py +64 -30
- package/src/superlocalmemory/hooks/codex_assets.py +14 -1
- package/src/superlocalmemory/infra/backup.py +434 -7
- package/src/superlocalmemory/infra/process_reaper.py +18 -0
- package/src/superlocalmemory/infra/self_heal.py +401 -0
- package/src/superlocalmemory/learning/feedback.py +52 -9
- package/src/superlocalmemory/loops/engine.py +10 -0
- package/src/superlocalmemory/mcp/_daemon_proxy.py +3 -0
- package/src/superlocalmemory/mcp/http_transport.py +30 -331
- package/src/superlocalmemory/mcp/profiles.py +5 -0
- package/src/superlocalmemory/mcp/resources.py +8 -0
- package/src/superlocalmemory/mcp/server.py +51 -4
- package/src/superlocalmemory/mcp/shared.py +19 -0
- package/src/superlocalmemory/mcp/tools_active.py +25 -4
- package/src/superlocalmemory/mcp/tools_code_graph.py +26 -18
- package/src/superlocalmemory/mcp/tools_context.py +50 -8
- package/src/superlocalmemory/mcp/tools_core.py +69 -21
- package/src/superlocalmemory/mcp/tools_evolution.py +9 -2
- package/src/superlocalmemory/mcp/tools_learning.py +21 -10
- package/src/superlocalmemory/mcp/tools_loops.py +29 -18
- package/src/superlocalmemory/mcp/tools_mesh.py +8 -0
- package/src/superlocalmemory/mcp/tools_ops.py +115 -0
- package/src/superlocalmemory/mcp/tools_optimize.py +4 -0
- package/src/superlocalmemory/mcp/tools_v28.py +10 -3
- package/src/superlocalmemory/mcp/tools_v3.py +34 -14
- package/src/superlocalmemory/mcp/tools_v33.py +18 -33
- package/src/superlocalmemory/mesh/broker.py +124 -46
- package/src/superlocalmemory/mesh/broker_security.py +470 -0
- package/src/superlocalmemory/mesh/discovery.py +365 -0
- package/src/superlocalmemory/mesh/lock_protocol.py +313 -0
- package/src/superlocalmemory/mesh/node_identity.py +97 -0
- package/src/superlocalmemory/mesh/outbox_remote.py +429 -0
- package/src/superlocalmemory/mesh/remote_sync.py +511 -28
- package/src/superlocalmemory/mesh/state_sync.py +286 -0
- package/src/superlocalmemory/optimize/config/store.py +45 -0
- package/src/superlocalmemory/parameterization/cross_project.py +12 -0
- package/src/superlocalmemory/parameterization/prompt_injector.py +13 -11
- package/src/superlocalmemory/parameterization/prompt_lifecycle.py +8 -2
- package/src/superlocalmemory/parameterization/workflow_miner.py +17 -0
- package/src/superlocalmemory/retrieval/ann_index.py +5 -0
- package/src/superlocalmemory/retrieval/bm25_channel.py +49 -2
- package/src/superlocalmemory/retrieval/engine.py +19 -4
- package/src/superlocalmemory/retrieval/fusion.py +4 -1
- package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -3
- package/src/superlocalmemory/retrieval/remote_reranker.py +47 -22
- package/src/superlocalmemory/retrieval/reranker.py +32 -1
- package/src/superlocalmemory/retrieval/temporal_channel.py +16 -3
- package/src/superlocalmemory/retrieval/temporal_utils.py +107 -0
- package/src/superlocalmemory/retrieval/temporal_validity_filter.py +155 -42
- package/src/superlocalmemory/retrieval/vector_store.py +214 -8
- package/src/superlocalmemory/server/api.py +5 -5
- package/src/superlocalmemory/server/egress_policy.py +258 -0
- package/src/superlocalmemory/server/rbac_enforce.py +32 -0
- package/src/superlocalmemory/server/route_mutations.py +20 -0
- package/src/superlocalmemory/server/routes/compliance.py +153 -7
- package/src/superlocalmemory/server/routes/data_io.py +43 -2
- package/src/superlocalmemory/server/routes/events.py +15 -0
- package/src/superlocalmemory/server/routes/memories.py +56 -3
- package/src/superlocalmemory/server/routes/mesh.py +82 -1
- package/src/superlocalmemory/server/routes/mesh_lock.py +54 -0
- package/src/superlocalmemory/server/routes/mesh_state.py +63 -0
- package/src/superlocalmemory/server/routes/v3_api.py +50 -27
- package/src/superlocalmemory/server/routes/ws.py +86 -0
- package/src/superlocalmemory/server/ui.py +6 -6
- package/src/superlocalmemory/server/unified_daemon.py +942 -119
- package/src/superlocalmemory/storage/_migration_internals.py +568 -0
- package/src/superlocalmemory/storage/_schema_version.py +110 -0
- package/src/superlocalmemory/storage/database.py +329 -24
- package/src/superlocalmemory/storage/embedding_migrator.py +246 -51
- package/src/superlocalmemory/storage/erasure_fence.py +45 -0
- package/src/superlocalmemory/storage/generation_fence.py +63 -0
- package/src/superlocalmemory/storage/migration_runner.py +140 -417
- package/src/superlocalmemory/storage/migrations/M009_model_lineage.py +40 -0
- package/src/superlocalmemory/storage/migrations/M033_projection_transactions.py +148 -0
- package/src/superlocalmemory/storage/migrations/M034_obligation_integrity.py +58 -0
- package/src/superlocalmemory/storage/migrations/M035_erasure_receipts.py +113 -0
- package/src/superlocalmemory/storage/migrations/M036_vector_row_map.py +107 -0
- package/src/superlocalmemory/storage/migrations/M037_manifest_hmac_version.py +162 -0
- package/src/superlocalmemory/storage/migrations/{M033_learning_feedback_channel.py → M038_learning_feedback_channel.py} +3 -3
- package/src/superlocalmemory/storage/migrations/M039_scene_fact_members.py +137 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +4 -2
- package/src/superlocalmemory/storage/schema.py +67 -0
- package/src/superlocalmemory/storage/write_coordinator.py +125 -0
- package/src/superlocalmemory/trust/scorer.py +28 -4
- package/src/superlocalmemory/ui/index.html +14 -3
- package/src/superlocalmemory/ui/js/auto-settings.js +12 -1
- package/src/superlocalmemory/ui/js/brain.js +6 -4
- package/src/superlocalmemory/ui/js/compliance.js +66 -12
- package/src/superlocalmemory/ui/js/dashboard.js +13 -3
- package/src/superlocalmemory/ui/js/feedback.js +8 -2
- package/src/superlocalmemory/ui/js/lifecycle.js +7 -1
- package/src/superlocalmemory/ui/js/modal.js +272 -5
- package/src/superlocalmemory/ui/js/od-backup.js +9 -2
- package/src/superlocalmemory/ui/js/od-compliance-ext.js +301 -0
- package/src/superlocalmemory/ui/js/od-operations.js +154 -23
- package/src/superlocalmemory/ui/js/od-ops-health.js +417 -0
- package/src/superlocalmemory/ui/js/od-optimize.js +35 -21
- package/src/superlocalmemory/ui/js/od-team.js +9 -2
- package/src/superlocalmemory/ui/js/optimize.js +13 -16
- package/src/superlocalmemory/ui/js/profiles.js +7 -3
- package/src/superlocalmemory/ui/js/settings.js +7 -1
- package/src/superlocalmemory/vector/lancedb_backend.py +19 -9
- package/src/superlocalmemory/attribution/mathematical_dna.py +0 -235
- package/src/superlocalmemory/cli/post_install.py +0 -114
- package/src/superlocalmemory/core/clock_monitor.py +0 -45
- package/src/superlocalmemory/core/db_pool.py +0 -80
- package/src/superlocalmemory/core/error_catalog.py +0 -113
- package/src/superlocalmemory/core/loop_watchdog.py +0 -56
- package/src/superlocalmemory/core/priority_queue.py +0 -61
- package/src/superlocalmemory/core/pruning_engine.py +0 -216
- package/src/superlocalmemory/core/queue_dispatcher.py +0 -73
- package/src/superlocalmemory/core/slmignore.py +0 -125
- package/src/superlocalmemory/infra/heartbeat_monitor.py +0 -140
- package/src/superlocalmemory/infra/webhook_dispatcher.py +0 -247
- package/src/superlocalmemory/learning/quantization_scheduler.py +0 -320
- package/src/superlocalmemory/storage/access_control.py +0 -182
|
@@ -19,6 +19,10 @@ import time
|
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
from typing import Any, Callable
|
|
21
21
|
|
|
22
|
+
from mcp.types import ToolAnnotations
|
|
23
|
+
|
|
24
|
+
from superlocalmemory.core.admission import admits
|
|
25
|
+
from superlocalmemory.core.operation_request import OperationKind
|
|
22
26
|
from superlocalmemory.core.security_primitives import (
|
|
23
27
|
PathTraversalError,
|
|
24
28
|
safe_resolve,
|
|
@@ -108,6 +112,7 @@ def register_code_graph_tools(server, get_engine: Callable) -> None:
|
|
|
108
112
|
# ==================================================================
|
|
109
113
|
|
|
110
114
|
@server.tool()
|
|
115
|
+
@admits(OperationKind.CORRECT)
|
|
111
116
|
async def build_code_graph(
|
|
112
117
|
repo_path: str,
|
|
113
118
|
languages: str = "",
|
|
@@ -242,6 +247,7 @@ def register_code_graph_tools(server, get_engine: Callable) -> None:
|
|
|
242
247
|
# ==================================================================
|
|
243
248
|
|
|
244
249
|
@server.tool()
|
|
250
|
+
@admits(OperationKind.CORRECT)
|
|
245
251
|
async def update_code_graph(
|
|
246
252
|
repo_path: str = "",
|
|
247
253
|
changed_files: str = "",
|
|
@@ -390,7 +396,7 @@ def register_code_graph_tools(server, get_engine: Callable) -> None:
|
|
|
390
396
|
# Tool 3: get_blast_radius
|
|
391
397
|
# ==================================================================
|
|
392
398
|
|
|
393
|
-
@server.tool()
|
|
399
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
394
400
|
async def get_blast_radius(
|
|
395
401
|
changed_files: str,
|
|
396
402
|
max_depth: int = 2,
|
|
@@ -452,7 +458,7 @@ def register_code_graph_tools(server, get_engine: Callable) -> None:
|
|
|
452
458
|
# Tool 4: get_review_context
|
|
453
459
|
# ==================================================================
|
|
454
460
|
|
|
455
|
-
@server.tool()
|
|
461
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
456
462
|
async def get_review_context(
|
|
457
463
|
changed_files: str,
|
|
458
464
|
include_source: bool = True,
|
|
@@ -509,7 +515,7 @@ def register_code_graph_tools(server, get_engine: Callable) -> None:
|
|
|
509
515
|
"tests_for", "inherits_from", "inherited_by", "contains",
|
|
510
516
|
})
|
|
511
517
|
|
|
512
|
-
@server.tool()
|
|
518
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
513
519
|
async def query_graph(
|
|
514
520
|
pattern: str,
|
|
515
521
|
target: str = "",
|
|
@@ -608,7 +614,7 @@ def register_code_graph_tools(server, get_engine: Callable) -> None:
|
|
|
608
614
|
# Tool 6: semantic_search_code
|
|
609
615
|
# ==================================================================
|
|
610
616
|
|
|
611
|
-
@server.tool()
|
|
617
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
612
618
|
async def semantic_search_code(
|
|
613
619
|
query: str,
|
|
614
620
|
kind: str = "",
|
|
@@ -658,7 +664,7 @@ def register_code_graph_tools(server, get_engine: Callable) -> None:
|
|
|
658
664
|
# Tool 7: list_graph_stats
|
|
659
665
|
# ==================================================================
|
|
660
666
|
|
|
661
|
-
@server.tool()
|
|
667
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
662
668
|
async def list_graph_stats() -> dict:
|
|
663
669
|
"""Get code graph size and health metrics."""
|
|
664
670
|
try:
|
|
@@ -705,7 +711,7 @@ def register_code_graph_tools(server, get_engine: Callable) -> None:
|
|
|
705
711
|
# Tool 8: find_large_functions
|
|
706
712
|
# ==================================================================
|
|
707
713
|
|
|
708
|
-
@server.tool()
|
|
714
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
709
715
|
async def find_large_functions(
|
|
710
716
|
threshold: int = 50,
|
|
711
717
|
limit: int = 20,
|
|
@@ -752,7 +758,7 @@ def register_code_graph_tools(server, get_engine: Callable) -> None:
|
|
|
752
758
|
# Tool 9: list_flows
|
|
753
759
|
# ==================================================================
|
|
754
760
|
|
|
755
|
-
@server.tool()
|
|
761
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
756
762
|
async def list_flows(
|
|
757
763
|
sort_by: str = "criticality",
|
|
758
764
|
limit: int = 20,
|
|
@@ -809,7 +815,7 @@ def register_code_graph_tools(server, get_engine: Callable) -> None:
|
|
|
809
815
|
# Tool 10: get_flow
|
|
810
816
|
# ==================================================================
|
|
811
817
|
|
|
812
|
-
@server.tool()
|
|
818
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
813
819
|
async def get_flow(
|
|
814
820
|
flow_name: str,
|
|
815
821
|
) -> dict:
|
|
@@ -860,7 +866,7 @@ def register_code_graph_tools(server, get_engine: Callable) -> None:
|
|
|
860
866
|
# Tool 11: get_affected_flows
|
|
861
867
|
# ==================================================================
|
|
862
868
|
|
|
863
|
-
@server.tool()
|
|
869
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
864
870
|
async def get_affected_flows(
|
|
865
871
|
changed_files: str,
|
|
866
872
|
) -> dict:
|
|
@@ -920,7 +926,7 @@ def register_code_graph_tools(server, get_engine: Callable) -> None:
|
|
|
920
926
|
# Tool 12: list_communities
|
|
921
927
|
# ==================================================================
|
|
922
928
|
|
|
923
|
-
@server.tool()
|
|
929
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
924
930
|
async def list_communities(
|
|
925
931
|
sort_by: str = "cohesion",
|
|
926
932
|
limit: int = 20,
|
|
@@ -967,7 +973,7 @@ def register_code_graph_tools(server, get_engine: Callable) -> None:
|
|
|
967
973
|
# Tool 13: get_community
|
|
968
974
|
# ==================================================================
|
|
969
975
|
|
|
970
|
-
@server.tool()
|
|
976
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
971
977
|
async def get_community(
|
|
972
978
|
community_id: int,
|
|
973
979
|
) -> dict:
|
|
@@ -1014,7 +1020,7 @@ def register_code_graph_tools(server, get_engine: Callable) -> None:
|
|
|
1014
1020
|
# Tool 14: get_architecture_overview
|
|
1015
1021
|
# ==================================================================
|
|
1016
1022
|
|
|
1017
|
-
@server.tool()
|
|
1023
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
1018
1024
|
async def get_architecture_overview() -> dict:
|
|
1019
1025
|
"""Get high-level architecture map showing communities and their relationships."""
|
|
1020
1026
|
try:
|
|
@@ -1063,7 +1069,7 @@ def register_code_graph_tools(server, get_engine: Callable) -> None:
|
|
|
1063
1069
|
# Tool 15: detect_changes
|
|
1064
1070
|
# ==================================================================
|
|
1065
1071
|
|
|
1066
|
-
@server.tool()
|
|
1072
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
1067
1073
|
async def detect_changes(
|
|
1068
1074
|
base: str = "HEAD~1",
|
|
1069
1075
|
) -> dict:
|
|
@@ -1122,7 +1128,7 @@ def register_code_graph_tools(server, get_engine: Callable) -> None:
|
|
|
1122
1128
|
# Tool 16: refactor_preview
|
|
1123
1129
|
# ==================================================================
|
|
1124
1130
|
|
|
1125
|
-
@server.tool()
|
|
1131
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
1126
1132
|
async def refactor_preview(
|
|
1127
1133
|
action: str,
|
|
1128
1134
|
target: str,
|
|
@@ -1216,6 +1222,7 @@ def register_code_graph_tools(server, get_engine: Callable) -> None:
|
|
|
1216
1222
|
# ==================================================================
|
|
1217
1223
|
|
|
1218
1224
|
@server.tool()
|
|
1225
|
+
@admits(OperationKind.CORRECT)
|
|
1219
1226
|
async def apply_refactor(
|
|
1220
1227
|
action: str,
|
|
1221
1228
|
target: str,
|
|
@@ -1250,7 +1257,7 @@ def register_code_graph_tools(server, get_engine: Callable) -> None:
|
|
|
1250
1257
|
# Tool 18 (BRIDGE): code_memory_search
|
|
1251
1258
|
# ==================================================================
|
|
1252
1259
|
|
|
1253
|
-
@server.tool()
|
|
1260
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
1254
1261
|
async def code_memory_search(
|
|
1255
1262
|
code_entity: str,
|
|
1256
1263
|
link_type: str = "",
|
|
@@ -1320,7 +1327,7 @@ def register_code_graph_tools(server, get_engine: Callable) -> None:
|
|
|
1320
1327
|
# Tool 19 (BRIDGE): code_entity_history
|
|
1321
1328
|
# ==================================================================
|
|
1322
1329
|
|
|
1323
|
-
@server.tool()
|
|
1330
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
1324
1331
|
async def code_entity_history(
|
|
1325
1332
|
code_entity: str,
|
|
1326
1333
|
) -> dict:
|
|
@@ -1390,7 +1397,7 @@ def register_code_graph_tools(server, get_engine: Callable) -> None:
|
|
|
1390
1397
|
# Tool 20 (BRIDGE): enrich_blast_radius
|
|
1391
1398
|
# ==================================================================
|
|
1392
1399
|
|
|
1393
|
-
@server.tool()
|
|
1400
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
1394
1401
|
async def enrich_blast_radius(
|
|
1395
1402
|
changed_files: str,
|
|
1396
1403
|
max_depth: int = 2,
|
|
@@ -1464,7 +1471,7 @@ def register_code_graph_tools(server, get_engine: Callable) -> None:
|
|
|
1464
1471
|
# Tool 21 (BRIDGE): code_stale_check
|
|
1465
1472
|
# ==================================================================
|
|
1466
1473
|
|
|
1467
|
-
@server.tool()
|
|
1474
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
1468
1475
|
async def code_stale_check(
|
|
1469
1476
|
scope: str = "all",
|
|
1470
1477
|
) -> dict:
|
|
@@ -1538,6 +1545,7 @@ def register_code_graph_tools(server, get_engine: Callable) -> None:
|
|
|
1538
1545
|
})
|
|
1539
1546
|
|
|
1540
1547
|
@server.tool()
|
|
1548
|
+
@admits(OperationKind.CORRECT)
|
|
1541
1549
|
async def link_memory_to_code(
|
|
1542
1550
|
fact_id: str,
|
|
1543
1551
|
code_entity: str,
|
|
@@ -21,6 +21,8 @@ from datetime import datetime, timezone
|
|
|
21
21
|
from threading import Lock
|
|
22
22
|
from typing import Callable
|
|
23
23
|
|
|
24
|
+
from mcp.types import ToolAnnotations
|
|
25
|
+
|
|
24
26
|
from superlocalmemory.core.security_primitives import redact_secrets
|
|
25
27
|
|
|
26
28
|
logger = logging.getLogger(__name__)
|
|
@@ -64,7 +66,10 @@ _DEFAULT_LIMITER = _RateLimiter()
|
|
|
64
66
|
|
|
65
67
|
|
|
66
68
|
# RecallFn for the tool. Callers inject a real recall engine; tests inject fakes.
|
|
67
|
-
|
|
69
|
+
# Phase 4b: extended to accept optional 4th positional arg (as_of: str | None).
|
|
70
|
+
# Legacy callers with a 3-arg signature are handled by the try/except TypeError
|
|
71
|
+
# fallback in prestage_context() (Decision A2, LLD §Module 8).
|
|
72
|
+
PrestageRecallFn = Callable[..., list[dict]]
|
|
68
73
|
|
|
69
74
|
|
|
70
75
|
def _iso_now() -> str:
|
|
@@ -100,12 +105,17 @@ def prestage_context(
|
|
|
100
105
|
session_id: str = "default",
|
|
101
106
|
recall_fn: PrestageRecallFn,
|
|
102
107
|
limiter: _RateLimiter | None = None,
|
|
108
|
+
as_of: str | None = None,
|
|
103
109
|
) -> dict:
|
|
104
110
|
"""Proactive-context tool body.
|
|
105
111
|
|
|
106
112
|
Pure function: takes an injected recall_fn + limiter. The MCP server
|
|
107
113
|
wrapper in the session process wires the real engine and shares a
|
|
108
114
|
single limiter instance.
|
|
115
|
+
|
|
116
|
+
Phase 4b: optional ``as_of`` (ISO 8601 UTC) for point-in-time context.
|
|
117
|
+
Normalized at entry; invalid input is silently ignored (background tool).
|
|
118
|
+
3-arg recall_fn callers are supported via TypeError fallback (Decision A2).
|
|
109
119
|
"""
|
|
110
120
|
_limiter = limiter or _DEFAULT_LIMITER
|
|
111
121
|
if not _limiter.allow(session_id):
|
|
@@ -127,8 +137,29 @@ def prestage_context(
|
|
|
127
137
|
}
|
|
128
138
|
limit = max(1, min(int(limit), 50))
|
|
129
139
|
|
|
140
|
+
# Normalize as_of at prestage boundary. Invalid input → silently ignore
|
|
141
|
+
# (prestage is background; user-facing surfaces raise instead).
|
|
142
|
+
_as_of: str | None = None
|
|
143
|
+
if as_of:
|
|
144
|
+
from superlocalmemory.retrieval.temporal_utils import normalize_as_of
|
|
145
|
+
_as_of = normalize_as_of(as_of)
|
|
146
|
+
if _as_of is None:
|
|
147
|
+
logger.warning("prestage_context: invalid as_of %r; ignoring", as_of)
|
|
148
|
+
|
|
130
149
|
try:
|
|
131
|
-
|
|
150
|
+
# Decision A2 (LLD §Module 8) + audit P2 (CRIT-3): decide arity by
|
|
151
|
+
# INSPECTING the callable, not by catching TypeError. A bare
|
|
152
|
+
# `except TypeError` would silently swallow a genuine TypeError raised
|
|
153
|
+
# inside a 4-arg recall_fn body and drop as_of (silent wrong PIT view).
|
|
154
|
+
import inspect as _inspect
|
|
155
|
+
try:
|
|
156
|
+
_arity = len(_inspect.signature(recall_fn).parameters)
|
|
157
|
+
except (TypeError, ValueError):
|
|
158
|
+
_arity = 4 # unintrospectable (partial/builtin) → assume as_of-capable
|
|
159
|
+
if _arity >= 4:
|
|
160
|
+
raw = recall_fn(query, limit, profile_id, _as_of) or []
|
|
161
|
+
else:
|
|
162
|
+
raw = recall_fn(query, limit, profile_id) or []
|
|
132
163
|
except Exception as exc:
|
|
133
164
|
logger.warning("prestage_context recall failed: %s", exc)
|
|
134
165
|
return {
|
|
@@ -172,20 +203,31 @@ def prestage_context(
|
|
|
172
203
|
def register_prestage_tool(server, recall_fn: PrestageRecallFn,
|
|
173
204
|
*, session_id_fn: Callable[[], str] | None = None
|
|
174
205
|
) -> None:
|
|
175
|
-
"""Register the ``prestage_context`` tool on an MCP server.
|
|
206
|
+
"""Register the ``prestage_context`` tool on an MCP server.
|
|
207
|
+
|
|
208
|
+
The tool function is named ``prestage_context`` so profile filters
|
|
209
|
+
(``func.__name__``) and docs agree.
|
|
210
|
+
"""
|
|
176
211
|
limiter = _RateLimiter()
|
|
212
|
+
# Module-level pure function (avoid UnboundLocalError from nested def name).
|
|
213
|
+
_pure_prestage = globals()["prestage_context"]
|
|
177
214
|
|
|
178
|
-
@server.tool()
|
|
179
|
-
async def
|
|
215
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
216
|
+
async def prestage_context( # noqa: F811 — MCP tool surface name
|
|
180
217
|
query: str,
|
|
181
218
|
limit: int = 5,
|
|
182
219
|
profile_id: str = "default",
|
|
220
|
+
as_of: str | None = None,
|
|
183
221
|
) -> dict:
|
|
184
|
-
"""Proactively return top-K memories for a query.
|
|
222
|
+
"""Proactively return top-K memories for a query.
|
|
223
|
+
|
|
224
|
+
Optional ``as_of`` (ISO 8601 UTC) for point-in-time context retrieval.
|
|
225
|
+
"""
|
|
185
226
|
session_id = session_id_fn() if session_id_fn else "default"
|
|
186
|
-
return
|
|
227
|
+
return _pure_prestage(
|
|
187
228
|
query, limit=limit, profile_id=profile_id,
|
|
188
|
-
session_id=session_id, recall_fn=recall_fn,
|
|
229
|
+
session_id=session_id, recall_fn=recall_fn,
|
|
230
|
+
limiter=limiter, as_of=as_of,
|
|
189
231
|
)
|
|
190
232
|
|
|
191
233
|
|
|
@@ -19,7 +19,9 @@ from typing import Callable
|
|
|
19
19
|
|
|
20
20
|
from mcp.types import ToolAnnotations
|
|
21
21
|
|
|
22
|
+
from superlocalmemory.core.admission import admits
|
|
22
23
|
from superlocalmemory.core.config import CANONICAL_RECALL_LIMIT
|
|
24
|
+
from superlocalmemory.core.operation_request import OperationKind
|
|
23
25
|
from superlocalmemory.infra.data_root import state_path
|
|
24
26
|
from superlocalmemory.mcp._daemon_proxy import daemon_unavailable_error
|
|
25
27
|
from superlocalmemory.mcp.shared import authorize_mcp_mutation
|
|
@@ -63,6 +65,7 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
63
65
|
"""Register the 13 core MCP tools on *server*."""
|
|
64
66
|
|
|
65
67
|
@server.tool()
|
|
68
|
+
@admits(OperationKind.REMEMBER)
|
|
66
69
|
async def remember(
|
|
67
70
|
content: str, tags: str = "", project: str = "",
|
|
68
71
|
importance: int = 5, session_id: str = "",
|
|
@@ -91,13 +94,25 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
91
94
|
"session_id": session_id,
|
|
92
95
|
}
|
|
93
96
|
effective_idempotency_key = idempotency_key
|
|
94
|
-
if not effective_idempotency_key
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
if not effective_idempotency_key:
|
|
98
|
+
# Derive a stable key before the first attempt so every retry of
|
|
99
|
+
# the same logical call carries the same key. When a session token
|
|
100
|
+
# is present it is included in the material to keep per-session
|
|
101
|
+
# stores separate. Without a session token the key is derived from
|
|
102
|
+
# the remaining call parameters so repeated observations with the
|
|
103
|
+
# same content, agent, and scope are deduplicated across retries.
|
|
104
|
+
if session_id:
|
|
105
|
+
material = (
|
|
106
|
+
f"{agent_id}\0{session_id}\0{scope or ''}\0{shared_with}\0{content}"
|
|
107
|
+
)
|
|
108
|
+
effective_idempotency_key = "mcp:" + hashlib.sha256(
|
|
109
|
+
material.encode("utf-8")
|
|
110
|
+
).hexdigest()
|
|
111
|
+
else:
|
|
112
|
+
material = f"{agent_id}\0{scope or ''}\0{shared_with}\0{content}"
|
|
113
|
+
effective_idempotency_key = "mcp:req:" + hashlib.sha256(
|
|
114
|
+
material.encode("utf-8")
|
|
115
|
+
).hexdigest()
|
|
101
116
|
# Parse shared_with from comma-separated string
|
|
102
117
|
_shared_list = [s.strip() for s in shared_with.split(",") if s.strip()] if shared_with else None
|
|
103
118
|
# v3.5.5 WRITE-THROUGH: route through the daemon's /remember, which does
|
|
@@ -109,6 +124,7 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
109
124
|
daemon_owned = False
|
|
110
125
|
try:
|
|
111
126
|
import asyncio as _asyncio
|
|
127
|
+
|
|
112
128
|
from superlocalmemory.cli.daemon import daemon_request, is_daemon_running
|
|
113
129
|
# is_daemon_running() and daemon_request() both use blocking urllib
|
|
114
130
|
# against the same uvicorn server — run in threads so the MCP
|
|
@@ -173,6 +189,7 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
173
189
|
|
|
174
190
|
try:
|
|
175
191
|
import asyncio as _asyncio
|
|
192
|
+
|
|
176
193
|
from superlocalmemory.mcp._daemon_proxy import choose_pool
|
|
177
194
|
|
|
178
195
|
worker_meta = {
|
|
@@ -247,12 +264,14 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
247
264
|
}
|
|
248
265
|
|
|
249
266
|
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
267
|
+
@admits(OperationKind.RECALL)
|
|
250
268
|
async def recall(
|
|
251
269
|
query: str, limit: int = CANONICAL_RECALL_LIMIT, agent_id: str = "mcp_client",
|
|
252
270
|
session_id: str = "", fast: bool | None = None,
|
|
253
271
|
include_global: bool | None = None,
|
|
254
272
|
include_shared: bool | None = None,
|
|
255
273
|
window: str = "",
|
|
274
|
+
as_of: str | None = None,
|
|
256
275
|
) -> dict:
|
|
257
276
|
"""Search memories through hybrid retrieval, RRF fusion, and reranking.
|
|
258
277
|
|
|
@@ -286,6 +305,10 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
286
305
|
range. Accepts a relative span (``"24h"``, ``"7d"``, ``"30d"``,
|
|
287
306
|
``"1y"``) or an explicit range (``"2026-07-01..2026-07-31"``). Empty =
|
|
288
307
|
no time filter.
|
|
308
|
+
|
|
309
|
+
Point-in-time: optional ``as_of`` (ISO-8601 string, e.g.
|
|
310
|
+
``"2026-01-01T00:00:00+00:00"``) pins recall to a temporal snapshot;
|
|
311
|
+
omit or pass ``None`` for current-state recall.
|
|
289
312
|
"""
|
|
290
313
|
# v3.6.10: resolve "mcp_client" sentinel → URL path (HTTP) or env var (stdio)
|
|
291
314
|
if agent_id == "mcp_client":
|
|
@@ -342,12 +365,28 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
342
365
|
#
|
|
343
366
|
# V3.4.26: WorkerPool now concurrent — parallel calls no longer
|
|
344
367
|
# block behind a single threading.Lock. See worker_pool.py.
|
|
368
|
+
# Phase 4b: normalize as_of at MCP boundary. Invalid → reject.
|
|
369
|
+
# Audit P2: treat empty/whitespace as_of as ABSENT (like HTTP does),
|
|
370
|
+
# not as an invalid value — only a non-blank unparseable string is
|
|
371
|
+
# rejected.
|
|
372
|
+
if as_of is not None and str(as_of).strip():
|
|
373
|
+
from superlocalmemory.retrieval.temporal_utils import normalize_as_of
|
|
374
|
+
as_of = normalize_as_of(as_of)
|
|
375
|
+
if as_of is None:
|
|
376
|
+
return {"success": False, "error": "invalid_as_of"}
|
|
377
|
+
else:
|
|
378
|
+
as_of = None
|
|
379
|
+
|
|
380
|
+
from superlocalmemory.core.admission import enforce_read_scope
|
|
381
|
+
_incl_global, _incl_shared = enforce_read_scope(include_global, include_shared)
|
|
382
|
+
|
|
345
383
|
def _recall_via_daemon_pool():
|
|
346
384
|
pool = choose_pool()
|
|
347
385
|
return pool.recall(
|
|
348
386
|
query, limit=limit, session_id=effective_sid,
|
|
349
|
-
fast=fast, include_global=
|
|
350
|
-
include_shared=
|
|
387
|
+
fast=fast, include_global=_incl_global,
|
|
388
|
+
include_shared=_incl_shared, window=window or None,
|
|
389
|
+
as_of=as_of,
|
|
351
390
|
)
|
|
352
391
|
|
|
353
392
|
result = await asyncio.to_thread(
|
|
@@ -376,11 +415,12 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
376
415
|
return {"success": False, "error": str(exc)}
|
|
377
416
|
|
|
378
417
|
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
379
|
-
|
|
418
|
+
@admits(OperationKind.RECALL)
|
|
419
|
+
async def search(query: str, limit: int = 10) -> dict:
|
|
380
420
|
"""Full-text search across memories using FTS5 with BM25 ranking."""
|
|
381
421
|
try:
|
|
382
422
|
engine = get_engine()
|
|
383
|
-
pid = await _runtime_profile(get_engine
|
|
423
|
+
pid = await _runtime_profile(get_engine)
|
|
384
424
|
facts = engine._db.search_facts_fts(query, pid, limit=limit)
|
|
385
425
|
items = []
|
|
386
426
|
for f in facts:
|
|
@@ -424,11 +464,11 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
424
464
|
return {"success": False, "error": str(exc)}
|
|
425
465
|
|
|
426
466
|
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
427
|
-
async def list_recent(limit: int = 20
|
|
467
|
+
async def list_recent(limit: int = 20) -> dict:
|
|
428
468
|
"""List most recently stored memories, newest first."""
|
|
429
469
|
try:
|
|
430
470
|
engine = get_engine()
|
|
431
|
-
pid = await _runtime_profile(get_engine
|
|
471
|
+
pid = await _runtime_profile(get_engine)
|
|
432
472
|
# v3.6.12 (search-2): push the limit into the query — was loading the
|
|
433
473
|
# ENTIRE facts table (deserializing every 768-float embedding) just
|
|
434
474
|
# to return the top N. get_all_facts preserves created_at DESC order.
|
|
@@ -522,11 +562,12 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
522
562
|
return {"success": False, "error": str(exc)}
|
|
523
563
|
|
|
524
564
|
@server.tool()
|
|
525
|
-
|
|
565
|
+
@admits(OperationKind.CORRECT)
|
|
566
|
+
async def build_graph() -> dict:
|
|
526
567
|
"""Rebuild knowledge graph edges for all facts in the active profile."""
|
|
527
568
|
try:
|
|
528
569
|
engine = get_engine()
|
|
529
|
-
pid = await _runtime_profile(get_engine
|
|
570
|
+
pid = await _runtime_profile(get_engine)
|
|
530
571
|
authorization = authorize_mcp_mutation(
|
|
531
572
|
engine,
|
|
532
573
|
"update",
|
|
@@ -550,6 +591,7 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
550
591
|
return {"success": False, "error": str(exc)}
|
|
551
592
|
|
|
552
593
|
@server.tool()
|
|
594
|
+
@admits(OperationKind.PROFILE_SWITCH)
|
|
553
595
|
async def switch_profile(profile_id: str) -> dict:
|
|
554
596
|
"""Switch the active memory profile. All operations scope to this profile."""
|
|
555
597
|
try:
|
|
@@ -674,19 +716,22 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
674
716
|
logger.exception("switch_profile failed")
|
|
675
717
|
return {"success": False, "error": str(exc)}
|
|
676
718
|
|
|
677
|
-
@server.tool()
|
|
719
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
678
720
|
async def backup_status() -> dict:
|
|
679
721
|
"""Get backup system status, last backup time, and available backup files."""
|
|
680
722
|
try:
|
|
681
723
|
engine = get_engine()
|
|
682
724
|
from superlocalmemory.infra.backup import BackupManager
|
|
683
|
-
bm = BackupManager(
|
|
725
|
+
bm = BackupManager(
|
|
726
|
+
db_path=engine._db.db_path,
|
|
727
|
+
base_dir=engine._config.base_dir,
|
|
728
|
+
)
|
|
684
729
|
return {"success": True, **bm.get_status()}
|
|
685
730
|
except Exception as exc:
|
|
686
731
|
logger.exception("backup_status failed")
|
|
687
732
|
return {"success": False, "error": str(exc)}
|
|
688
733
|
|
|
689
|
-
@server.tool()
|
|
734
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
690
735
|
async def memory_used() -> dict:
|
|
691
736
|
"""Get memory usage breakdown by fact type and lifecycle state."""
|
|
692
737
|
try:
|
|
@@ -711,7 +756,7 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
711
756
|
logger.exception("memory_used failed")
|
|
712
757
|
return {"success": False, "error": str(exc)}
|
|
713
758
|
|
|
714
|
-
@server.tool()
|
|
759
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
715
760
|
async def get_learned_patterns(pattern_type: str = "", limit: int = 20) -> dict:
|
|
716
761
|
"""Get learned behavioral patterns (interests, refinements, archival habits)."""
|
|
717
762
|
try:
|
|
@@ -729,6 +774,7 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
729
774
|
return {"success": False, "error": str(exc)}
|
|
730
775
|
|
|
731
776
|
@server.tool()
|
|
777
|
+
@admits(OperationKind.CORRECT)
|
|
732
778
|
async def correct_pattern(pattern_id: str, correction: str) -> dict:
|
|
733
779
|
"""Correct or annotate a learned behavioral pattern to improve retrieval."""
|
|
734
780
|
try:
|
|
@@ -757,6 +803,7 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
757
803
|
return {"success": False, "error": str(exc)}
|
|
758
804
|
|
|
759
805
|
@server.tool(annotations=ToolAnnotations(destructiveHint=True))
|
|
806
|
+
@admits(OperationKind.FORGET)
|
|
760
807
|
async def delete_memory(fact_id: str, agent_id: str = "mcp_client") -> dict:
|
|
761
808
|
"""Delete a specific memory by exact fact ID.
|
|
762
809
|
|
|
@@ -823,6 +870,7 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
823
870
|
return {"success": False, "error": str(exc)}
|
|
824
871
|
|
|
825
872
|
@server.tool(annotations=ToolAnnotations(idempotentHint=True))
|
|
873
|
+
@admits(OperationKind.CORRECT)
|
|
826
874
|
async def update_memory(
|
|
827
875
|
fact_id: str, content: str, agent_id: str = "mcp_client",
|
|
828
876
|
) -> dict:
|
|
@@ -886,12 +934,12 @@ def register_core_tools(server, get_engine: Callable) -> None:
|
|
|
886
934
|
logger.exception("update_memory failed")
|
|
887
935
|
return {"success": False, "error": str(exc)}
|
|
888
936
|
|
|
889
|
-
@server.tool()
|
|
937
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
890
938
|
async def get_attribution() -> dict:
|
|
891
939
|
"""Get system attribution: author, version, license, and provenance metadata."""
|
|
892
940
|
return {
|
|
893
941
|
"success": True,
|
|
894
|
-
"product": "SuperLocalMemory
|
|
942
|
+
"product": "SuperLocalMemory V4",
|
|
895
943
|
"author": "Varun Pratap Bhardwaj",
|
|
896
944
|
"organization": "Qualixar",
|
|
897
945
|
"license": "AGPL-3.0-or-later",
|
|
@@ -19,6 +19,8 @@ import logging
|
|
|
19
19
|
from typing import Callable
|
|
20
20
|
|
|
21
21
|
from mcp.types import ToolAnnotations
|
|
22
|
+
from superlocalmemory.core.admission import admits
|
|
23
|
+
from superlocalmemory.core.operation_request import OperationKind
|
|
22
24
|
from superlocalmemory.infra.data_root import state_path
|
|
23
25
|
from superlocalmemory.storage.read_connection import ReadConnectionFactory
|
|
24
26
|
|
|
@@ -28,6 +30,7 @@ def register_evolution_tools(server, get_engine: Callable) -> None:
|
|
|
28
30
|
"""Register evolution MCP tools for skill evolution intelligence."""
|
|
29
31
|
|
|
30
32
|
@server.tool()
|
|
33
|
+
@admits(OperationKind.EVOLVE_SKILL)
|
|
31
34
|
async def evolve_skill(
|
|
32
35
|
skill_name: str,
|
|
33
36
|
evolution_type: str = "fix",
|
|
@@ -270,6 +273,8 @@ def register_evolution_tools(server, get_engine: Callable) -> None:
|
|
|
270
273
|
skill_name: Specific skill name (empty = all skills)
|
|
271
274
|
"""
|
|
272
275
|
try:
|
|
276
|
+
engine = get_engine()
|
|
277
|
+
profile_id = engine.profile_id if engine else "default"
|
|
273
278
|
db_path = state_path("memory.db")
|
|
274
279
|
conn = ReadConnectionFactory(db_path).open()
|
|
275
280
|
|
|
@@ -279,9 +284,9 @@ def register_evolution_tools(server, get_engine: Callable) -> None:
|
|
|
279
284
|
"trigger_type, generation, status, mutation_summary, "
|
|
280
285
|
"blind_verified, created_at, completed_at "
|
|
281
286
|
"FROM skill_evolution_log "
|
|
282
|
-
"WHERE skill_name = ? OR parent_skill_id = ? "
|
|
287
|
+
"WHERE profile_id = ? AND (skill_name = ? OR parent_skill_id = ?) "
|
|
283
288
|
"ORDER BY created_at ASC",
|
|
284
|
-
(skill_name, skill_name),
|
|
289
|
+
(profile_id, skill_name, skill_name),
|
|
285
290
|
).fetchall()
|
|
286
291
|
else:
|
|
287
292
|
rows = conn.execute(
|
|
@@ -289,7 +294,9 @@ def register_evolution_tools(server, get_engine: Callable) -> None:
|
|
|
289
294
|
"trigger_type, generation, status, mutation_summary, "
|
|
290
295
|
"blind_verified, created_at, completed_at "
|
|
291
296
|
"FROM skill_evolution_log "
|
|
297
|
+
"WHERE profile_id = ? "
|
|
292
298
|
"ORDER BY created_at DESC LIMIT 100",
|
|
299
|
+
(profile_id,),
|
|
293
300
|
).fetchall()
|
|
294
301
|
|
|
295
302
|
conn.close()
|