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
|
@@ -118,8 +118,13 @@ def cmd_compress_prose(args: Namespace) -> None:
|
|
|
118
118
|
cfg = store.get()
|
|
119
119
|
|
|
120
120
|
fields: dict = {"compress_prose": (value == "on")}
|
|
121
|
-
if value == "on"
|
|
122
|
-
|
|
121
|
+
if value == "on":
|
|
122
|
+
# Prose (Layer 2) only fires in aggressive mode, so turning it on sets a
|
|
123
|
+
# COHERENT state — it can never be left enabled-but-inert in safe mode.
|
|
124
|
+
if not cfg.compress_enabled:
|
|
125
|
+
fields["compress_enabled"] = True
|
|
126
|
+
if cfg.compress_mode != "aggressive":
|
|
127
|
+
fields["compress_mode"] = "aggressive"
|
|
123
128
|
|
|
124
129
|
try:
|
|
125
130
|
cfg = dataclasses.replace(cfg, **fields)
|
|
@@ -129,13 +134,18 @@ def cmd_compress_prose(args: Namespace) -> None:
|
|
|
129
134
|
sys.exit(1)
|
|
130
135
|
|
|
131
136
|
if use_json:
|
|
132
|
-
print(json.dumps({
|
|
137
|
+
print(json.dumps({
|
|
138
|
+
"status": "ok",
|
|
139
|
+
"compress_prose": value == "on",
|
|
140
|
+
"compress_mode": cfg.compress_mode,
|
|
141
|
+
}))
|
|
133
142
|
return
|
|
134
143
|
|
|
135
144
|
print(f"Prose compression (LLMLingua-2): {'ENABLED' if value == 'on' else 'DISABLED'}.")
|
|
136
145
|
if value == "on":
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
146
|
+
if "compress_mode" in fields:
|
|
147
|
+
print(" Compression mode set to 'aggressive' (required for Layer 2).")
|
|
148
|
+
print(" Requires the llmlingua package for lossy prose compression.")
|
|
149
|
+
if "compress_enabled" in fields:
|
|
150
|
+
print(" (also enabled global compression)")
|
|
141
151
|
print("Daemon hot-reload: active within 2s.")
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""``slm loop`` — inspect and demonstrate bounded loops backed by SLM memory.
|
|
2
|
+
|
|
3
|
+
Subcommands:
|
|
4
|
+
|
|
5
|
+
* ``slm loop demo`` run a built-in, keyless convergence demo (stub
|
|
6
|
+
proposer + deterministic gate) and persist every
|
|
7
|
+
lap to SLM memory. Proves the engine + ledger
|
|
8
|
+
end to end without a credentialed agent.
|
|
9
|
+
* ``slm loop history [--name]`` list recorded loop runs from SLM memory.
|
|
10
|
+
* ``slm loop show <run_id>`` show every lap of one run.
|
|
11
|
+
|
|
12
|
+
The durable value here is that a loop's history lives in the same SLM data
|
|
13
|
+
root as everything else the agent remembers — queryable via ``slm recall`` and
|
|
14
|
+
visible in the dashboard. Reads and the demo run through an in-process engine
|
|
15
|
+
store rooted at the active data root (``SLM_DATA_DIR`` or
|
|
16
|
+
``~/.superlocalmemory``); point ``SLM_DATA_DIR`` elsewhere to sandbox a run.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import os
|
|
23
|
+
from argparse import Namespace
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
from superlocalmemory.loops import (
|
|
28
|
+
Bounds,
|
|
29
|
+
LapResult,
|
|
30
|
+
SLMMemoryLedger,
|
|
31
|
+
Verdict,
|
|
32
|
+
open_engine_store,
|
|
33
|
+
run_bounded_loop,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _data_root() -> Path:
|
|
38
|
+
value = (
|
|
39
|
+
os.environ.get("SLM_DATA_DIR")
|
|
40
|
+
or os.environ.get("SL_MEMORY_PATH")
|
|
41
|
+
or os.environ.get("SLM_HOME")
|
|
42
|
+
)
|
|
43
|
+
return Path(value).expanduser() if value else Path.home() / ".superlocalmemory"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class _FailOpenLedger:
|
|
47
|
+
"""Wrap a ledger so a memory write hiccup never aborts a running loop.
|
|
48
|
+
|
|
49
|
+
A ledger is observability; the loop's correctness comes from the gate.
|
|
50
|
+
Write errors are counted and surfaced after the run rather than raised
|
|
51
|
+
mid-flight, so we never silently pretend they did not happen.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self, inner: Any) -> None:
|
|
55
|
+
self._inner = inner
|
|
56
|
+
self.write_errors: list[str] = []
|
|
57
|
+
|
|
58
|
+
def record(self, entry: Any) -> None:
|
|
59
|
+
try:
|
|
60
|
+
self._inner.record(entry)
|
|
61
|
+
except Exception as exc: # pragma: no cover - defensive path
|
|
62
|
+
self.write_errors.append(f"lap {getattr(entry, 'lap', '?')}: {exc}")
|
|
63
|
+
|
|
64
|
+
def laps(self, run_id: str) -> list:
|
|
65
|
+
return self._inner.laps(run_id)
|
|
66
|
+
|
|
67
|
+
def runs(self, name: str) -> list:
|
|
68
|
+
return self._inner.runs(name)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _open_ledger() -> tuple[SLMMemoryLedger, Any]:
|
|
72
|
+
store = open_engine_store(_data_root() / "memory.db")
|
|
73
|
+
return SLMMemoryLedger(store), store
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def cmd_loop(args: Namespace) -> None:
|
|
77
|
+
action = getattr(args, "loop_command", None)
|
|
78
|
+
if action == "demo":
|
|
79
|
+
_cmd_demo(args)
|
|
80
|
+
elif action == "history":
|
|
81
|
+
_cmd_history(args)
|
|
82
|
+
elif action == "show":
|
|
83
|
+
_cmd_show(args)
|
|
84
|
+
else:
|
|
85
|
+
print("Usage: slm loop {demo|history|show} [options]")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _cmd_demo(args: Namespace) -> None:
|
|
89
|
+
"""Run the convergence demo: the gate fails twice, then passes on lap 3."""
|
|
90
|
+
iterations = int(getattr(args, "iterations", 10) or 10)
|
|
91
|
+
as_json = bool(getattr(args, "json", False))
|
|
92
|
+
pass_on = 3
|
|
93
|
+
|
|
94
|
+
ledger, store = _open_ledger()
|
|
95
|
+
guarded = _FailOpenLedger(ledger)
|
|
96
|
+
try:
|
|
97
|
+
outcome = run_bounded_loop(
|
|
98
|
+
"convergence-demo",
|
|
99
|
+
bounds=Bounds(max_iterations=iterations),
|
|
100
|
+
runner=lambda lap: LapResult(changed=True, tokens=8),
|
|
101
|
+
gate=lambda lap: Verdict(lap >= pass_on, f"demo gate: lap {lap}"),
|
|
102
|
+
ledger=guarded,
|
|
103
|
+
)
|
|
104
|
+
laps = guarded.laps(outcome.run_id)
|
|
105
|
+
finally:
|
|
106
|
+
store.close()
|
|
107
|
+
|
|
108
|
+
if as_json:
|
|
109
|
+
print(json.dumps({
|
|
110
|
+
"status": outcome.status.value,
|
|
111
|
+
"reason": outcome.reason,
|
|
112
|
+
"laps": outcome.laps,
|
|
113
|
+
"run_id": outcome.run_id,
|
|
114
|
+
"ledger": [
|
|
115
|
+
{"lap": e.lap, "decision": e.decision, "passed": e.passed}
|
|
116
|
+
for e in laps
|
|
117
|
+
],
|
|
118
|
+
"write_errors": guarded.write_errors,
|
|
119
|
+
}, indent=2))
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
mark = "✓" if outcome.ok else "✗"
|
|
123
|
+
print(f"{mark} [{outcome.status.value}] {outcome.reason} (laps: {outcome.laps})")
|
|
124
|
+
for e in laps:
|
|
125
|
+
gate = "gate-pass" if e.passed else "gate-fail"
|
|
126
|
+
print(f" lap {e.lap}: {e.decision:<8} {gate} {e.detail}")
|
|
127
|
+
print(f"run_id: {outcome.run_id} (recall with tag loop:convergence-demo)")
|
|
128
|
+
if guarded.write_errors:
|
|
129
|
+
print(f"WARNING: {len(guarded.write_errors)} ledger write error(s): "
|
|
130
|
+
f"{guarded.write_errors[0]}")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _cmd_history(args: Namespace) -> None:
|
|
134
|
+
name = getattr(args, "name", None) or "convergence-demo"
|
|
135
|
+
as_json = bool(getattr(args, "json", False))
|
|
136
|
+
ledger, store = _open_ledger()
|
|
137
|
+
try:
|
|
138
|
+
run_ids = ledger.runs(name)
|
|
139
|
+
rows = []
|
|
140
|
+
for rid in run_ids:
|
|
141
|
+
laps = ledger.laps(rid)
|
|
142
|
+
last = laps[-1] if laps else None
|
|
143
|
+
rows.append({
|
|
144
|
+
"run_id": rid,
|
|
145
|
+
"laps": len(laps),
|
|
146
|
+
"final": last.decision if last else "unknown",
|
|
147
|
+
"ts": last.ts if last else "",
|
|
148
|
+
})
|
|
149
|
+
finally:
|
|
150
|
+
store.close()
|
|
151
|
+
|
|
152
|
+
if as_json:
|
|
153
|
+
print(json.dumps({"name": name, "runs": rows}, indent=2))
|
|
154
|
+
return
|
|
155
|
+
if not rows:
|
|
156
|
+
print(f"No recorded runs for loop '{name}'.")
|
|
157
|
+
return
|
|
158
|
+
print(f"Runs for loop '{name}':")
|
|
159
|
+
for r in rows:
|
|
160
|
+
print(f" {r['run_id']:<28} laps={r['laps']:<3} final={r['final']:<8} {r['ts']}")
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _cmd_show(args: Namespace) -> None:
|
|
164
|
+
run_id = getattr(args, "run_id", None)
|
|
165
|
+
as_json = bool(getattr(args, "json", False))
|
|
166
|
+
if not run_id:
|
|
167
|
+
print("Usage: slm loop show <run_id>")
|
|
168
|
+
return
|
|
169
|
+
ledger, store = _open_ledger()
|
|
170
|
+
try:
|
|
171
|
+
laps = ledger.laps(run_id)
|
|
172
|
+
finally:
|
|
173
|
+
store.close()
|
|
174
|
+
|
|
175
|
+
if as_json:
|
|
176
|
+
print(json.dumps({
|
|
177
|
+
"run_id": run_id,
|
|
178
|
+
"laps": [
|
|
179
|
+
{"lap": e.lap, "ts": e.ts, "decision": e.decision,
|
|
180
|
+
"passed": e.passed, "detail": e.detail, "budget": e.budget}
|
|
181
|
+
for e in laps
|
|
182
|
+
],
|
|
183
|
+
}, indent=2))
|
|
184
|
+
return
|
|
185
|
+
if not laps:
|
|
186
|
+
print(f"No ledger entries for run '{run_id}'.")
|
|
187
|
+
return
|
|
188
|
+
print(f"Loop run {run_id} ({laps[0].name}):")
|
|
189
|
+
for e in laps:
|
|
190
|
+
gate = "gate-pass" if e.passed else "gate-fail"
|
|
191
|
+
print(f" lap {e.lap}: {e.decision:<8} {gate} {e.detail} "
|
|
192
|
+
f"[tokens={e.budget.get('tokens', 0)}]")
|
|
@@ -80,6 +80,8 @@ _NO_DAEMON_COMMANDS = {
|
|
|
80
80
|
"wrap",
|
|
81
81
|
# V3.6 Optimize commands that are config read/write only.
|
|
82
82
|
"optimize", "cache", "compress", "help-optimize",
|
|
83
|
+
# Bounded loops use an in-process engine store, not the daemon.
|
|
84
|
+
"loop",
|
|
83
85
|
# Lifecycle orchestration must run before any global auto-start hook.
|
|
84
86
|
"serve", "restart",
|
|
85
87
|
}
|
|
@@ -276,6 +278,14 @@ def main() -> None:
|
|
|
276
278
|
db_scale_p.add_argument("--stage-id", help="Stage identifier required by verify/promote")
|
|
277
279
|
db_scale_p.add_argument("--backup-id", help="Backup identifier required by rollback")
|
|
278
280
|
|
|
281
|
+
# -- Mesh inspection (v3.7.9, M-03) --------------------------------
|
|
282
|
+
mesh_p = sub.add_parser("mesh", help="Inspect the local agent mesh (status/peers)")
|
|
283
|
+
mesh_p.add_argument(
|
|
284
|
+
"mesh_action",
|
|
285
|
+
choices=("status", "peers"),
|
|
286
|
+
help="status: broker health + stats; peers: active peer sessions",
|
|
287
|
+
)
|
|
288
|
+
|
|
279
289
|
# -- Memory Operations ---------------------------------------------
|
|
280
290
|
remember_p = sub.add_parser("remember", help="Store a memory (extracts facts, builds graph)")
|
|
281
291
|
remember_p.add_argument("content", help="Content to remember")
|
|
@@ -304,9 +314,15 @@ def main() -> None:
|
|
|
304
314
|
help=f"Max results (default {CANONICAL_RECALL_LIMIT})",
|
|
305
315
|
)
|
|
306
316
|
recall_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
|
|
317
|
+
recall_p.add_argument(
|
|
318
|
+
"--window", default="",
|
|
319
|
+
help="Restrict results to an event-time range: a relative span "
|
|
320
|
+
"(24h, 7d, 30d, 1y) or an explicit range (2026-07-01..2026-07-31). "
|
|
321
|
+
"Default: no time filter.",
|
|
322
|
+
)
|
|
307
323
|
recall_p.add_argument(
|
|
308
324
|
"--fast", action="store_true",
|
|
309
|
-
help="Skip
|
|
325
|
+
help="Skip graph-assisted candidate expansion and remote agentic verification for a "
|
|
310
326
|
"latency-bounded response. Other configured retrieval channels still run. "
|
|
311
327
|
"Use when you need recall before a tool call (e.g. before WebSearch).",
|
|
312
328
|
)
|
|
@@ -332,8 +348,8 @@ def main() -> None:
|
|
|
332
348
|
)
|
|
333
349
|
|
|
334
350
|
forget_p = sub.add_parser("forget", help="Delete memories matching a query (fuzzy)")
|
|
335
|
-
forget_p.add_argument("query", help="Query to match for deletion")
|
|
336
|
-
forget_p.add_argument("--dry-run", action="store_true", default=False, help="Preview matches without deleting")
|
|
351
|
+
forget_p.add_argument("query", nargs="?", default=None, help="Query to match for deletion. Optional with --dry-run (previews all memories).")
|
|
352
|
+
forget_p.add_argument("--dry-run", action="store_true", default=False, help="Preview matches without deleting. With no query, previews every memory.")
|
|
337
353
|
forget_p.add_argument("--yes", "-y", action="store_true", help="Skip confirmation prompt")
|
|
338
354
|
forget_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
|
|
339
355
|
|
|
@@ -361,7 +377,7 @@ def main() -> None:
|
|
|
361
377
|
help="Show extended status: migration log, daemon port, disabled marker, last version",
|
|
362
378
|
)
|
|
363
379
|
|
|
364
|
-
health_p = sub.add_parser("health", help="Math layer health (
|
|
380
|
+
health_p = sub.add_parser("health", help="Math layer health (scoring, consistency, and lifecycle layers)")
|
|
365
381
|
health_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
|
|
366
382
|
|
|
367
383
|
trace_p = sub.add_parser("trace", help="Recall with per-channel score breakdown")
|
|
@@ -774,6 +790,25 @@ def main() -> None:
|
|
|
774
790
|
|
|
775
791
|
# ---- end SLM v3.6 Optimize subcommands ----
|
|
776
792
|
|
|
793
|
+
# slm loop demo|history|show — bounded, gate-verified agent loops (v3.8.0)
|
|
794
|
+
loop_p = sub.add_parser(
|
|
795
|
+
"loop",
|
|
796
|
+
help="Bounded loops: gate-verified agent loops with an SLM-backed ledger",
|
|
797
|
+
)
|
|
798
|
+
loop_sub = loop_p.add_subparsers(dest="loop_command", title="loop subcommands")
|
|
799
|
+
loop_demo_p = loop_sub.add_parser(
|
|
800
|
+
"demo", help="Run the keyless convergence demo (proves engine + ledger)")
|
|
801
|
+
loop_demo_p.add_argument(
|
|
802
|
+
"--iterations", type=int, default=10, help="Max iterations (default: 10)")
|
|
803
|
+
loop_hist_p = loop_sub.add_parser("history", help="List recorded loop runs")
|
|
804
|
+
loop_hist_p.add_argument(
|
|
805
|
+
"--name", default=None, help="Loop name (default: convergence-demo)")
|
|
806
|
+
loop_show_p = loop_sub.add_parser("show", help="Show every lap of one run")
|
|
807
|
+
loop_show_p.add_argument("run_id", help="Run id (from history)")
|
|
808
|
+
for _sp in loop_sub.choices.values():
|
|
809
|
+
_sp.add_argument("--json", action="store_true",
|
|
810
|
+
help="Output structured JSON (agent-native)")
|
|
811
|
+
|
|
777
812
|
args = parser.parse_args()
|
|
778
813
|
|
|
779
814
|
if not args.command:
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under AGPL-3.0-or-later
|
|
3
|
+
"""`slm mesh` — inspect the local agent mesh from the terminal (M-03, v3.7.9).
|
|
4
|
+
|
|
5
|
+
Before 3.7.9 the mesh was reachable only via MCP tools, the dashboard, and
|
|
6
|
+
Claude Code skills — there was no terminal command to check broker health or
|
|
7
|
+
list peer sessions. This is a thin, read-only wrapper over the same
|
|
8
|
+
capability-authenticated `/mesh/*` daemon endpoints the MCP tools use.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
from argparse import Namespace
|
|
14
|
+
|
|
15
|
+
_ACTION_ENDPOINTS = {
|
|
16
|
+
"status": "/mesh/status",
|
|
17
|
+
"peers": "/mesh/peers",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def cmd_mesh(args: Namespace) -> int:
|
|
22
|
+
action = getattr(args, "mesh_action", None)
|
|
23
|
+
endpoint = _ACTION_ENDPOINTS.get(action)
|
|
24
|
+
if endpoint is None:
|
|
25
|
+
print("Usage: slm mesh {status|peers}")
|
|
26
|
+
return 2
|
|
27
|
+
|
|
28
|
+
from superlocalmemory.cli.daemon import daemon_request
|
|
29
|
+
|
|
30
|
+
result = daemon_request("GET", endpoint)
|
|
31
|
+
if result is None:
|
|
32
|
+
print(
|
|
33
|
+
"Mesh: cannot reach the daemon broker. Is the daemon running? "
|
|
34
|
+
"(slm serve start)"
|
|
35
|
+
)
|
|
36
|
+
return 1
|
|
37
|
+
print(json.dumps(result, indent=2, sort_keys=True))
|
|
38
|
+
return 0
|
|
@@ -98,6 +98,9 @@ def cmd_optimize_status(args: Namespace) -> None:
|
|
|
98
98
|
print(f" Compress: {'enabled' if cfg.compress_enabled else 'disabled'}"
|
|
99
99
|
f" (mode: {cfg.compress_mode},"
|
|
100
100
|
f" prose/L2: {'ON' if cfg.compress_prose else 'OFF'})")
|
|
101
|
+
if cfg.compress_prose or cfg.compress_mode == "aggressive":
|
|
102
|
+
print(" note: the live proxy applies LOSSLESS compression only;"
|
|
103
|
+
" lossy Layer-2 (prose) runs via the slm_compress tool.")
|
|
101
104
|
proxy_status = f"running on :{OPTIMIZE_DEFAULT_PORT}" if proxy_running else "not running"
|
|
102
105
|
print(f" Proxy: {proxy_status}")
|
|
103
106
|
print(f" Config: ~/.superlocalmemory/optimize.json (version {cfg.config_version})")
|
|
@@ -43,6 +43,7 @@ _MAX_RETRY_DELAY_SECONDS = 3600
|
|
|
43
43
|
_SCHEMA = """
|
|
44
44
|
CREATE TABLE IF NOT EXISTS pending_memories (
|
|
45
45
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
46
|
+
profile_id TEXT NOT NULL DEFAULT 'default',
|
|
46
47
|
content TEXT NOT NULL,
|
|
47
48
|
tags TEXT DEFAULT '',
|
|
48
49
|
metadata TEXT DEFAULT '{}',
|
|
@@ -62,6 +63,12 @@ def _get_db(base_dir: Path | None = None) -> sqlite3.Connection:
|
|
|
62
63
|
db_path = d / _PENDING_DB
|
|
63
64
|
conn = sqlite3.connect(str(db_path), timeout=5)
|
|
64
65
|
conn.execute("PRAGMA journal_mode=WAL")
|
|
66
|
+
# C4: pending queue can hold not-yet-materialized memory content owner-only.
|
|
67
|
+
try:
|
|
68
|
+
from superlocalmemory.core.security_primitives import harden_db_perms
|
|
69
|
+
harden_db_perms(db_path)
|
|
70
|
+
except Exception:
|
|
71
|
+
pass
|
|
65
72
|
conn.execute(_SCHEMA)
|
|
66
73
|
columns = {
|
|
67
74
|
row[1]
|
|
@@ -73,6 +80,15 @@ def _get_db(base_dir: Path | None = None) -> sqlite3.Connection:
|
|
|
73
80
|
"next_retry_at REAL DEFAULT 0"
|
|
74
81
|
)
|
|
75
82
|
conn.commit()
|
|
83
|
+
# Per-profile isolation: a queued item must materialize under the profile
|
|
84
|
+
# that was active when it was enqueued — never under whatever profile is
|
|
85
|
+
# active at drain time. Existing rows backfill to 'default'.
|
|
86
|
+
if "profile_id" not in columns:
|
|
87
|
+
conn.execute(
|
|
88
|
+
"ALTER TABLE pending_memories ADD COLUMN "
|
|
89
|
+
"profile_id TEXT NOT NULL DEFAULT 'default'"
|
|
90
|
+
)
|
|
91
|
+
conn.commit()
|
|
76
92
|
# Pre-V3.7 rows were terminally hidden after three failures. Restore them
|
|
77
93
|
# to the retry queue; M018 makes replay idempotent and no raw evidence may
|
|
78
94
|
# remain stranded solely because an older version exhausted its counter.
|
|
@@ -89,8 +105,12 @@ def store_pending(
|
|
|
89
105
|
tags: str = "",
|
|
90
106
|
metadata: dict | None = None,
|
|
91
107
|
base_dir: Path | None = None,
|
|
108
|
+
profile_id: str = "default",
|
|
92
109
|
) -> int:
|
|
93
|
-
"""Store content in pending table. Returns the row ID.
|
|
110
|
+
"""Store content in pending table under a profile. Returns the row ID.
|
|
111
|
+
|
|
112
|
+
``profile_id`` captures the profile active at ENQUEUE time so a later
|
|
113
|
+
profile switch can never redirect this memory to a different profile.
|
|
94
114
|
|
|
95
115
|
This is intentionally FAST — no engine init, no embedding, no model loading.
|
|
96
116
|
Just a raw SQLite INSERT (~0.1s).
|
|
@@ -98,9 +118,11 @@ def store_pending(
|
|
|
98
118
|
conn = _get_db(base_dir)
|
|
99
119
|
try:
|
|
100
120
|
cur = conn.execute(
|
|
101
|
-
"INSERT INTO pending_memories
|
|
102
|
-
"
|
|
103
|
-
(
|
|
121
|
+
"INSERT INTO pending_memories "
|
|
122
|
+
"(profile_id, content, tags, metadata, created_at, status) "
|
|
123
|
+
"VALUES (?, ?, ?, ?, ?, 'pending')",
|
|
124
|
+
(profile_id, content, tags, json.dumps(metadata or {}),
|
|
125
|
+
time.strftime("%Y-%m-%dT%H:%M:%S")),
|
|
104
126
|
)
|
|
105
127
|
conn.commit()
|
|
106
128
|
return cur.lastrowid or 0
|
|
@@ -108,20 +130,34 @@ def store_pending(
|
|
|
108
130
|
conn.close()
|
|
109
131
|
|
|
110
132
|
|
|
111
|
-
def get_pending(
|
|
112
|
-
|
|
133
|
+
def get_pending(
|
|
134
|
+
base_dir: Path | None = None,
|
|
135
|
+
limit: int = 50,
|
|
136
|
+
profile_id: str | None = None,
|
|
137
|
+
) -> list[dict]:
|
|
138
|
+
"""Get unprocessed pending memories, optionally scoped to one profile.
|
|
139
|
+
|
|
140
|
+
The drain passes ``profile_id`` = its engine's active profile so it only
|
|
141
|
+
ever claims items enqueued under that profile. Items for other profiles
|
|
142
|
+
wait until their profile is active — never materialized under the wrong one.
|
|
143
|
+
"""
|
|
113
144
|
conn = _get_db(base_dir)
|
|
114
145
|
try:
|
|
115
|
-
|
|
116
|
-
"SELECT id, content, tags, metadata, created_at, retry_count "
|
|
146
|
+
query = (
|
|
147
|
+
"SELECT id, content, tags, metadata, created_at, retry_count, profile_id "
|
|
117
148
|
"FROM pending_memories WHERE status = 'pending' "
|
|
118
|
-
"AND COALESCE(next_retry_at, 0) <= ?
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
149
|
+
"AND COALESCE(next_retry_at, 0) <= ?"
|
|
150
|
+
)
|
|
151
|
+
params: list = [time.time()]
|
|
152
|
+
if profile_id is not None:
|
|
153
|
+
query += " AND profile_id = ?"
|
|
154
|
+
params.append(profile_id)
|
|
155
|
+
query += " ORDER BY id ASC LIMIT ?"
|
|
156
|
+
params.append(limit)
|
|
157
|
+
rows = conn.execute(query, params).fetchall()
|
|
122
158
|
return [
|
|
123
159
|
{"id": r[0], "content": r[1], "tags": r[2], "metadata": r[3],
|
|
124
|
-
"created_at": r[4], "retry_count": r[5]}
|
|
160
|
+
"created_at": r[4], "retry_count": r[5], "profile_id": r[6]}
|
|
125
161
|
for r in rows
|
|
126
162
|
]
|
|
127
163
|
finally:
|
|
@@ -99,6 +99,10 @@ def cmd_proxy(args: Namespace) -> None:
|
|
|
99
99
|
print("Or run: slm wrap claude")
|
|
100
100
|
print()
|
|
101
101
|
print("Proxy ready.")
|
|
102
|
+
print()
|
|
103
|
+
print("Note: 'slm proxy' enables the proxy independently — it does not")
|
|
104
|
+
print("flip the master optimize switch, so 'slm optimize status' may")
|
|
105
|
+
print("show OFF while the proxy is running.")
|
|
102
106
|
else:
|
|
103
107
|
print("Error: proxy failed to start. Check logs.", file=sys.stderr)
|
|
104
108
|
sys.exit(1)
|
|
@@ -37,10 +37,16 @@ def cmd_db_scale(args: Namespace) -> int:
|
|
|
37
37
|
if not args.stage_id:
|
|
38
38
|
raise ScaleEngineError("promote requires --stage-id (see `slm db scale status`)")
|
|
39
39
|
result = manager.promote(args.stage_id)
|
|
40
|
+
# The running daemon reads scale_engine_state once at startup; the
|
|
41
|
+
# promoted Cozo/Lance backends are only wired in on the next start.
|
|
42
|
+
# Without this flag users see a clean promote and keep hitting
|
|
43
|
+
# SQLite-only silently (no error, no speedup).
|
|
44
|
+
result = {**result, "restart_required": True}
|
|
40
45
|
elif action == "rollback":
|
|
41
46
|
if not args.backup_id:
|
|
42
47
|
raise ScaleEngineError("rollback requires --backup-id (see `slm db scale status`)")
|
|
43
48
|
result = manager.rollback(args.backup_id)
|
|
49
|
+
result = {**result, "restart_required": True}
|
|
44
50
|
else:
|
|
45
51
|
raise ScaleEngineError(f"unknown Scale Engine action: {action}")
|
|
46
52
|
except (ScaleEngineError, CanonicalVectorError) as exc:
|
|
@@ -114,17 +114,19 @@ def _download_model(model_name: str, label: str) -> bool:
|
|
|
114
114
|
print(f"\n Downloading {label}: {model_name}")
|
|
115
115
|
print(f" (this may take a few minutes on first run)\n")
|
|
116
116
|
|
|
117
|
+
# H-03: pass the model name as argv, never interpolated into executed
|
|
118
|
+
# source, so a crafted model_name cannot become arbitrary Python.
|
|
117
119
|
script = (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
120
|
+
"import sys; "
|
|
121
|
+
"from sentence_transformers import SentenceTransformer; "
|
|
122
|
+
"m = SentenceTransformer(sys.argv[1], trust_remote_code=True); "
|
|
123
|
+
"d = m.get_sentence_embedding_dimension(); "
|
|
124
|
+
"print(f'OK dim={d}'); "
|
|
123
125
|
)
|
|
124
126
|
|
|
125
127
|
try:
|
|
126
128
|
result = subprocess.run(
|
|
127
|
-
[sys.executable, "-c", script],
|
|
129
|
+
[sys.executable, "-c", script, model_name],
|
|
128
130
|
timeout=600, # 10 min for large model downloads
|
|
129
131
|
capture_output=False, # Show download progress
|
|
130
132
|
text=True,
|
|
@@ -156,15 +158,16 @@ def _download_reranker(model_name: str) -> bool:
|
|
|
156
158
|
print(f"\n Downloading reranker: {model_name}")
|
|
157
159
|
print(f" (cross-encoder for result re-ranking)\n")
|
|
158
160
|
|
|
161
|
+
# H-03: model name via argv, never interpolated into executed source.
|
|
159
162
|
script = (
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
+
"import sys; from sentence_transformers import CrossEncoder; "
|
|
164
|
+
"m = CrossEncoder(sys.argv[1], trust_remote_code=True); "
|
|
165
|
+
"print('OK'); "
|
|
163
166
|
)
|
|
164
167
|
|
|
165
168
|
try:
|
|
166
169
|
result = subprocess.run(
|
|
167
|
-
[sys.executable, "-c", script],
|
|
170
|
+
[sys.executable, "-c", script, model_name],
|
|
168
171
|
timeout=300,
|
|
169
172
|
capture_output=False,
|
|
170
173
|
text=True,
|
|
@@ -195,16 +198,17 @@ def _download_compressor(model_name: str) -> bool:
|
|
|
195
198
|
print(f"\n Downloading compression model: {model_name}")
|
|
196
199
|
print(f" (LLMLingua-2 prose compressor, ~560MB — aggressive mode only)\n")
|
|
197
200
|
|
|
201
|
+
# H-03: model name via argv, never interpolated into executed source.
|
|
198
202
|
script = (
|
|
199
|
-
"from llmlingua import PromptCompressor; "
|
|
200
|
-
|
|
203
|
+
"import sys; from llmlingua import PromptCompressor; "
|
|
204
|
+
"PromptCompressor(model_name=sys.argv[1], use_llmlingua2=True, "
|
|
201
205
|
"device_map='cpu'); "
|
|
202
206
|
"print('OK')"
|
|
203
207
|
)
|
|
204
208
|
|
|
205
209
|
try:
|
|
206
210
|
result = subprocess.run(
|
|
207
|
-
[sys.executable, "-c", script],
|
|
211
|
+
[sys.executable, "-c", script, model_name],
|
|
208
212
|
timeout=900, # 560MB on a slow link can exceed 5 min
|
|
209
213
|
capture_output=False,
|
|
210
214
|
text=True,
|
|
@@ -494,6 +498,11 @@ def run_wizard(auto: bool = False) -> None:
|
|
|
494
498
|
|
|
495
499
|
# -- Step 4: Download models --
|
|
496
500
|
print()
|
|
501
|
+
# H-06 (CVE-2025-14926): a malicious HuggingFace checkpoint can execute code
|
|
502
|
+
# at load time. SLM only downloads its pinned defaults, but warn users who
|
|
503
|
+
# point config at a custom model.
|
|
504
|
+
print(" ⚠ Only install models from sources you trust — a malicious model")
|
|
505
|
+
print(" checkpoint can run code on your machine at load time. See SECURITY.md.")
|
|
497
506
|
print("─── Step 4/10: Download Embedding Model ───")
|
|
498
507
|
|
|
499
508
|
if _embedding_is_remote(config):
|
|
@@ -92,6 +92,12 @@ class AuditChain:
|
|
|
92
92
|
conn = sqlite3.connect(path)
|
|
93
93
|
conn.execute("PRAGMA journal_mode=WAL")
|
|
94
94
|
conn.row_factory = sqlite3.Row
|
|
95
|
+
# C4: audit chain holds a tamper-evident record — keep it owner-only.
|
|
96
|
+
try:
|
|
97
|
+
from superlocalmemory.core.security_primitives import harden_db_perms
|
|
98
|
+
harden_db_perms(path)
|
|
99
|
+
except Exception:
|
|
100
|
+
pass
|
|
95
101
|
return conn
|
|
96
102
|
|
|
97
103
|
def _make_conn(self) -> sqlite3.Connection:
|