superlocalmemory 3.7.8 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/ATTRIBUTION.md +1 -3
- package/CHANGELOG.md +69 -0
- package/README.md +199 -29
- package/package.json +4 -2
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/CLAUDE.md +8 -8
- package/plugin/agents/slm-governance-advisor.md +80 -0
- package/plugin/agents/slm-loop-runner.md +71 -0
- package/plugin/agents/slm-memory-advisor.md +10 -5
- package/plugin/agents/slm-optimize-advisor.md +9 -3
- package/plugin/commands/slm-loop.md +31 -0
- package/plugin/hooks/hooks.json +79 -0
- package/plugin/requirements.txt +1 -1
- package/plugin/scripts/slm-launch +46 -7
- package/plugin/settings.json +9 -0
- package/plugin/skills/slm-cache/SKILL.md +9 -1
- package/plugin/skills/slm-compress/SKILL.md +8 -1
- package/plugin/skills/slm-governance/SKILL.md +248 -0
- package/plugin/skills/slm-graph/SKILL.md +17 -3
- package/plugin/skills/slm-loop/SKILL.md +99 -0
- package/plugin/skills/slm-mesh/SKILL.md +282 -0
- package/plugin/skills/slm-profile/SKILL.md +148 -0
- package/plugin/skills/slm-recall/SKILL.md +46 -10
- package/plugin/skills/slm-remember/SKILL.md +48 -1
- package/plugin/skills/slm-scope/SKILL.md +176 -0
- package/plugin/skills/slm-session/SKILL.md +24 -1
- package/plugin/skills/slm-status/SKILL.md +18 -1
- package/plugin-src/agents/slm-governance-advisor.md +80 -0
- package/plugin-src/agents/slm-loop-runner.md +71 -0
- package/plugin-src/agents/slm-memory-advisor.md +10 -5
- package/plugin-src/agents/slm-optimize-advisor.md +9 -3
- package/plugin-src/commands/slm-loop.md +31 -0
- package/plugin-src/hooks/hooks.json +79 -0
- package/plugin-src/manifest.json +7 -2
- package/plugin-src/requirements.txt +1 -1
- package/plugin-src/rules/AGENTS.md +57 -18
- package/plugin-src/rules/CLAUDE.md.fragment +8 -8
- package/plugin-src/scripts/slm-launch +46 -7
- package/plugin-src/settings.json +9 -0
- package/plugin-src/skills/slm-cache/SKILL.md +9 -1
- package/plugin-src/skills/slm-compress/SKILL.md +8 -1
- package/plugin-src/skills/slm-governance/SKILL.md +248 -0
- package/plugin-src/skills/slm-graph/SKILL.md +17 -3
- package/plugin-src/skills/slm-loop/SKILL.md +99 -0
- package/plugin-src/skills/slm-mesh/SKILL.md +282 -0
- package/plugin-src/skills/slm-profile/SKILL.md +148 -0
- package/plugin-src/skills/slm-recall/SKILL.md +46 -10
- package/plugin-src/skills/slm-remember/SKILL.md +48 -1
- package/plugin-src/skills/slm-scope/SKILL.md +176 -0
- package/plugin-src/skills/slm-session/SKILL.md +24 -1
- package/plugin-src/skills/slm-status/SKILL.md +18 -1
- package/pyproject.toml +1 -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 +94 -10
- package/src/superlocalmemory/cli/compress_cmd.py +17 -7
- package/src/superlocalmemory/cli/loop_cmd.py +192 -0
- package/src/superlocalmemory/cli/main.py +39 -4
- package/src/superlocalmemory/cli/mesh_cmd.py +38 -0
- package/src/superlocalmemory/cli/optimize_cmd.py +3 -0
- package/src/superlocalmemory/cli/pending_store.py +49 -13
- package/src/superlocalmemory/cli/proxy_cmd.py +4 -0
- package/src/superlocalmemory/cli/scale_engine_cmd.py +6 -0
- package/src/superlocalmemory/cli/setup_wizard.py +22 -13
- package/src/superlocalmemory/compliance/audit.py +6 -0
- package/src/superlocalmemory/compliance/gdpr.py +128 -138
- package/src/superlocalmemory/compliance/retention.py +176 -45
- package/src/superlocalmemory/core/backend_orchestrator.py +5 -43
- package/src/superlocalmemory/core/community_summary.py +267 -0
- package/src/superlocalmemory/core/config.py +216 -3
- package/src/superlocalmemory/core/consolidation_engine.py +95 -22
- package/src/superlocalmemory/core/context_cache.py +61 -18
- package/src/superlocalmemory/core/embedding_worker.py +17 -2
- package/src/superlocalmemory/core/embeddings.py +12 -1
- package/src/superlocalmemory/core/engine.py +17 -1
- package/src/superlocalmemory/core/engine_ingestion.py +29 -0
- package/src/superlocalmemory/core/engine_wiring.py +13 -0
- package/src/superlocalmemory/core/entity_community.py +178 -0
- package/src/superlocalmemory/core/graph_analyzer.py +39 -2
- package/src/superlocalmemory/core/graph_pruner.py +13 -8
- package/src/superlocalmemory/core/key_expander.py +138 -0
- package/src/superlocalmemory/core/maintenance.py +23 -0
- package/src/superlocalmemory/core/modes.py +1 -1
- package/src/superlocalmemory/core/mutations.py +2 -2
- package/src/superlocalmemory/core/pii.py +105 -0
- package/src/superlocalmemory/core/progressive_abstraction.py +208 -0
- package/src/superlocalmemory/core/recall_pipeline.py +2 -0
- package/src/superlocalmemory/core/recall_worker.py +20 -6
- package/src/superlocalmemory/core/scale_engine.py +60 -1
- package/src/superlocalmemory/core/security_primitives.py +40 -2
- package/src/superlocalmemory/core/store_pipeline.py +35 -11
- package/src/superlocalmemory/core/worker_pool.py +21 -6
- package/src/superlocalmemory/encoding/entity_reflexion.py +200 -0
- package/src/superlocalmemory/encoding/entity_resolver.py +34 -24
- package/src/superlocalmemory/encoding/fact_extractor.py +26 -1
- package/src/superlocalmemory/encoding/temporal_validator.py +64 -1
- package/src/superlocalmemory/evolution/evolution_store.py +122 -45
- package/src/superlocalmemory/evolution/llm_dispatch.py +12 -1
- package/src/superlocalmemory/evolution/model_selection.py +160 -0
- package/src/superlocalmemory/evolution/mutation_generator.py +16 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +127 -42
- package/src/superlocalmemory/evolution/triggers.py +22 -13
- package/src/superlocalmemory/graph/cozo_backend.py +43 -20
- package/src/superlocalmemory/hooks/adapter_base.py +5 -1
- package/src/superlocalmemory/hooks/auto_recall.py +13 -1
- package/src/superlocalmemory/hooks/claude_code_hooks.py +11 -0
- package/src/superlocalmemory/hooks/codex_assets.py +64 -5
- package/src/superlocalmemory/hooks/hook_daemon.py +20 -3
- package/src/superlocalmemory/hooks/memory_protocol.py +54 -0
- package/src/superlocalmemory/hooks/portable_kit.py +114 -1
- package/src/superlocalmemory/infra/backup.py +12 -1
- package/src/superlocalmemory/infra/daemon_identity.py +40 -4
- package/src/superlocalmemory/infra/data_root.py +43 -4
- package/src/superlocalmemory/infra/event_bus.py +107 -24
- package/src/superlocalmemory/infra/rate_limiter.py +93 -0
- package/src/superlocalmemory/ingestion/adapter_manager.py +4 -1
- package/src/superlocalmemory/ingestion/credentials.py +1 -1
- package/src/superlocalmemory/learning/cross_project.py +28 -19
- package/src/superlocalmemory/learning/reward_proxy.py +42 -9
- package/src/superlocalmemory/loops/__init__.py +56 -0
- package/src/superlocalmemory/loops/budget.py +58 -0
- package/src/superlocalmemory/loops/engine.py +164 -0
- package/src/superlocalmemory/loops/ledger.py +243 -0
- package/src/superlocalmemory/loops/models.py +152 -0
- package/src/superlocalmemory/loops/rules.py +52 -0
- package/src/superlocalmemory/mcp/_daemon_proxy.py +3 -0
- package/src/superlocalmemory/mcp/_pool_adapter.py +2 -0
- package/src/superlocalmemory/mcp/profiles.py +103 -0
- package/src/superlocalmemory/mcp/server.py +21 -49
- package/src/superlocalmemory/mcp/tools_active.py +4 -7
- package/src/superlocalmemory/mcp/tools_code_graph.py +51 -5
- package/src/superlocalmemory/mcp/tools_core.py +8 -1
- package/src/superlocalmemory/mcp/tools_evolution.py +6 -3
- package/src/superlocalmemory/mcp/tools_loops.py +300 -0
- package/src/superlocalmemory/mcp/tools_mesh.py +140 -4
- package/src/superlocalmemory/mcp/tools_optimize.py +15 -8
- package/src/superlocalmemory/mesh/broker.py +237 -129
- package/src/superlocalmemory/mesh/remote_sync.py +50 -8
- package/src/superlocalmemory/optimize/NOTICE +1 -6
- package/src/superlocalmemory/optimize/adapters/anthropic_adapter.py +1 -4
- package/src/superlocalmemory/optimize/adapters/openai_adapter.py +1 -4
- package/src/superlocalmemory/optimize/cache/semantic.py +27 -19
- package/src/superlocalmemory/optimize/compress/align.py +32 -26
- package/src/superlocalmemory/optimize/compress/ccr.py +14 -71
- package/src/superlocalmemory/optimize/compress/router.py +105 -22
- package/src/superlocalmemory/optimize/config/defaults.py +1 -1
- package/src/superlocalmemory/optimize/config/schema.py +87 -4
- package/src/superlocalmemory/optimize/metrics/counters.py +13 -4
- package/src/superlocalmemory/optimize/metrics/estimator.py +0 -3
- package/src/superlocalmemory/optimize/proxy/_helpers.py +31 -4
- package/src/superlocalmemory/optimize/storage/db.py +38 -9
- package/src/superlocalmemory/optimize/storage/schema.py +10 -0
- package/src/superlocalmemory/parameterization/pattern_extractor.py +6 -3
- package/src/superlocalmemory/retrieval/agentic.py +1 -1
- package/src/superlocalmemory/retrieval/bm25_channel.py +68 -10
- package/src/superlocalmemory/retrieval/engine.py +168 -26
- package/src/superlocalmemory/retrieval/entity_channel.py +7 -5
- package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -2
- package/src/superlocalmemory/retrieval/semantic_channel.py +114 -21
- package/src/superlocalmemory/retrieval/spreading_activation.py +11 -2
- package/src/superlocalmemory/retrieval/temporal_channel.py +48 -9
- package/src/superlocalmemory/retrieval/temporal_frame.py +102 -0
- package/src/superlocalmemory/retrieval/temporal_validity_filter.py +135 -0
- package/src/superlocalmemory/retrieval/time_window.py +181 -0
- package/src/superlocalmemory/server/api.py +4 -4
- package/src/superlocalmemory/server/profile_runtime.py +125 -8
- package/src/superlocalmemory/server/rbac_enforce.py +142 -0
- package/src/superlocalmemory/server/recall_health.py +24 -3
- package/src/superlocalmemory/server/recall_serializer.py +19 -1
- package/src/superlocalmemory/server/routes/abstraction.py +115 -0
- package/src/superlocalmemory/server/routes/agents.py +128 -38
- package/src/superlocalmemory/server/routes/backup.py +34 -10
- package/src/superlocalmemory/server/routes/behavioral.py +13 -12
- package/src/superlocalmemory/server/routes/brain.py +21 -5
- package/src/superlocalmemory/server/routes/chat.py +10 -5
- package/src/superlocalmemory/server/routes/compliance.py +171 -21
- package/src/superlocalmemory/server/routes/config_api.py +436 -0
- package/src/superlocalmemory/server/routes/data_io.py +30 -8
- package/src/superlocalmemory/server/routes/entity.py +9 -4
- package/src/superlocalmemory/server/routes/events.py +24 -8
- package/src/superlocalmemory/server/routes/evolution.py +135 -17
- package/src/superlocalmemory/server/routes/helpers.py +16 -1
- package/src/superlocalmemory/server/routes/ingest.py +7 -4
- package/src/superlocalmemory/server/routes/insights.py +3 -3
- package/src/superlocalmemory/server/routes/learning.py +14 -14
- package/src/superlocalmemory/server/routes/lifecycle.py +59 -8
- package/src/superlocalmemory/server/routes/memories.py +182 -57
- package/src/superlocalmemory/server/routes/mesh.py +95 -15
- package/src/superlocalmemory/server/routes/optimize.py +33 -1
- package/src/superlocalmemory/server/routes/prewarm.py +2 -0
- package/src/superlocalmemory/server/routes/profiles.py +63 -17
- package/src/superlocalmemory/server/routes/ratelimit.py +124 -0
- package/src/superlocalmemory/server/routes/rbac.py +367 -0
- package/src/superlocalmemory/server/routes/stats.py +13 -6
- package/src/superlocalmemory/server/routes/tiers.py +11 -9
- package/src/superlocalmemory/server/routes/v3_api.py +183 -69
- 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 +384 -56
- package/src/superlocalmemory/server/write_identity.py +38 -8
- package/src/superlocalmemory/storage/database.py +265 -53
- package/src/superlocalmemory/storage/migration_runner.py +53 -0
- package/src/superlocalmemory/storage/migrations/M021_ingestion_log_profile.py +108 -0
- package/src/superlocalmemory/storage/migrations/M022_entity_aliases_profile.py +86 -0
- package/src/superlocalmemory/storage/migrations/M023_mesh_profile_isolation.py +194 -0
- package/src/superlocalmemory/storage/migrations/M024_rbac_users_roles.py +87 -0
- package/src/superlocalmemory/storage/migrations/M025_perf_indexes.py +90 -0
- package/src/superlocalmemory/storage/migrations/M026_rbac_memberships_fk.py +136 -0
- package/src/superlocalmemory/storage/migrations/M027_transferable_patterns_profile.py +163 -0
- package/src/superlocalmemory/storage/models.py +4 -0
- package/src/superlocalmemory/storage/schema.py +87 -0
- package/src/superlocalmemory/storage/schema_v343.py +24 -12
- package/src/superlocalmemory/trust/gate.py +49 -8
- package/src/superlocalmemory/ui/assets/slm-icon-white.svg +64 -0
- package/src/superlocalmemory/ui/assets/slm-icon.svg +36 -0
- package/src/superlocalmemory/ui/css/design-system.css +621 -0
- package/src/superlocalmemory/ui/css/neural-glass.css +6 -0
- package/src/superlocalmemory/ui/css/od-bridge.css +158 -0
- package/src/superlocalmemory/ui/favicon.svg +35 -4
- package/src/superlocalmemory/ui/index.html +306 -173
- package/src/superlocalmemory/ui/js/brain.js +5 -20
- package/src/superlocalmemory/ui/js/core.js +47 -31
- package/src/superlocalmemory/ui/js/dashboard.js +314 -63
- package/src/superlocalmemory/ui/js/event-delegation.js +102 -0
- package/src/superlocalmemory/ui/js/knowledge-graph.js +11 -11
- package/src/superlocalmemory/ui/js/math-health.js +1 -1
- package/src/superlocalmemory/ui/js/memories.js +15 -4
- package/src/superlocalmemory/ui/js/memory-chat.js +7 -7
- package/src/superlocalmemory/ui/js/ng-entities.js +6 -8
- package/src/superlocalmemory/ui/js/ng-ingestion.js +4 -4
- package/src/superlocalmemory/ui/js/ng-mesh.js +4 -9
- package/src/superlocalmemory/ui/js/ng-shell.js +8 -8
- package/src/superlocalmemory/ui/js/ng-skills.js +54 -2
- package/src/superlocalmemory/ui/js/od-agents.js +544 -0
- package/src/superlocalmemory/ui/js/od-auth-gate.js +257 -0
- package/src/superlocalmemory/ui/js/od-backup.js +780 -0
- package/src/superlocalmemory/ui/js/od-brain.js +779 -0
- package/src/superlocalmemory/ui/js/od-entities.js +579 -0
- package/src/superlocalmemory/ui/js/od-graph.js +593 -0
- package/src/superlocalmemory/ui/js/od-health.js +539 -0
- package/src/superlocalmemory/ui/js/od-mcp.js +508 -0
- package/src/superlocalmemory/ui/js/od-memories.js +887 -0
- package/src/superlocalmemory/ui/js/od-mesh.js +539 -0
- package/src/superlocalmemory/ui/js/od-operations.js +1250 -0
- package/src/superlocalmemory/ui/js/od-optimize.js +787 -0
- package/src/superlocalmemory/ui/js/od-settings.js +1053 -0
- package/src/superlocalmemory/ui/js/od-shell.js +593 -0
- package/src/superlocalmemory/ui/js/od-skills.js +573 -0
- package/src/superlocalmemory/ui/js/od-team.js +258 -0
- package/src/superlocalmemory/ui/js/profiles.js +159 -46
- package/src/superlocalmemory/ui/js/settings.js +2 -2
- package/src/superlocalmemory/ui/js/timeline.js +34 -5
- package/src/superlocalmemory/ui/js/trust-dashboard.js +2 -2
- package/src/superlocalmemory/vector/lancedb_backend.py +8 -6
- package/src/superlocalmemory/learning/behavioral_listener.py +0 -94
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""Durable, queryable ledger for bounded-loop runs.
|
|
2
|
+
|
|
3
|
+
Every lap produces one append-only :class:`LedgerEntry`. A ledger persists
|
|
4
|
+
those entries so a run can be inspected, resumed, and audited after the fact.
|
|
5
|
+
|
|
6
|
+
Two implementations ship:
|
|
7
|
+
|
|
8
|
+
* :class:`InMemoryLedger` — a dict-backed store used by tests and as a safe
|
|
9
|
+
fallback when no SLM data root is available.
|
|
10
|
+
* :class:`SLMMemoryLedger` — the real backend. It writes each lap through a
|
|
11
|
+
SuperLocalMemory engine so the ledger *is* memory: queryable via ``slm
|
|
12
|
+
recall``, visible in the dashboard, and resumable across sessions. This is
|
|
13
|
+
what makes SLM's take on bounded loops distinct — the loop's history lives
|
|
14
|
+
in the same durable store as everything else the agent remembers.
|
|
15
|
+
|
|
16
|
+
The engine-backed store mirrors the exact profile-scoped SQL contract the
|
|
17
|
+
shipped framework adapters already rely on, so it stays valid as the engine
|
|
18
|
+
evolves.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
from dataclasses import asdict, dataclass, field
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Any, Protocol, runtime_checkable
|
|
27
|
+
|
|
28
|
+
LEDGER_TAG = "slm-loop"
|
|
29
|
+
_LEDGER_IMPORTANCE = 2 # below ordinary user memories so laps never crowd recall
|
|
30
|
+
_SESSION_PREFIX = "loop:"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class LedgerEntry:
|
|
35
|
+
"""One immutable row recording what happened on a single lap."""
|
|
36
|
+
|
|
37
|
+
run_id: str
|
|
38
|
+
name: str
|
|
39
|
+
lap: int
|
|
40
|
+
ts: str
|
|
41
|
+
decision: str # continue | done | halt | pause | killed | error
|
|
42
|
+
passed: bool
|
|
43
|
+
detail: str
|
|
44
|
+
# The runner's own claim (audit-only; never terminates the loop) + its log.
|
|
45
|
+
agent_claimed_done: bool = False
|
|
46
|
+
runner_log: str = ""
|
|
47
|
+
budget: dict[str, Any] = field(default_factory=dict)
|
|
48
|
+
|
|
49
|
+
def to_json(self) -> str:
|
|
50
|
+
return json.dumps(asdict(self), ensure_ascii=False, separators=(",", ":"))
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def from_json(cls, text: str) -> "LedgerEntry | None":
|
|
54
|
+
try:
|
|
55
|
+
data = json.loads(text)
|
|
56
|
+
except (TypeError, json.JSONDecodeError):
|
|
57
|
+
return None
|
|
58
|
+
if not isinstance(data, dict) or "run_id" not in data or "lap" not in data:
|
|
59
|
+
return None
|
|
60
|
+
return cls(
|
|
61
|
+
run_id=str(data.get("run_id", "")),
|
|
62
|
+
name=str(data.get("name", "")),
|
|
63
|
+
lap=int(data.get("lap", 0)),
|
|
64
|
+
ts=str(data.get("ts", "")),
|
|
65
|
+
decision=str(data.get("decision", "")),
|
|
66
|
+
passed=bool(data.get("passed", False)),
|
|
67
|
+
detail=str(data.get("detail", "")),
|
|
68
|
+
agent_claimed_done=bool(data.get("agent_claimed_done", False)),
|
|
69
|
+
runner_log=str(data.get("runner_log", "")),
|
|
70
|
+
budget=data.get("budget") if isinstance(data.get("budget"), dict) else {},
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@runtime_checkable
|
|
75
|
+
class LedgerStore(Protocol):
|
|
76
|
+
"""Append-only audit trail keyed by run."""
|
|
77
|
+
|
|
78
|
+
def record(self, entry: LedgerEntry) -> None: ...
|
|
79
|
+
def laps(self, run_id: str) -> list[LedgerEntry]: ...
|
|
80
|
+
def runs(self, name: str) -> list[str]: ...
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class InMemoryLedger:
|
|
84
|
+
"""Process-local ledger. Never raises; ideal for tests and offline demos."""
|
|
85
|
+
|
|
86
|
+
def __init__(self) -> None:
|
|
87
|
+
self._by_run: dict[str, list[LedgerEntry]] = {}
|
|
88
|
+
|
|
89
|
+
def record(self, entry: LedgerEntry) -> None:
|
|
90
|
+
self._by_run.setdefault(entry.run_id, []).append(entry)
|
|
91
|
+
|
|
92
|
+
def laps(self, run_id: str) -> list[LedgerEntry]:
|
|
93
|
+
return list(self._by_run.get(run_id, ()))
|
|
94
|
+
|
|
95
|
+
def runs(self, name: str) -> list[str]:
|
|
96
|
+
seen: list[str] = []
|
|
97
|
+
for run_id, entries in self._by_run.items():
|
|
98
|
+
if entries and entries[0].name == name and run_id not in seen:
|
|
99
|
+
seen.append(run_id)
|
|
100
|
+
return seen
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class SLMMemoryLedger:
|
|
104
|
+
"""Ledger backed by a SuperLocalMemory engine store.
|
|
105
|
+
|
|
106
|
+
``store`` is any object exposing ``add(content, *, session_id, metadata)``,
|
|
107
|
+
``list_session(session_id)`` and ``list_prefix(prefix)`` — the small
|
|
108
|
+
contract :func:`open_engine_store` provides. Injecting it keeps this class
|
|
109
|
+
free of engine-construction concerns and trivially testable with a fake.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
def __init__(self, store: Any) -> None:
|
|
113
|
+
self._store = store
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def _session_id(run_id: str) -> str:
|
|
117
|
+
return f"{_SESSION_PREFIX}{run_id}"
|
|
118
|
+
|
|
119
|
+
def record(self, entry: LedgerEntry) -> None:
|
|
120
|
+
self._store.add(
|
|
121
|
+
entry.to_json(),
|
|
122
|
+
session_id=self._session_id(entry.run_id),
|
|
123
|
+
metadata={
|
|
124
|
+
"integration": "slm-loop",
|
|
125
|
+
"loop_name": entry.name,
|
|
126
|
+
"loop_run_id": entry.run_id,
|
|
127
|
+
"loop_lap": entry.lap,
|
|
128
|
+
"loop_decision": entry.decision,
|
|
129
|
+
"tags": [LEDGER_TAG, f"loop:{entry.name}"],
|
|
130
|
+
"importance": _LEDGER_IMPORTANCE,
|
|
131
|
+
"project_name": "slm-loop",
|
|
132
|
+
},
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
def laps(self, run_id: str) -> list[LedgerEntry]:
|
|
136
|
+
rows = self._store.list_session(self._session_id(run_id))
|
|
137
|
+
entries = [LedgerEntry.from_json(r.get("content", "")) for r in rows]
|
|
138
|
+
return [e for e in entries if e is not None]
|
|
139
|
+
|
|
140
|
+
def runs(self, name: str) -> list[str]:
|
|
141
|
+
"""Run ids for ``name``, newest run first.
|
|
142
|
+
|
|
143
|
+
``list_prefix`` returns rows created_at DESC, so the first time a run's
|
|
144
|
+
id is seen it is its most-recent lap; ``slm loop history`` therefore
|
|
145
|
+
lists the most recent runs first.
|
|
146
|
+
"""
|
|
147
|
+
rows = self._store.list_prefix(_SESSION_PREFIX)
|
|
148
|
+
ordered: list[str] = []
|
|
149
|
+
for row in rows:
|
|
150
|
+
entry = LedgerEntry.from_json(row.get("content", ""))
|
|
151
|
+
if entry is not None and entry.name == name and entry.run_id not in ordered:
|
|
152
|
+
ordered.append(entry.run_id)
|
|
153
|
+
return ordered
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class _EngineLedgerStore:
|
|
157
|
+
"""Minimal profile-scoped store over a SuperLocalMemory engine.
|
|
158
|
+
|
|
159
|
+
Uses ``MemoryEngine.store`` for writes and direct, escaped, profile-scoped
|
|
160
|
+
reads — the same contract the LangChain/LlamaIndex adapters depend on.
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
def __init__(self, engine: Any, *, owns_engine: bool = True) -> None:
|
|
164
|
+
self._engine = engine
|
|
165
|
+
# When False, this store does NOT own the engine's lifecycle (the
|
|
166
|
+
# caller — e.g. the MCP daemon — keeps it), so close() must not tear
|
|
167
|
+
# down a shared engine. open_engine_store() passes True (it built the
|
|
168
|
+
# engine); engine_backed_ledger() passes False (daemon-owned engine).
|
|
169
|
+
self._owns_engine = owns_engine
|
|
170
|
+
|
|
171
|
+
def add(self, content: str, *, session_id: str, metadata: dict) -> None:
|
|
172
|
+
# The parent memory row is what the ledger needs; fact extraction is a
|
|
173
|
+
# bonus, so (unlike chat history) an empty fact set is not an error.
|
|
174
|
+
self._engine.store(content, session_id=session_id, metadata=metadata)
|
|
175
|
+
|
|
176
|
+
def list_session(self, session_id: str) -> list[dict]:
|
|
177
|
+
# Cap the read: a bounded-loop run is capped at max_iterations laps, so
|
|
178
|
+
# a legitimate run is small; the LIMIT stops a pathologically long
|
|
179
|
+
# session_id from forcing an unbounded materialization on every
|
|
180
|
+
# `slm loop show` / history lookup.
|
|
181
|
+
rows = self._engine.db.execute(
|
|
182
|
+
"SELECT content, created_at FROM memories "
|
|
183
|
+
"WHERE profile_id=? AND session_id=? "
|
|
184
|
+
"ORDER BY created_at ASC, rowid ASC LIMIT 5000",
|
|
185
|
+
(self._engine.profile_id, session_id),
|
|
186
|
+
)
|
|
187
|
+
return [dict(row) for row in rows]
|
|
188
|
+
|
|
189
|
+
def list_prefix(self, prefix: str) -> list[dict]:
|
|
190
|
+
escaped = prefix.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
|
|
191
|
+
# Cap the scan so a long-lived, high-volume ledger can't force an
|
|
192
|
+
# unbounded read on `slm loop history`.
|
|
193
|
+
rows = self._engine.db.execute(
|
|
194
|
+
"SELECT content, created_at FROM memories "
|
|
195
|
+
"WHERE profile_id=? AND session_id LIKE ? ESCAPE '\\' "
|
|
196
|
+
"ORDER BY created_at DESC, rowid DESC LIMIT 5000",
|
|
197
|
+
(self._engine.profile_id, escaped + "%"),
|
|
198
|
+
)
|
|
199
|
+
return [dict(row) for row in rows]
|
|
200
|
+
|
|
201
|
+
def close(self) -> None:
|
|
202
|
+
if self._owns_engine:
|
|
203
|
+
self._engine.close()
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def engine_backed_ledger(engine: Any) -> SLMMemoryLedger:
|
|
207
|
+
"""Build an SLM-backed ledger over an ALREADY-OPEN engine.
|
|
208
|
+
|
|
209
|
+
Unlike :func:`open_engine_store`, this neither creates nor owns the engine —
|
|
210
|
+
the caller (e.g. the MCP daemon, which keeps one long-lived engine per
|
|
211
|
+
profile) retains full ownership and lifecycle. The returned ledger never
|
|
212
|
+
closes the engine, so it is safe to build one per tool call. The engine's
|
|
213
|
+
per-call, WAL-mode connection model makes the ledger's reads/writes safe
|
|
214
|
+
from a worker thread.
|
|
215
|
+
"""
|
|
216
|
+
return SLMMemoryLedger(_EngineLedgerStore(engine, owns_engine=False))
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def open_engine_store(db_path: str | Path) -> _EngineLedgerStore:
|
|
220
|
+
"""Build an engine-backed ledger store rooted at ``db_path``.
|
|
221
|
+
|
|
222
|
+
Raises ``ImportError`` with an install hint if the SLM runtime is missing.
|
|
223
|
+
"""
|
|
224
|
+
from dataclasses import replace
|
|
225
|
+
|
|
226
|
+
try:
|
|
227
|
+
from superlocalmemory.core.config import SLMConfig
|
|
228
|
+
from superlocalmemory.core.engine import MemoryEngine
|
|
229
|
+
from superlocalmemory.storage.models import Mode
|
|
230
|
+
except ImportError as exc: # pragma: no cover - defensive
|
|
231
|
+
raise ImportError(
|
|
232
|
+
"SuperLocalMemory runtime is required for the SLM-backed loop "
|
|
233
|
+
"ledger. Install it with: python -m pip install superlocalmemory."
|
|
234
|
+
) from exc
|
|
235
|
+
|
|
236
|
+
path = Path(db_path).expanduser().resolve()
|
|
237
|
+
config = SLMConfig.for_mode(Mode.A, base_dir=path.parent)
|
|
238
|
+
config.db_path = path
|
|
239
|
+
config.forgetting = replace(config.forgetting, enabled=False)
|
|
240
|
+
config.retrieval.use_cross_encoder = False
|
|
241
|
+
engine = MemoryEngine(config)
|
|
242
|
+
engine.initialize()
|
|
243
|
+
return _EngineLedgerStore(engine)
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""Immutable value types for SuperLocalMemory bounded loops.
|
|
2
|
+
|
|
3
|
+
A *bounded loop* is an agent loop that terminates when an independent gate
|
|
4
|
+
passes — never when the agent claims it is finished. This module holds the
|
|
5
|
+
pure data types the loop engine reasons over.
|
|
6
|
+
|
|
7
|
+
Design rules (kept deliberately strict):
|
|
8
|
+
* Standard-library imports only. No I/O, no framework, no side effects.
|
|
9
|
+
* Every dataclass is ``frozen=True`` — any attribute mutation raises
|
|
10
|
+
``TypeError`` at runtime, so a lap result cannot be rewritten after the
|
|
11
|
+
gate has judged it.
|
|
12
|
+
* Timestamps are ISO-8601 strings supplied by the engine's clock, never
|
|
13
|
+
produced here with ``datetime.now()``.
|
|
14
|
+
|
|
15
|
+
This is SuperLocalMemory's own realization of the bounded-loop concept; the
|
|
16
|
+
loop-control discipline it encodes (gate-verified termination, enforced
|
|
17
|
+
bounds, an advisory-only agent claim) is a general practice, reimplemented
|
|
18
|
+
here against SLM's durable memory rather than a flat file.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from dataclasses import dataclass, field
|
|
24
|
+
from enum import Enum
|
|
25
|
+
from typing import Any, Optional
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Rung(str, Enum):
|
|
29
|
+
"""Autonomy rung governing how much a human stays in the loop.
|
|
30
|
+
|
|
31
|
+
``L1`` report — a human reads every verdict; the loop still exits on
|
|
32
|
+
a passing gate but nothing is auto-approved.
|
|
33
|
+
``L2`` assisted — the agent acts but pauses for human approval before a
|
|
34
|
+
passing gate is accepted as DONE.
|
|
35
|
+
``L3`` unattended — the agent acts autonomously; approval is derived from
|
|
36
|
+
the bounds alone.
|
|
37
|
+
|
|
38
|
+
Subclassing ``(str, Enum)`` makes ``Rung.L2 == "L2"`` true and
|
|
39
|
+
``Rung("L2")`` reconstruct the member, so a rung round-trips through JSON
|
|
40
|
+
or a CLI argument without a hand-written lookup table.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
L1 = "L1"
|
|
44
|
+
L2 = "L2"
|
|
45
|
+
L3 = "L3"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Status(str, Enum):
|
|
49
|
+
"""Terminal status of a bounded-loop run.
|
|
50
|
+
|
|
51
|
+
``DONE`` — the gate passed and approval was granted or not required.
|
|
52
|
+
``HALT`` — a safety bound tripped (iteration cap, no progress, budget).
|
|
53
|
+
``PAUSE`` — the gate passed but required approval was not granted.
|
|
54
|
+
``KILLED`` — an external kill switch tripped between laps.
|
|
55
|
+
``ERROR`` — the runner or gate raised before a verdict was produced.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
DONE = "DONE"
|
|
59
|
+
HALT = "HALT"
|
|
60
|
+
PAUSE = "PAUSE"
|
|
61
|
+
KILLED = "KILLED"
|
|
62
|
+
ERROR = "ERROR"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass(frozen=True)
|
|
66
|
+
class Bounds:
|
|
67
|
+
"""The safety envelope a loop runs inside.
|
|
68
|
+
|
|
69
|
+
``max_iterations`` hard cap on laps; required and must be >= 1.
|
|
70
|
+
``no_progress_window`` consecutive no-change laps that trigger a HALT.
|
|
71
|
+
``max_tokens`` cumulative token budget across laps, or ``None``.
|
|
72
|
+
``max_wallclock_s`` wall-clock ceiling in seconds, or ``None``.
|
|
73
|
+
``require_approval`` ``True``/``False`` forces the approval posture;
|
|
74
|
+
``None`` derives it from the rung (L1 -> no
|
|
75
|
+
approval, L2/L3 -> approval required).
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
max_iterations: int
|
|
79
|
+
no_progress_window: int = 3
|
|
80
|
+
max_tokens: Optional[int] = None
|
|
81
|
+
max_wallclock_s: Optional[float] = None
|
|
82
|
+
require_approval: Optional[bool] = None
|
|
83
|
+
|
|
84
|
+
def __post_init__(self) -> None:
|
|
85
|
+
if self.max_iterations < 1:
|
|
86
|
+
raise ValueError("max_iterations must be >= 1")
|
|
87
|
+
if self.no_progress_window < 0:
|
|
88
|
+
raise ValueError("no_progress_window must be >= 0")
|
|
89
|
+
if self.max_tokens is not None and self.max_tokens < 0:
|
|
90
|
+
raise ValueError("max_tokens must be >= 0 when set")
|
|
91
|
+
if self.max_wallclock_s is not None and self.max_wallclock_s < 0:
|
|
92
|
+
raise ValueError("max_wallclock_s must be >= 0 when set")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass(frozen=True)
|
|
96
|
+
class Verdict:
|
|
97
|
+
"""The independent gate's judgement of a single lap.
|
|
98
|
+
|
|
99
|
+
``passed`` True only when the gate mechanically confirmed the goal.
|
|
100
|
+
``detail`` human-readable one-line summary (required, non-empty).
|
|
101
|
+
``evidence`` structured gate output (counts, tails, diffs). Defaults to a
|
|
102
|
+
fresh dict per instance via ``default_factory`` so verdicts do
|
|
103
|
+
not share one mutable dict.
|
|
104
|
+
|
|
105
|
+
``passed=True`` is necessary but not sufficient for the loop to exit; the
|
|
106
|
+
engine still consults the approval rung.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
passed: bool
|
|
110
|
+
detail: str
|
|
111
|
+
evidence: dict[str, Any] = field(default_factory=dict)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@dataclass(frozen=True)
|
|
115
|
+
class LapResult:
|
|
116
|
+
"""What the runner reports after proposing one lap of work.
|
|
117
|
+
|
|
118
|
+
``changed`` True if the runner altered the workspace/state.
|
|
119
|
+
``agent_claimed_done`` the agent's own "I am finished" signal. Recorded for
|
|
120
|
+
audit and **never** used to terminate the loop — the
|
|
121
|
+
gate is the sole authority.
|
|
122
|
+
``tokens`` tokens spent this lap (0 when unknown).
|
|
123
|
+
``log`` short runner log for the lap.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
changed: bool
|
|
127
|
+
agent_claimed_done: bool = False
|
|
128
|
+
tokens: int = 0
|
|
129
|
+
log: str = ""
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@dataclass(frozen=True)
|
|
133
|
+
class Outcome:
|
|
134
|
+
"""The final result of a bounded-loop run.
|
|
135
|
+
|
|
136
|
+
``status`` terminal status.
|
|
137
|
+
``reason`` short machine-friendly explanation ("gate-passed",
|
|
138
|
+
"no-progress", "max-iterations", "awaiting-approval",
|
|
139
|
+
"killed", or a gate/runner error string).
|
|
140
|
+
``laps`` number of laps executed at termination.
|
|
141
|
+
``run_id`` identifier used to locate this run's ledger in SLM memory.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
status: Status
|
|
145
|
+
reason: str
|
|
146
|
+
laps: int
|
|
147
|
+
run_id: str
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def ok(self) -> bool:
|
|
151
|
+
"""True only for a DONE outcome — the single success state."""
|
|
152
|
+
return self.status is Status.DONE
|
|
@@ -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
|
+
}
|