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
|
@@ -24,6 +24,8 @@ from typing import Callable
|
|
|
24
24
|
|
|
25
25
|
from mcp.types import ToolAnnotations
|
|
26
26
|
|
|
27
|
+
from superlocalmemory.core.admission import admits
|
|
28
|
+
from superlocalmemory.core.operation_request import OperationKind
|
|
27
29
|
from superlocalmemory.mcp.shared import authorize_mcp_mutation
|
|
28
30
|
|
|
29
31
|
logger = logging.getLogger(__name__)
|
|
@@ -35,6 +37,7 @@ def register_learning_tools(server, get_engine: Callable) -> None:
|
|
|
35
37
|
"""Register learning MCP tools for two-way intelligence."""
|
|
36
38
|
|
|
37
39
|
@server.tool()
|
|
40
|
+
@admits(OperationKind.REMEMBER)
|
|
38
41
|
async def log_tool_event(
|
|
39
42
|
tool_name: str,
|
|
40
43
|
event_type: str = "invoke",
|
|
@@ -144,6 +147,7 @@ def register_learning_tools(server, get_engine: Callable) -> None:
|
|
|
144
147
|
return {"assertions": [], "count": 0, "error": str(exc)}
|
|
145
148
|
|
|
146
149
|
@server.tool()
|
|
150
|
+
@admits(OperationKind.CORRECT)
|
|
147
151
|
async def reinforce_assertion(assertion_id: str) -> dict:
|
|
148
152
|
"""Reinforce a behavioral assertion (increase confidence).
|
|
149
153
|
|
|
@@ -164,6 +168,7 @@ def register_learning_tools(server, get_engine: Callable) -> None:
|
|
|
164
168
|
)
|
|
165
169
|
result = _update_assertion_confidence(
|
|
166
170
|
engine._db, assertion_id, reinforce=True,
|
|
171
|
+
profile_id=engine.profile_id,
|
|
167
172
|
)
|
|
168
173
|
if result.get("success"):
|
|
169
174
|
authorization.complete()
|
|
@@ -172,6 +177,7 @@ def register_learning_tools(server, get_engine: Callable) -> None:
|
|
|
172
177
|
return {"success": False, "error": str(exc)}
|
|
173
178
|
|
|
174
179
|
@server.tool()
|
|
180
|
+
@admits(OperationKind.FORGET)
|
|
175
181
|
async def contradict_assertion(assertion_id: str) -> dict:
|
|
176
182
|
"""Contradict a behavioral assertion (decrease confidence).
|
|
177
183
|
|
|
@@ -192,6 +198,7 @@ def register_learning_tools(server, get_engine: Callable) -> None:
|
|
|
192
198
|
)
|
|
193
199
|
result = _update_assertion_confidence(
|
|
194
200
|
engine._db, assertion_id, reinforce=False,
|
|
201
|
+
profile_id=engine.profile_id,
|
|
195
202
|
)
|
|
196
203
|
if result.get("success"):
|
|
197
204
|
authorization.complete()
|
|
@@ -200,14 +207,16 @@ def register_learning_tools(server, get_engine: Callable) -> None:
|
|
|
200
207
|
return {"success": False, "error": str(exc)}
|
|
201
208
|
|
|
202
209
|
|
|
203
|
-
def _update_assertion_confidence(
|
|
204
|
-
|
|
210
|
+
def _update_assertion_confidence(
|
|
211
|
+
db, assertion_id: str, reinforce: bool, profile_id: str,
|
|
212
|
+
) -> dict:
|
|
213
|
+
"""Bayesian confidence update for behavioral assertions, scoped to one profile."""
|
|
205
214
|
now = datetime.now(timezone.utc).isoformat()
|
|
206
215
|
try:
|
|
207
216
|
row = db.execute(
|
|
208
217
|
"SELECT confidence, reinforcement_count, contradiction_count "
|
|
209
|
-
"FROM behavioral_assertions WHERE id = ?",
|
|
210
|
-
(assertion_id,),
|
|
218
|
+
"FROM behavioral_assertions WHERE id = ? AND profile_id = ?",
|
|
219
|
+
(assertion_id, profile_id),
|
|
211
220
|
)
|
|
212
221
|
rows = list(row)
|
|
213
222
|
if not rows:
|
|
@@ -221,22 +230,24 @@ def _update_assertion_confidence(db, assertion_id: str, reinforce: bool) -> dict
|
|
|
221
230
|
db.execute(
|
|
222
231
|
"UPDATE behavioral_assertions SET confidence = ?, "
|
|
223
232
|
"reinforcement_count = reinforcement_count + 1, "
|
|
224
|
-
"last_reinforced_at = ?, updated_at = ?
|
|
225
|
-
|
|
233
|
+
"last_reinforced_at = ?, updated_at = ? "
|
|
234
|
+
"WHERE id = ? AND profile_id = ?",
|
|
235
|
+
(round(new_conf, 4), now, now, assertion_id, profile_id),
|
|
226
236
|
)
|
|
227
237
|
else:
|
|
228
238
|
new_conf = old_conf * 0.7 # 30% decay
|
|
229
239
|
db.execute(
|
|
230
240
|
"UPDATE behavioral_assertions SET confidence = ?, "
|
|
231
241
|
"contradiction_count = contradiction_count + 1, "
|
|
232
|
-
"last_contradicted_at = ?, updated_at = ?
|
|
233
|
-
|
|
242
|
+
"last_contradicted_at = ?, updated_at = ? "
|
|
243
|
+
"WHERE id = ? AND profile_id = ?",
|
|
244
|
+
(round(new_conf, 4), now, now, assertion_id, profile_id),
|
|
234
245
|
)
|
|
235
246
|
# Auto-delete if confidence drops below 0.2
|
|
236
247
|
if new_conf < 0.2:
|
|
237
248
|
db.execute(
|
|
238
|
-
"DELETE FROM behavioral_assertions WHERE id = ?",
|
|
239
|
-
(assertion_id,),
|
|
249
|
+
"DELETE FROM behavioral_assertions WHERE id = ? AND profile_id = ?",
|
|
250
|
+
(assertion_id, profile_id),
|
|
240
251
|
)
|
|
241
252
|
return {
|
|
242
253
|
"success": True, "action": "deleted",
|
|
@@ -39,9 +39,12 @@ from typing import Any, Callable
|
|
|
39
39
|
|
|
40
40
|
from mcp.types import ToolAnnotations
|
|
41
41
|
|
|
42
|
+
from superlocalmemory.core.admission import admits
|
|
43
|
+
from superlocalmemory.core.operation_request import OperationKind
|
|
42
44
|
from superlocalmemory.loops import (
|
|
43
45
|
Bounds,
|
|
44
46
|
LapResult,
|
|
47
|
+
LedgerEntry,
|
|
45
48
|
Verdict,
|
|
46
49
|
engine_backed_ledger,
|
|
47
50
|
run_bounded_loop,
|
|
@@ -66,20 +69,6 @@ _MAX_NAME_CHARS = 128
|
|
|
66
69
|
_MAX_QUERY_CHARS = 2000
|
|
67
70
|
|
|
68
71
|
|
|
69
|
-
def _top_score(resp: Any) -> float:
|
|
70
|
-
"""Highest result score in a RecallResponse (0.0 when there are none)."""
|
|
71
|
-
best = 0.0
|
|
72
|
-
for r in getattr(resp, "results", None) or []:
|
|
73
|
-
s = getattr(r, "score", None)
|
|
74
|
-
if s is None:
|
|
75
|
-
s = getattr(r, "relevance_score", 0.0) or 0.0
|
|
76
|
-
try:
|
|
77
|
-
best = max(best, float(s))
|
|
78
|
-
except (TypeError, ValueError):
|
|
79
|
-
continue
|
|
80
|
-
return best
|
|
81
|
-
|
|
82
|
-
|
|
83
72
|
def register_loop_tools(server, get_engine: Callable) -> None:
|
|
84
73
|
"""Register the 3 bounded-loop tools on *server*.
|
|
85
74
|
|
|
@@ -88,6 +77,7 @@ def register_loop_tools(server, get_engine: Callable) -> None:
|
|
|
88
77
|
"""
|
|
89
78
|
|
|
90
79
|
@server.tool(annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False))
|
|
80
|
+
@admits(OperationKind.REMEMBER)
|
|
91
81
|
async def slm_loop_run(
|
|
92
82
|
name: str,
|
|
93
83
|
gate_query: str,
|
|
@@ -157,14 +147,35 @@ def register_loop_tools(server, get_engine: Callable) -> None:
|
|
|
157
147
|
|
|
158
148
|
def gate(lap: int) -> Verdict:
|
|
159
149
|
resp = engine.recall(gate_query, limit=3, fast=True)
|
|
160
|
-
|
|
150
|
+
all_results = getattr(resp, "results", None) or []
|
|
151
|
+
# Exclude the loop's own audit records from the gate evaluation.
|
|
152
|
+
# Each lap writes a LedgerEntry (JSON with "run_id" + "lap") into
|
|
153
|
+
# SLM via store_fast; the FTS5 trigger indexes every such insert.
|
|
154
|
+
# Without this filter BM25 finds those self-authored records on the
|
|
155
|
+
# next lap and the gate falsely passes. Only memories written by an
|
|
156
|
+
# external agent — whose content does not parse as a LedgerEntry —
|
|
157
|
+
# can satisfy an independent gate.
|
|
158
|
+
results = [
|
|
159
|
+
r for r in all_results
|
|
160
|
+
if LedgerEntry.from_json(
|
|
161
|
+
getattr(getattr(r, "fact", None), "content", "") or ""
|
|
162
|
+
) is None
|
|
163
|
+
]
|
|
161
164
|
floored = bool(getattr(resp, "no_confident_match", False))
|
|
162
|
-
|
|
163
|
-
|
|
165
|
+
best: float = 0.0
|
|
166
|
+
for r in results:
|
|
167
|
+
s = getattr(r, "score", None)
|
|
168
|
+
if s is None:
|
|
169
|
+
s = getattr(r, "relevance_score", 0.0) or 0.0
|
|
170
|
+
try:
|
|
171
|
+
best = max(best, float(s))
|
|
172
|
+
except (TypeError, ValueError):
|
|
173
|
+
pass
|
|
174
|
+
passed = bool(results) and not floored and best >= min_score
|
|
164
175
|
return Verdict(
|
|
165
176
|
passed,
|
|
166
177
|
f"recall '{gate_query[:48]}': hits={len(results)} "
|
|
167
|
-
f"top={
|
|
178
|
+
f"top={best:.3f} floor={floored}",
|
|
168
179
|
)
|
|
169
180
|
|
|
170
181
|
def runner(lap: int) -> LapResult:
|
|
@@ -26,6 +26,9 @@ from typing import Callable
|
|
|
26
26
|
|
|
27
27
|
from mcp.types import ToolAnnotations
|
|
28
28
|
|
|
29
|
+
from superlocalmemory.core.admission import admits
|
|
30
|
+
from superlocalmemory.core.operation_request import OperationKind
|
|
31
|
+
|
|
29
32
|
logger = logging.getLogger(__name__)
|
|
30
33
|
|
|
31
34
|
|
|
@@ -221,6 +224,7 @@ def register_mesh_tools(server, get_engine: Callable) -> None:
|
|
|
221
224
|
"""Register all 8 mesh MCP tools."""
|
|
222
225
|
|
|
223
226
|
@server.tool()
|
|
227
|
+
@admits(OperationKind.MESH_SEND)
|
|
224
228
|
async def mesh_summary(summary: str = "") -> dict:
|
|
225
229
|
"""Register this session and describe what you're working on.
|
|
226
230
|
|
|
@@ -269,6 +273,7 @@ def register_mesh_tools(server, get_engine: Callable) -> None:
|
|
|
269
273
|
}
|
|
270
274
|
|
|
271
275
|
@server.tool()
|
|
276
|
+
@admits(OperationKind.MESH_SEND)
|
|
272
277
|
async def mesh_send(to: str, message: str) -> dict:
|
|
273
278
|
"""Send a message to another peer session, broadcast, or project.
|
|
274
279
|
|
|
@@ -309,6 +314,7 @@ def register_mesh_tools(server, get_engine: Callable) -> None:
|
|
|
309
314
|
return result
|
|
310
315
|
|
|
311
316
|
@server.tool()
|
|
317
|
+
@admits(OperationKind.MESH_SEND)
|
|
312
318
|
async def mesh_inbox() -> dict:
|
|
313
319
|
"""Read messages sent to this session.
|
|
314
320
|
|
|
@@ -341,6 +347,7 @@ def register_mesh_tools(server, get_engine: Callable) -> None:
|
|
|
341
347
|
}
|
|
342
348
|
|
|
343
349
|
@server.tool()
|
|
350
|
+
@admits(OperationKind.MESH_SEND)
|
|
344
351
|
async def mesh_state(key: str = "", value: str = "", action: str = "get") -> dict:
|
|
345
352
|
"""Get or set shared state across all sessions.
|
|
346
353
|
|
|
@@ -370,6 +377,7 @@ def register_mesh_tools(server, get_engine: Callable) -> None:
|
|
|
370
377
|
return result or {"state": {}}
|
|
371
378
|
|
|
372
379
|
@server.tool()
|
|
380
|
+
@admits(OperationKind.MESH_LOCK)
|
|
373
381
|
async def mesh_lock(
|
|
374
382
|
file_path: str,
|
|
375
383
|
action: str = "query",
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under AGPL-3.0-or-later - see LICENSE file
|
|
3
|
+
# Part of SuperLocalMemory V4 | https://qualixar.com | https://varunpratap.com
|
|
4
|
+
|
|
5
|
+
"""Wave-3 Operational Recovery & Admin Remediation MCP tools (2 tools).
|
|
6
|
+
|
|
7
|
+
list_failed_operations — Surface dead-letter, degraded, and exhausted ops.
|
|
8
|
+
resolve_operation — Admin retry/force-reconcile/cancel for stuck ops.
|
|
9
|
+
|
|
10
|
+
RBAC: both tools require OWNER or ADMIN role (OPS_INSPECT / OPS_RESOLVE policy).
|
|
11
|
+
|
|
12
|
+
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
from typing import Callable
|
|
19
|
+
|
|
20
|
+
from mcp.types import ToolAnnotations
|
|
21
|
+
|
|
22
|
+
from superlocalmemory.core.admission import admits
|
|
23
|
+
from superlocalmemory.core.operation_request import OperationKind
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
_VALID_ACTIONS = frozenset({"retry", "force_reconcile", "cancel"})
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def register_ops_tools(server, get_engine: Callable) -> None:
|
|
31
|
+
"""Register Wave-3 operational-recovery MCP tools on *server*."""
|
|
32
|
+
|
|
33
|
+
# ------------------------------------------------------------------
|
|
34
|
+
# 1. list_failed_operations — surface all stuck/failed/degraded ops
|
|
35
|
+
# ------------------------------------------------------------------
|
|
36
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
37
|
+
@admits(OperationKind.OPS_INSPECT)
|
|
38
|
+
async def list_failed_operations(
|
|
39
|
+
profile_id: str = "",
|
|
40
|
+
) -> dict:
|
|
41
|
+
"""List all failed, stuck, or degraded memory operations.
|
|
42
|
+
|
|
43
|
+
Returns three categories of troubled operations:
|
|
44
|
+
- dead_letter: ingestion ops that exhausted all automatic retries.
|
|
45
|
+
- degraded_manifests: completion records where some projections failed.
|
|
46
|
+
- exhausted_obligations: graph/projection tasks that could not apply.
|
|
47
|
+
|
|
48
|
+
An admin can then call resolve_operation to retry, force-reconcile, or
|
|
49
|
+
cancel each entry. Non-technical users see these in the dashboard
|
|
50
|
+
Operations & Health panel.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
profile_id: Filter to a specific profile (empty = all profiles).
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
engine = get_engine()
|
|
57
|
+
db_path = getattr(getattr(engine, "_config", None), "db_path", None)
|
|
58
|
+
if db_path is None:
|
|
59
|
+
from superlocalmemory.infra.data_root import state_path
|
|
60
|
+
db_path = state_path("memory.db")
|
|
61
|
+
|
|
62
|
+
from superlocalmemory.core.ops_remediation import (
|
|
63
|
+
list_failed_operations as _list,
|
|
64
|
+
)
|
|
65
|
+
result = _list(db_path, profile_id=profile_id or None)
|
|
66
|
+
return {"success": True, **result}
|
|
67
|
+
except Exception as exc:
|
|
68
|
+
logger.exception("list_failed_operations tool failed")
|
|
69
|
+
return {"success": False, "error": str(exc)}
|
|
70
|
+
|
|
71
|
+
# ------------------------------------------------------------------
|
|
72
|
+
# 2. resolve_operation — admin remediation: retry / force / cancel
|
|
73
|
+
# ------------------------------------------------------------------
|
|
74
|
+
@server.tool(annotations=ToolAnnotations(destructiveHint=True))
|
|
75
|
+
@admits(OperationKind.OPS_RESOLVE)
|
|
76
|
+
async def resolve_operation(
|
|
77
|
+
operation_id: str,
|
|
78
|
+
action: str,
|
|
79
|
+
) -> dict:
|
|
80
|
+
"""Resolve a stuck, failed, or degraded operation.
|
|
81
|
+
|
|
82
|
+
Performs one of three remediation actions:
|
|
83
|
+
- retry: Re-queue the dead-letter entry for ingestion.
|
|
84
|
+
- force_reconcile: Force projection obligations to re-apply now.
|
|
85
|
+
- cancel: Remove the entry from failure surfaces permanently.
|
|
86
|
+
|
|
87
|
+
After a cancel, the entry no longer appears in list_failed_operations.
|
|
88
|
+
After retry/force_reconcile, results depend on whether the underlying
|
|
89
|
+
issue (e.g. missing embedding model) is now resolved.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
operation_id: The operation_id string shown in list_failed_operations.
|
|
93
|
+
action: One of: retry, force_reconcile, cancel.
|
|
94
|
+
"""
|
|
95
|
+
if action not in _VALID_ACTIONS:
|
|
96
|
+
return {
|
|
97
|
+
"success": False,
|
|
98
|
+
"error": f"invalid action '{action}': must be one of {sorted(_VALID_ACTIONS)}",
|
|
99
|
+
}
|
|
100
|
+
try:
|
|
101
|
+
engine = get_engine()
|
|
102
|
+
db_path = getattr(getattr(engine, "_config", None), "db_path", None)
|
|
103
|
+
if db_path is None:
|
|
104
|
+
from superlocalmemory.infra.data_root import state_path
|
|
105
|
+
db_path = state_path("memory.db")
|
|
106
|
+
|
|
107
|
+
from superlocalmemory.core.ops_remediation import (
|
|
108
|
+
resolve_operation as _resolve,
|
|
109
|
+
)
|
|
110
|
+
return _resolve(db_path, engine, operation_id, action)
|
|
111
|
+
except ValueError as exc:
|
|
112
|
+
return {"success": False, "error": str(exc)}
|
|
113
|
+
except Exception as exc:
|
|
114
|
+
logger.exception("resolve_operation tool failed op=%s action=%s", operation_id, action)
|
|
115
|
+
return {"success": False, "error": str(exc)}
|
|
@@ -24,6 +24,8 @@ import time
|
|
|
24
24
|
|
|
25
25
|
from mcp.types import ToolAnnotations
|
|
26
26
|
|
|
27
|
+
from superlocalmemory.core.admission import admits
|
|
28
|
+
from superlocalmemory.core.operation_request import OperationKind
|
|
27
29
|
from superlocalmemory.mcp.agent_context import get_current_agent_id
|
|
28
30
|
from superlocalmemory.optimize.compress.ccr import CCRStore, _UUID4_RE
|
|
29
31
|
from superlocalmemory.optimize.compress.router import CompressRouter
|
|
@@ -70,6 +72,7 @@ def register_optimize_tools(server) -> None:
|
|
|
70
72
|
"""
|
|
71
73
|
|
|
72
74
|
@server.tool(annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False))
|
|
75
|
+
@admits(OperationKind.CONSOLIDATE)
|
|
73
76
|
async def slm_compress(
|
|
74
77
|
content: str,
|
|
75
78
|
mode: str = "auto",
|
|
@@ -191,6 +194,7 @@ def register_optimize_tools(server) -> None:
|
|
|
191
194
|
}
|
|
192
195
|
|
|
193
196
|
@server.tool(annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False))
|
|
197
|
+
@admits(OperationKind.REMEMBER)
|
|
194
198
|
async def slm_cache_set(key: str, value: str, ttl_seconds: int = 86400) -> dict:
|
|
195
199
|
"""Cache a result you want to reuse (tool output, file read, search result).
|
|
196
200
|
|
|
@@ -15,6 +15,10 @@ from __future__ import annotations
|
|
|
15
15
|
import logging
|
|
16
16
|
from typing import Callable
|
|
17
17
|
|
|
18
|
+
from mcp.types import ToolAnnotations
|
|
19
|
+
|
|
20
|
+
from superlocalmemory.core.admission import admits
|
|
21
|
+
from superlocalmemory.core.operation_request import OperationKind
|
|
18
22
|
from superlocalmemory.mcp.shared import authorize_mcp_mutation
|
|
19
23
|
|
|
20
24
|
logger = logging.getLogger(__name__)
|
|
@@ -27,6 +31,7 @@ def register_v28_tools(server, get_engine: Callable) -> None:
|
|
|
27
31
|
# 1. report_outcome
|
|
28
32
|
# ------------------------------------------------------------------
|
|
29
33
|
@server.tool()
|
|
34
|
+
@admits(OperationKind.REMEMBER)
|
|
30
35
|
async def report_outcome(
|
|
31
36
|
memory_ids: str,
|
|
32
37
|
outcome: str,
|
|
@@ -91,7 +96,7 @@ def register_v28_tools(server, get_engine: Callable) -> None:
|
|
|
91
96
|
# ------------------------------------------------------------------
|
|
92
97
|
# 2. get_lifecycle_status
|
|
93
98
|
# ------------------------------------------------------------------
|
|
94
|
-
@server.tool()
|
|
99
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
95
100
|
async def get_lifecycle_status(limit: int = 50) -> dict:
|
|
96
101
|
"""Get lifecycle state distribution for stored memories.
|
|
97
102
|
|
|
@@ -127,6 +132,7 @@ def register_v28_tools(server, get_engine: Callable) -> None:
|
|
|
127
132
|
# 3. set_retention_policy
|
|
128
133
|
# ------------------------------------------------------------------
|
|
129
134
|
@server.tool()
|
|
135
|
+
@admits(OperationKind.CONSOLIDATE)
|
|
130
136
|
async def set_retention_policy(
|
|
131
137
|
cold_after_days: int = 30,
|
|
132
138
|
archive_after_days: int = 90,
|
|
@@ -163,6 +169,7 @@ def register_v28_tools(server, get_engine: Callable) -> None:
|
|
|
163
169
|
# 4. compact_memories
|
|
164
170
|
# ------------------------------------------------------------------
|
|
165
171
|
@server.tool()
|
|
172
|
+
@admits(OperationKind.CONSOLIDATE)
|
|
166
173
|
async def compact_memories(dry_run: bool = True) -> dict:
|
|
167
174
|
"""Compact memory store by archiving cold/stale facts.
|
|
168
175
|
|
|
@@ -212,7 +219,7 @@ def register_v28_tools(server, get_engine: Callable) -> None:
|
|
|
212
219
|
# ------------------------------------------------------------------
|
|
213
220
|
# 5. get_behavioral_patterns
|
|
214
221
|
# ------------------------------------------------------------------
|
|
215
|
-
@server.tool()
|
|
222
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
216
223
|
async def get_behavioral_patterns(limit: int = 20) -> dict:
|
|
217
224
|
"""Get detected behavioral patterns for the active profile.
|
|
218
225
|
|
|
@@ -241,7 +248,7 @@ def register_v28_tools(server, get_engine: Callable) -> None:
|
|
|
241
248
|
# ------------------------------------------------------------------
|
|
242
249
|
# 6. audit_trail
|
|
243
250
|
# ------------------------------------------------------------------
|
|
244
|
-
@server.tool()
|
|
251
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
245
252
|
async def audit_trail(limit: int = 50) -> dict:
|
|
246
253
|
"""Get compliance audit trail for the active profile.
|
|
247
254
|
|
|
@@ -14,6 +14,10 @@ from __future__ import annotations
|
|
|
14
14
|
import logging
|
|
15
15
|
from typing import Callable
|
|
16
16
|
|
|
17
|
+
from mcp.types import ToolAnnotations
|
|
18
|
+
|
|
19
|
+
from superlocalmemory.core.admission import admits
|
|
20
|
+
from superlocalmemory.core.operation_request import OperationKind
|
|
17
21
|
from superlocalmemory.mcp.shared import authorize_mcp_mutation
|
|
18
22
|
|
|
19
23
|
logger = logging.getLogger(__name__)
|
|
@@ -25,7 +29,7 @@ def register_v3_tools(server, get_engine: Callable) -> None:
|
|
|
25
29
|
# ------------------------------------------------------------------
|
|
26
30
|
# 0. get_version (so IDEs can check compatibility)
|
|
27
31
|
# ------------------------------------------------------------------
|
|
28
|
-
@server.tool()
|
|
32
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
29
33
|
async def get_version() -> dict:
|
|
30
34
|
"""Get SuperLocalMemory version, Python version, and platform info."""
|
|
31
35
|
try:
|
|
@@ -47,12 +51,13 @@ def register_v3_tools(server, get_engine: Callable) -> None:
|
|
|
47
51
|
# 1. set_mode
|
|
48
52
|
# ------------------------------------------------------------------
|
|
49
53
|
@server.tool()
|
|
54
|
+
@admits(OperationKind.MODE_CHANGE)
|
|
50
55
|
async def set_mode(mode: str) -> dict:
|
|
51
56
|
"""Switch operating mode (a, b, or c).
|
|
52
57
|
|
|
53
|
-
Mode A: Local Guardian (zero LLM,
|
|
54
|
-
Mode B: Smart Local (local Ollama LLM,
|
|
55
|
-
Mode C: Full Power (cloud LLM, best accuracy).
|
|
58
|
+
Mode A: Local Guardian (zero LLM, local embeddings only).
|
|
59
|
+
Mode B: Smart Local (local Ollama LLM, on-device inference).
|
|
60
|
+
Mode C: Full Power (configured cloud LLM provider, best accuracy).
|
|
56
61
|
|
|
57
62
|
Resets the engine to apply the new mode configuration.
|
|
58
63
|
|
|
@@ -106,7 +111,7 @@ def register_v3_tools(server, get_engine: Callable) -> None:
|
|
|
106
111
|
# ------------------------------------------------------------------
|
|
107
112
|
# 2. get_mode
|
|
108
113
|
# ------------------------------------------------------------------
|
|
109
|
-
@server.tool()
|
|
114
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
110
115
|
async def get_mode() -> dict:
|
|
111
116
|
"""Get current operating mode and its capabilities.
|
|
112
117
|
|
|
@@ -135,7 +140,7 @@ def register_v3_tools(server, get_engine: Callable) -> None:
|
|
|
135
140
|
# ------------------------------------------------------------------
|
|
136
141
|
# 3. health
|
|
137
142
|
# ------------------------------------------------------------------
|
|
138
|
-
@server.tool()
|
|
143
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
139
144
|
async def health() -> dict:
|
|
140
145
|
"""Get system health including math layer status.
|
|
141
146
|
|
|
@@ -219,7 +224,7 @@ def register_v3_tools(server, get_engine: Callable) -> None:
|
|
|
219
224
|
# ------------------------------------------------------------------
|
|
220
225
|
# 4. consistency_check
|
|
221
226
|
# ------------------------------------------------------------------
|
|
222
|
-
@server.tool()
|
|
227
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
223
228
|
async def consistency_check(limit: int = 100) -> dict:
|
|
224
229
|
"""Run sheaf consistency check on stored memories.
|
|
225
230
|
|
|
@@ -276,8 +281,12 @@ def register_v3_tools(server, get_engine: Callable) -> None:
|
|
|
276
281
|
# ------------------------------------------------------------------
|
|
277
282
|
# 5. recall_trace
|
|
278
283
|
# ------------------------------------------------------------------
|
|
279
|
-
@server.tool()
|
|
280
|
-
async def recall_trace(
|
|
284
|
+
@server.tool(annotations=ToolAnnotations(readOnlyHint=True))
|
|
285
|
+
async def recall_trace(
|
|
286
|
+
query: str,
|
|
287
|
+
limit: int = 10,
|
|
288
|
+
as_of: str | None = None,
|
|
289
|
+
) -> dict:
|
|
281
290
|
"""Recall with per-channel score breakdown.
|
|
282
291
|
|
|
283
292
|
Like recall, but returns detailed channel-by-channel scores
|
|
@@ -286,14 +295,25 @@ def register_v3_tools(server, get_engine: Callable) -> None:
|
|
|
286
295
|
Args:
|
|
287
296
|
query: Natural-language search query.
|
|
288
297
|
limit: Maximum results (default 10).
|
|
298
|
+
as_of: Optional ISO 8601 UTC datetime for point-in-time recall
|
|
299
|
+
(e.g. "2024-01-01T00:00:00Z"). Omit for current-state recall.
|
|
289
300
|
"""
|
|
290
301
|
try:
|
|
291
302
|
import asyncio
|
|
292
303
|
from superlocalmemory.mcp._daemon_proxy import choose_pool
|
|
304
|
+
from superlocalmemory.retrieval.temporal_utils import normalize_as_of
|
|
305
|
+
|
|
306
|
+
# Normalize at MCP boundary before forwarding.
|
|
307
|
+
_as_of: str | None = None
|
|
308
|
+
if as_of:
|
|
309
|
+
_as_of = normalize_as_of(as_of)
|
|
310
|
+
if _as_of is None:
|
|
311
|
+
return {"success": False, "error": "invalid_as_of"}
|
|
312
|
+
|
|
293
313
|
# choose_pool().recall uses blocking urllib; run off the event loop
|
|
294
314
|
# so recall_trace doesn't stall the MCP server for other tools.
|
|
295
315
|
raw = await asyncio.to_thread(
|
|
296
|
-
lambda: choose_pool().recall(query=query, limit=limit)
|
|
316
|
+
lambda: choose_pool().recall(query=query, limit=limit, as_of=_as_of)
|
|
297
317
|
)
|
|
298
318
|
items = raw.get("results", []) if isinstance(raw, dict) else []
|
|
299
319
|
results = []
|
|
@@ -341,10 +361,10 @@ def register_v3_tools(server, get_engine: Callable) -> None:
|
|
|
341
361
|
# -- Helpers ------------------------------------------------------------------
|
|
342
362
|
|
|
343
363
|
def _mode_description(mode: str) -> str:
|
|
344
|
-
"""Human-readable description for a mode."""
|
|
364
|
+
"""Human-readable capability description for a mode (never a legal claim)."""
|
|
345
365
|
descriptions = {
|
|
346
|
-
"a": "Local Guardian: zero LLM,
|
|
347
|
-
"b": "Smart Local: local Ollama LLM,
|
|
348
|
-
"c": "Full Power: cloud LLM, best accuracy
|
|
366
|
+
"a": "Local Guardian: zero LLM, local embeddings only",
|
|
367
|
+
"b": "Smart Local: local Ollama LLM, on-device inference",
|
|
368
|
+
"c": "Full Power: configured cloud LLM provider, best accuracy",
|
|
349
369
|
}
|
|
350
370
|
return descriptions.get(mode, "Unknown mode")
|