superlocalmemory 3.8.3 → 3.8.6
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/CHANGELOG.md +76 -0
- package/README.md +3 -2
- package/package.json +1 -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-graph/SKILL.md +1 -1
- 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-session/SKILL.md +1 -1
- package/plugin-src/skills/slm-status/SKILL.md +1 -1
- package/pyproject.toml +9 -4
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/access/rbac.py +68 -76
- package/src/superlocalmemory/cli/commands.py +158 -404
- package/src/superlocalmemory/cli/ingest_cmd.py +11 -1
- package/src/superlocalmemory/cli/main.py +30 -0
- package/src/superlocalmemory/cli/pending_store.py +39 -14
- package/src/superlocalmemory/core/backend_orchestrator.py +93 -0
- package/src/superlocalmemory/core/component_registry.py +4 -2
- package/src/superlocalmemory/core/config.py +78 -0
- package/src/superlocalmemory/core/consolidation_engine.py +79 -73
- package/src/superlocalmemory/core/embeddings.py +33 -6
- package/src/superlocalmemory/core/engine.py +186 -60
- package/src/superlocalmemory/core/engine_ingestion.py +150 -63
- package/src/superlocalmemory/core/fact_consolidator.py +148 -30
- package/src/superlocalmemory/core/graph_pruner.py +436 -39
- package/src/superlocalmemory/core/ingestion_command.py +273 -32
- package/src/superlocalmemory/core/maintenance_scheduler.py +61 -1
- package/src/superlocalmemory/core/mutations.py +32 -10
- package/src/superlocalmemory/core/recall_pipeline.py +111 -74
- package/src/superlocalmemory/core/registry.py +5 -1
- package/src/superlocalmemory/core/remember_admission.py +152 -0
- package/src/superlocalmemory/core/remember_runtime.py +712 -0
- package/src/superlocalmemory/core/remote_mode.py +3 -1
- package/src/superlocalmemory/core/scale_engine.py +41 -18
- package/src/superlocalmemory/core/store_pipeline.py +18 -4
- package/src/superlocalmemory/encoding/entity_resolver.py +18 -11
- package/src/superlocalmemory/graph/cozo_backend.py +5 -5
- package/src/superlocalmemory/hooks/_outcome_common.py +9 -2
- package/src/superlocalmemory/hooks/adapter_base.py +58 -44
- package/src/superlocalmemory/hooks/ide_connector.py +26 -8
- package/src/superlocalmemory/hooks/portable_kit.py +105 -9
- package/src/superlocalmemory/hooks/prewarm_auth.py +21 -2
- package/src/superlocalmemory/infra/auth_middleware.py +3 -1
- package/src/superlocalmemory/infra/cloud_backup.py +26 -27
- package/src/superlocalmemory/infra/event_bus.py +250 -88
- package/src/superlocalmemory/learning/bandit.py +50 -1
- package/src/superlocalmemory/learning/consolidation_cycle.py +33 -16
- package/src/superlocalmemory/learning/entity_compiler.py +148 -132
- package/src/superlocalmemory/learning/memory_merge.py +97 -82
- package/src/superlocalmemory/learning/reward_archive.py +98 -90
- package/src/superlocalmemory/learning/reward_boost.py +40 -30
- package/src/superlocalmemory/learning/source_quality.py +38 -35
- package/src/superlocalmemory/mcp/_daemon_proxy.py +38 -15
- package/src/superlocalmemory/mcp/http_transport.py +335 -3
- package/src/superlocalmemory/mcp/tools_active.py +4 -41
- package/src/superlocalmemory/mcp/tools_core.py +26 -87
- package/src/superlocalmemory/mcp/tools_evolution.py +5 -10
- package/src/superlocalmemory/optimize/proxy/capture.py +196 -8
- package/src/superlocalmemory/retrieval/engine.py +15 -4
- package/src/superlocalmemory/retrieval/entity_channel.py +25 -1
- package/src/superlocalmemory/retrieval/reranker.py +130 -22
- package/src/superlocalmemory/retrieval/spreading_activation.py +20 -12
- package/src/superlocalmemory/retrieval/vector_store.py +84 -69
- package/src/superlocalmemory/server/loopback.py +85 -0
- package/src/superlocalmemory/server/origin.py +9 -4
- package/src/superlocalmemory/server/profile_runtime.py +14 -0
- package/src/superlocalmemory/server/routes/abstraction.py +2 -4
- package/src/superlocalmemory/server/routes/agents.py +3 -5
- package/src/superlocalmemory/server/routes/backup.py +6 -2
- package/src/superlocalmemory/server/routes/behavioral.py +11 -25
- package/src/superlocalmemory/server/routes/brain.py +6 -9
- package/src/superlocalmemory/server/routes/compliance.py +20 -23
- package/src/superlocalmemory/server/routes/config_api.py +83 -0
- package/src/superlocalmemory/server/routes/entity.py +3 -7
- package/src/superlocalmemory/server/routes/evolution.py +3 -5
- package/src/superlocalmemory/server/routes/helpers.py +57 -25
- package/src/superlocalmemory/server/routes/insights.py +2 -4
- package/src/superlocalmemory/server/routes/learning.py +2 -5
- package/src/superlocalmemory/server/routes/lifecycle.py +2 -4
- package/src/superlocalmemory/server/routes/memories.py +119 -98
- package/src/superlocalmemory/server/routes/mesh.py +7 -2
- package/src/superlocalmemory/server/routes/profiles.py +20 -21
- package/src/superlocalmemory/server/routes/rbac.py +0 -1
- package/src/superlocalmemory/server/routes/tiers.py +28 -35
- package/src/superlocalmemory/server/routes/timeline.py +2 -4
- package/src/superlocalmemory/server/routes/v3_api.py +85 -93
- package/src/superlocalmemory/server/unified_daemon.py +400 -140
- package/src/superlocalmemory/server/write_identity.py +22 -4
- package/src/superlocalmemory/storage/admission_codec.py +119 -0
- package/src/superlocalmemory/storage/admission_journal.py +728 -0
- package/src/superlocalmemory/storage/database.py +168 -19
- package/src/superlocalmemory/storage/deferred_writes.py +209 -0
- package/src/superlocalmemory/storage/embedding_migrator.py +19 -0
- package/src/superlocalmemory/storage/memory_write.py +115 -0
- package/src/superlocalmemory/storage/migration_runner.py +44 -0
- package/src/superlocalmemory/storage/migrations/M028_fact_entity_associations.py +113 -78
- package/src/superlocalmemory/storage/migrations/M031_dead_letter_operations.py +80 -0
- package/src/superlocalmemory/storage/migrations/M032_write_coordinator_admission.py +188 -0
- package/src/superlocalmemory/storage/read_connection.py +115 -0
- package/src/superlocalmemory/storage/write_coordinator.py +756 -0
- package/src/superlocalmemory/storage/write_lock.py +88 -0
- package/src/superlocalmemory/ui/index.html +1 -1
- package/src/superlocalmemory/ui/js/auto-settings.js +14 -1
- package/src/superlocalmemory/ui/js/od-settings.js +9 -3
|
@@ -21,6 +21,25 @@ from pathlib import Path
|
|
|
21
21
|
logger = logging.getLogger(__name__)
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
def _daemon_unavailable(command: str, use_json: bool) -> None:
|
|
25
|
+
"""Exit a mutation client without opening a process-local writer."""
|
|
26
|
+
error = {
|
|
27
|
+
"code": "DAEMON_UNAVAILABLE",
|
|
28
|
+
"message": "Owned daemon is unavailable; retry later.",
|
|
29
|
+
"retryable": True,
|
|
30
|
+
}
|
|
31
|
+
if use_json:
|
|
32
|
+
from superlocalmemory.cli.json_output import json_print
|
|
33
|
+
|
|
34
|
+
json_print(command, error=error)
|
|
35
|
+
else:
|
|
36
|
+
print(
|
|
37
|
+
"DAEMON_UNAVAILABLE: owned daemon is unavailable; retry later.",
|
|
38
|
+
file=sys.stderr,
|
|
39
|
+
)
|
|
40
|
+
raise SystemExit(1)
|
|
41
|
+
|
|
42
|
+
|
|
24
43
|
def _cmd_db_dispatch(args: Namespace) -> None:
|
|
25
44
|
"""Route ``slm db ...`` subcommands. LLD-06 §7.2."""
|
|
26
45
|
sub = getattr(args, "db_command", None)
|
|
@@ -654,14 +673,14 @@ def cmd_restart(args: Namespace) -> None:
|
|
|
654
673
|
|
|
655
674
|
# Step 5: Database integrity check
|
|
656
675
|
try:
|
|
657
|
-
import
|
|
676
|
+
from superlocalmemory.storage.memory_write import memory_read
|
|
677
|
+
|
|
658
678
|
db_path = slm_dir / "memory.db"
|
|
659
679
|
if db_path.exists():
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
conn.close()
|
|
680
|
+
with memory_read(db_path) as conn:
|
|
681
|
+
integrity = conn.execute("PRAGMA integrity_check").fetchone()[0]
|
|
682
|
+
fact_count = conn.execute("SELECT COUNT(*) FROM atomic_facts").fetchone()[0]
|
|
683
|
+
entity_count = conn.execute("SELECT COUNT(*) FROM canonical_entities").fetchone()[0]
|
|
665
684
|
_log(5, "Database integrity", "ok" if integrity == "ok" else "fail",
|
|
666
685
|
f"integrity={integrity}, {fact_count} facts, {entity_count} entities")
|
|
667
686
|
else:
|
|
@@ -1135,6 +1154,9 @@ def cmd_connect(args: Namespace) -> None:
|
|
|
1135
1154
|
if ide_arg in IDE_MATRIX:
|
|
1136
1155
|
here = getattr(args, "here", False)
|
|
1137
1156
|
profile = getattr(args, "profile", None)
|
|
1157
|
+
transport = getattr(args, "transport", "stdio")
|
|
1158
|
+
daemon_port = getattr(args, "daemon_port", 8765)
|
|
1159
|
+
verify = getattr(args, "verify", False)
|
|
1138
1160
|
project = None
|
|
1139
1161
|
if here:
|
|
1140
1162
|
import pathlib
|
|
@@ -1148,8 +1170,24 @@ def cmd_connect(args: Namespace) -> None:
|
|
|
1148
1170
|
profile=profile,
|
|
1149
1171
|
agents_md_source=_agents_md_source_factory(),
|
|
1150
1172
|
dry_run=getattr(args, "dry_run", False),
|
|
1173
|
+
transport=transport,
|
|
1174
|
+
daemon_port=daemon_port,
|
|
1151
1175
|
)
|
|
1152
1176
|
|
|
1177
|
+
# --verify: probe daemon health after writing
|
|
1178
|
+
if verify and transport in ("http", "http-mcp-remote") and not result.get("error"):
|
|
1179
|
+
from superlocalmemory.hooks.portable_kit import _check_daemon_health
|
|
1180
|
+
reachable = _check_daemon_health(daemon_port)
|
|
1181
|
+
if reachable:
|
|
1182
|
+
print(f"[verify] Daemon reachable at http://127.0.0.1:{daemon_port}/api/v3/health")
|
|
1183
|
+
else:
|
|
1184
|
+
print(
|
|
1185
|
+
f"[verify] Warning: daemon NOT reachable at "
|
|
1186
|
+
f"http://127.0.0.1:{daemon_port}/api/v3/health. "
|
|
1187
|
+
f"Run `slm serve start` to start it.",
|
|
1188
|
+
file=sys.stderr,
|
|
1189
|
+
)
|
|
1190
|
+
|
|
1153
1191
|
if not result.get("error") and not getattr(args, "dry_run", False):
|
|
1154
1192
|
from superlocalmemory.infra.local_diagnostics import record_operation
|
|
1155
1193
|
|
|
@@ -1320,8 +1358,7 @@ def cmd_list(args: Namespace) -> None:
|
|
|
1320
1358
|
|
|
1321
1359
|
|
|
1322
1360
|
def cmd_remember(args: Namespace) -> None:
|
|
1323
|
-
"""Store a memory
|
|
1324
|
-
from superlocalmemory.core.config import SLMConfig
|
|
1361
|
+
"""Store a memory through the owned daemon."""
|
|
1325
1362
|
|
|
1326
1363
|
use_json = getattr(args, 'json', False)
|
|
1327
1364
|
sync_mode = getattr(args, 'sync_mode', False)
|
|
@@ -1339,128 +1376,43 @@ def cmd_remember(args: Namespace) -> None:
|
|
|
1339
1376
|
if isinstance(_sw_raw, str) and _sw_raw.strip() else _sw_raw
|
|
1340
1377
|
)
|
|
1341
1378
|
|
|
1342
|
-
# Both paths use the one owned daemon. A second local engine for --sync
|
|
1343
|
-
# duplicates heavyweight workers and can block for minutes on cold models.
|
|
1344
|
-
daemon_owned = False
|
|
1345
1379
|
try:
|
|
1346
1380
|
from superlocalmemory.cli.daemon import (
|
|
1347
1381
|
daemon_request, ensure_daemon, is_daemon_running,
|
|
1348
1382
|
)
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
if use_json:
|
|
1363
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1364
|
-
json_print("remember", data=result)
|
|
1365
|
-
else:
|
|
1366
|
-
state = result.get("materialization_state", "queryable")
|
|
1367
|
-
operation_id = result.get("operation_id", "unknown")
|
|
1368
|
-
print(
|
|
1369
|
-
f"{state.capitalize()} \u2713 {result['count']} facts "
|
|
1370
|
-
f"(operation={operation_id})."
|
|
1371
|
-
)
|
|
1372
|
-
return
|
|
1373
|
-
if sync_mode:
|
|
1374
|
-
if use_json:
|
|
1375
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1376
|
-
json_print("remember", error={
|
|
1377
|
-
"code": "SYNC_TIMEOUT",
|
|
1378
|
-
"message": (
|
|
1379
|
-
"Canonical ingestion did not complete within 30s; "
|
|
1380
|
-
"the durable operation remains available for retry."
|
|
1381
|
-
),
|
|
1382
|
-
})
|
|
1383
|
-
else:
|
|
1384
|
-
print(
|
|
1385
|
-
"Synchronous ingestion did not complete within 30s; "
|
|
1386
|
-
"the durable operation remains queued.",
|
|
1387
|
-
file=sys.stderr,
|
|
1388
|
-
)
|
|
1389
|
-
sys.exit(1)
|
|
1390
|
-
except SystemExit:
|
|
1391
|
-
raise
|
|
1392
|
-
except Exception:
|
|
1393
|
-
if sync_mode and daemon_owned:
|
|
1383
|
+
if not (is_daemon_running() or ensure_daemon()):
|
|
1384
|
+
_daemon_unavailable("remember", use_json)
|
|
1385
|
+
path = "/remember?wait=true" if sync_mode else "/remember"
|
|
1386
|
+
result = daemon_request(
|
|
1387
|
+
"POST", path, {
|
|
1388
|
+
"content": args.content,
|
|
1389
|
+
"tags": args.tags or "",
|
|
1390
|
+
"scope": scope,
|
|
1391
|
+
"shared_with": shared_with,
|
|
1392
|
+
},
|
|
1393
|
+
timeout_seconds=30,
|
|
1394
|
+
)
|
|
1395
|
+
if result and "fact_ids" in result:
|
|
1394
1396
|
if use_json:
|
|
1395
1397
|
from superlocalmemory.cli.json_output import json_print
|
|
1396
|
-
json_print("remember",
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
try:
|
|
1407
|
-
config = SLMConfig.load()
|
|
1408
|
-
engine = MemoryEngine(config)
|
|
1409
|
-
engine.initialize()
|
|
1410
|
-
|
|
1411
|
-
# v3.6.15: resolve an unset scope to the configured default_scope.
|
|
1412
|
-
_scope = scope or getattr(getattr(config, "scope", None), "default_scope", "personal")
|
|
1413
|
-
from superlocalmemory.core.engine_ingestion import (
|
|
1414
|
-
canonical_store,
|
|
1415
|
-
local_trusted_actor_id,
|
|
1416
|
-
)
|
|
1417
|
-
|
|
1418
|
-
metadata = {"tags": args.tags} if args.tags else {}
|
|
1419
|
-
operation = canonical_store(
|
|
1420
|
-
engine,
|
|
1421
|
-
args.content,
|
|
1422
|
-
source_type="cli-sync" if sync_mode else "cli-offline-canonical",
|
|
1423
|
-
trusted_actor_id=local_trusted_actor_id("cli"),
|
|
1424
|
-
metadata=metadata,
|
|
1425
|
-
scope=_scope,
|
|
1426
|
-
shared_with=shared_with,
|
|
1427
|
-
return_receipt=True,
|
|
1428
|
-
)
|
|
1429
|
-
except Exception as exc:
|
|
1430
|
-
if use_json:
|
|
1431
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1432
|
-
json_print("remember", error={"code": "STORE_ERROR", "message": str(exc)})
|
|
1433
|
-
sys.exit(1)
|
|
1398
|
+
json_print("remember", data=result)
|
|
1399
|
+
else:
|
|
1400
|
+
state = result.get("materialization_state", "queryable")
|
|
1401
|
+
operation_id = result.get("operation_id", "unknown")
|
|
1402
|
+
print(
|
|
1403
|
+
f"{state.capitalize()} \u2713 {result['count']} facts "
|
|
1404
|
+
f"(operation={operation_id})."
|
|
1405
|
+
)
|
|
1406
|
+
return
|
|
1407
|
+
except SystemExit:
|
|
1434
1408
|
raise
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
"fact_ids": fact_ids,
|
|
1439
|
-
"count": len(fact_ids),
|
|
1440
|
-
"materialization_state": getattr(
|
|
1441
|
-
getattr(operation, "state", None), "value", "complete"
|
|
1442
|
-
),
|
|
1443
|
-
}
|
|
1444
|
-
if getattr(operation, "operation_id", None):
|
|
1445
|
-
operation_data["operation_id"] = operation.operation_id
|
|
1446
|
-
|
|
1447
|
-
if use_json:
|
|
1448
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1449
|
-
json_print("remember", data=operation_data,
|
|
1450
|
-
next_actions=[
|
|
1451
|
-
{"command": "slm recall '<query>' --json", "description": "Search your memories"},
|
|
1452
|
-
{"command": "slm list --json -n 5", "description": "See recent memories"},
|
|
1453
|
-
])
|
|
1454
|
-
return
|
|
1455
|
-
|
|
1456
|
-
print(
|
|
1457
|
-
f"Complete \u2713 {len(fact_ids)} facts "
|
|
1458
|
-
f"(operation={operation_data.get('operation_id', 'none')})."
|
|
1459
|
-
)
|
|
1409
|
+
except Exception as exc:
|
|
1410
|
+
logger.warning("owned daemon remember request failed: %s", exc)
|
|
1411
|
+
_daemon_unavailable("remember", use_json)
|
|
1460
1412
|
|
|
1461
1413
|
|
|
1462
1414
|
def cmd_recall(args: Namespace) -> None:
|
|
1463
|
-
"""Search memories
|
|
1415
|
+
"""Search memories through the owned daemon without a local engine."""
|
|
1464
1416
|
use_json = getattr(args, 'json', False)
|
|
1465
1417
|
# v3.6.15: None = "not specified" → daemon/engine resolves the configured
|
|
1466
1418
|
# default (shared-off). Only an explicit --include-global / --no-global
|
|
@@ -1469,7 +1421,6 @@ def cmd_recall(args: Namespace) -> None:
|
|
|
1469
1421
|
include_shared = getattr(args, 'include_shared', None)
|
|
1470
1422
|
|
|
1471
1423
|
# V3.3.21: Route through daemon for instant response (no cold start).
|
|
1472
|
-
# Falls back to direct engine if daemon not running.
|
|
1473
1424
|
# S9-DASH-02: pass a stable session_id derived from the shell's
|
|
1474
1425
|
# parent PID so a sequence of CLI recalls in one terminal can be
|
|
1475
1426
|
# grouped. The Stop hook on session end won't fire for CLI, so
|
|
@@ -1515,76 +1466,10 @@ def cmd_recall(args: Namespace) -> None:
|
|
|
1515
1466
|
return
|
|
1516
1467
|
except Exception as _exc: # noqa: BLE001
|
|
1517
1468
|
logger.warning(
|
|
1518
|
-
"
|
|
1469
|
+
"owned daemon recall request failed: %s", _exc
|
|
1519
1470
|
)
|
|
1520
1471
|
|
|
1521
|
-
|
|
1522
|
-
from superlocalmemory.core.engine import MemoryEngine
|
|
1523
|
-
|
|
1524
|
-
try:
|
|
1525
|
-
config = SLMConfig.load()
|
|
1526
|
-
engine = MemoryEngine(config)
|
|
1527
|
-
engine.initialize()
|
|
1528
|
-
|
|
1529
|
-
response = engine.recall(
|
|
1530
|
-
args.query, limit=args.limit,
|
|
1531
|
-
# v3.8.2: --fast → True; unset → None so engine resolves the
|
|
1532
|
-
# client-driven-agentic default (parity with the daemon path, which
|
|
1533
|
-
# omits the fast query param when --fast is absent).
|
|
1534
|
-
fast=(True if getattr(args, "fast", False) else None),
|
|
1535
|
-
include_global=include_global,
|
|
1536
|
-
include_shared=include_shared,
|
|
1537
|
-
window=getattr(args, "window", "") or None,
|
|
1538
|
-
)
|
|
1539
|
-
except Exception as exc:
|
|
1540
|
-
if use_json:
|
|
1541
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1542
|
-
json_print("recall", error={"code": "RECALL_ERROR", "message": str(exc)})
|
|
1543
|
-
sys.exit(1)
|
|
1544
|
-
raise
|
|
1545
|
-
|
|
1546
|
-
# v3.6.6: route the direct-fallback path through the SAME shared
|
|
1547
|
-
# serializer the daemon uses, so CLI-without-daemon output is identical
|
|
1548
|
-
# to CLI/MCP-with-daemon (budget + source discipline + no_confident_match).
|
|
1549
|
-
from superlocalmemory.server.recall_serializer import (
|
|
1550
|
-
recall_response_metadata,
|
|
1551
|
-
serialize_recall_response,
|
|
1552
|
-
)
|
|
1553
|
-
_rc = getattr(config, "retrieval", None)
|
|
1554
|
-
_ser, _no_match = serialize_recall_response(
|
|
1555
|
-
response,
|
|
1556
|
-
limit=args.limit,
|
|
1557
|
-
per_fact_max=getattr(_rc, "recall_per_fact_max_chars", 2400),
|
|
1558
|
-
total_max=getattr(_rc, "recall_total_max_chars", 12000),
|
|
1559
|
-
full=getattr(args, "full", False),
|
|
1560
|
-
)
|
|
1561
|
-
|
|
1562
|
-
if use_json:
|
|
1563
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1564
|
-
items = []
|
|
1565
|
-
for d in _ser:
|
|
1566
|
-
items.append(dict(d))
|
|
1567
|
-
json_print("recall", data={
|
|
1568
|
-
"results": items, "count": len(items),
|
|
1569
|
-
"query_type": getattr(response, "query_type", "unknown"),
|
|
1570
|
-
"no_confident_match": _no_match,
|
|
1571
|
-
**recall_response_metadata(response),
|
|
1572
|
-
}, next_actions=[
|
|
1573
|
-
{"command": "slm list --json", "description": "List recent memories"},
|
|
1574
|
-
])
|
|
1575
|
-
return
|
|
1576
|
-
|
|
1577
|
-
# Record learning signals (CLI path — works without MCP)
|
|
1578
|
-
try:
|
|
1579
|
-
_cli_record_signals(config, args.query, response.results)
|
|
1580
|
-
except Exception:
|
|
1581
|
-
pass
|
|
1582
|
-
|
|
1583
|
-
if not _ser:
|
|
1584
|
-
print("No confident match." if _no_match else "No memories found.")
|
|
1585
|
-
return
|
|
1586
|
-
for i, d in enumerate(_ser, 1):
|
|
1587
|
-
print(f" {i}. [relevance {d['relevance_score']:.2f}] {d['content']}")
|
|
1472
|
+
_daemon_unavailable("recall", use_json)
|
|
1588
1473
|
|
|
1589
1474
|
|
|
1590
1475
|
def _cli_record_signals(config, query, results):
|
|
@@ -1614,9 +1499,14 @@ def _cli_record_signals(config, query, results):
|
|
|
1614
1499
|
|
|
1615
1500
|
|
|
1616
1501
|
def cmd_forget(args: Namespace) -> None:
|
|
1617
|
-
"""Delete memories matching a query."""
|
|
1618
|
-
|
|
1619
|
-
|
|
1502
|
+
"""Delete daemon-queried memories matching a query."""
|
|
1503
|
+
import urllib.parse
|
|
1504
|
+
|
|
1505
|
+
from superlocalmemory.cli.daemon import (
|
|
1506
|
+
daemon_request,
|
|
1507
|
+
ensure_daemon,
|
|
1508
|
+
is_daemon_running,
|
|
1509
|
+
)
|
|
1620
1510
|
|
|
1621
1511
|
use_json = getattr(args, 'json', False)
|
|
1622
1512
|
dry_run = getattr(args, 'dry_run', False)
|
|
@@ -1640,38 +1530,45 @@ def cmd_forget(args: Namespace) -> None:
|
|
|
1640
1530
|
query_lower = "" if raw_query is None else raw_query.lower()
|
|
1641
1531
|
query_label = raw_query if raw_query is not None else "*all*"
|
|
1642
1532
|
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
if
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1533
|
+
if not (is_daemon_running() or ensure_daemon()):
|
|
1534
|
+
_daemon_unavailable("forget", use_json)
|
|
1535
|
+
|
|
1536
|
+
memories: list[dict[str, object]] = []
|
|
1537
|
+
offset = 0
|
|
1538
|
+
while True:
|
|
1539
|
+
page = daemon_request("GET", f"/api/memories?limit=200&offset={offset}")
|
|
1540
|
+
rows = page.get("memories") if isinstance(page, dict) else None
|
|
1541
|
+
if not isinstance(rows, list):
|
|
1542
|
+
_daemon_unavailable("forget", use_json)
|
|
1543
|
+
memories.extend(row for row in rows if isinstance(row, dict))
|
|
1544
|
+
if not page.get("has_more"):
|
|
1545
|
+
break
|
|
1546
|
+
offset += len(rows)
|
|
1547
|
+
if not rows:
|
|
1548
|
+
_daemon_unavailable("forget", use_json)
|
|
1655
1549
|
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1550
|
+
matches = [
|
|
1551
|
+
fact for fact in memories
|
|
1552
|
+
if query_lower in str(fact.get("content", "")).lower()
|
|
1553
|
+
]
|
|
1659
1554
|
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
source_agent_id="cli",
|
|
1555
|
+
def delete_from_daemon(fact_id: str) -> None:
|
|
1556
|
+
result = daemon_request(
|
|
1557
|
+
"DELETE",
|
|
1558
|
+
"/api/memories/" + urllib.parse.quote(fact_id, safe=""),
|
|
1665
1559
|
)
|
|
1666
|
-
if not result.get("
|
|
1667
|
-
|
|
1560
|
+
if not isinstance(result, dict) or not result.get("success"):
|
|
1561
|
+
_daemon_unavailable("forget", use_json)
|
|
1668
1562
|
|
|
1669
1563
|
if use_json:
|
|
1670
1564
|
from superlocalmemory.cli.json_output import json_print
|
|
1671
1565
|
if not matches:
|
|
1672
1566
|
json_print("forget", data={"matched_count": 0, "deleted_count": 0, "matches": []})
|
|
1673
1567
|
return
|
|
1674
|
-
match_items = [
|
|
1568
|
+
match_items = [
|
|
1569
|
+
{"fact_id": f["id"], "content": str(f.get("content", ""))[:120]}
|
|
1570
|
+
for f in matches[:20]
|
|
1571
|
+
]
|
|
1675
1572
|
if dry_run:
|
|
1676
1573
|
json_print("forget", data={
|
|
1677
1574
|
"matched_count": len(matches), "deleted_count": 0,
|
|
@@ -1680,10 +1577,10 @@ def cmd_forget(args: Namespace) -> None:
|
|
|
1680
1577
|
return
|
|
1681
1578
|
if getattr(args, 'yes', False):
|
|
1682
1579
|
for f in matches:
|
|
1683
|
-
|
|
1580
|
+
delete_from_daemon(str(f["id"]))
|
|
1684
1581
|
json_print("forget", data={
|
|
1685
1582
|
"matched_count": len(matches), "deleted_count": len(matches),
|
|
1686
|
-
"deleted": [f
|
|
1583
|
+
"deleted": [f["id"] for f in matches],
|
|
1687
1584
|
}, next_actions=[
|
|
1688
1585
|
{"command": "slm list --json", "description": "Verify remaining memories"},
|
|
1689
1586
|
])
|
|
@@ -1693,7 +1590,10 @@ def cmd_forget(args: Namespace) -> None:
|
|
|
1693
1590
|
"matches": match_items,
|
|
1694
1591
|
"hint": "Add --yes to confirm deletion",
|
|
1695
1592
|
}, next_actions=[
|
|
1696
|
-
{
|
|
1593
|
+
{
|
|
1594
|
+
"command": f"slm forget '{query_label}' --json --yes",
|
|
1595
|
+
"description": "Confirm deletion",
|
|
1596
|
+
},
|
|
1697
1597
|
])
|
|
1698
1598
|
return
|
|
1699
1599
|
|
|
@@ -1702,19 +1602,19 @@ def cmd_forget(args: Namespace) -> None:
|
|
|
1702
1602
|
return
|
|
1703
1603
|
print(f"Found {len(matches)} matching memories:")
|
|
1704
1604
|
for f in matches[:10]:
|
|
1705
|
-
print(f" - {f
|
|
1605
|
+
print(f" - {str(f['id'])[:8]}... {str(f.get('content', ''))[:80]}")
|
|
1706
1606
|
if dry_run:
|
|
1707
1607
|
print(f"(dry run — {len(matches)} would be deleted)")
|
|
1708
1608
|
return
|
|
1709
1609
|
if getattr(args, 'yes', False):
|
|
1710
1610
|
for f in matches:
|
|
1711
|
-
|
|
1611
|
+
delete_from_daemon(str(f["id"]))
|
|
1712
1612
|
print(f"Deleted {len(matches)} memories.")
|
|
1713
1613
|
return
|
|
1714
1614
|
confirm = input(f"Delete {len(matches)} memories? [y/N] ").strip().lower()
|
|
1715
1615
|
if confirm in ("y", "yes"):
|
|
1716
1616
|
for f in matches:
|
|
1717
|
-
|
|
1617
|
+
delete_from_daemon(str(f["id"]))
|
|
1718
1618
|
print(f"Deleted {len(matches)} memories.")
|
|
1719
1619
|
else:
|
|
1720
1620
|
print("Cancelled.")
|
|
@@ -1724,13 +1624,15 @@ def cmd_delete(args: Namespace) -> None:
|
|
|
1724
1624
|
"""Delete a specific memory by exact fact ID."""
|
|
1725
1625
|
import urllib.parse
|
|
1726
1626
|
|
|
1727
|
-
from superlocalmemory.cli.daemon import
|
|
1728
|
-
|
|
1729
|
-
|
|
1627
|
+
from superlocalmemory.cli.daemon import (
|
|
1628
|
+
daemon_request,
|
|
1629
|
+
ensure_daemon,
|
|
1630
|
+
is_daemon_running,
|
|
1631
|
+
)
|
|
1730
1632
|
|
|
1731
1633
|
use_json = getattr(args, 'json', False)
|
|
1732
1634
|
fact_id = args.fact_id.strip()
|
|
1733
|
-
if is_daemon_running():
|
|
1635
|
+
if is_daemon_running() or ensure_daemon():
|
|
1734
1636
|
path = "/api/memories/" + urllib.parse.quote(fact_id, safe="")
|
|
1735
1637
|
confirmed = getattr(args, "yes", False)
|
|
1736
1638
|
content = ""
|
|
@@ -1740,14 +1642,7 @@ def cmd_delete(args: Namespace) -> None:
|
|
|
1740
1642
|
"/api/facts/" + urllib.parse.quote(fact_id, safe=""),
|
|
1741
1643
|
)
|
|
1742
1644
|
if not isinstance(detail, dict):
|
|
1743
|
-
|
|
1744
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1745
|
-
json_print("delete", error={
|
|
1746
|
-
"code": "DAEMON_MUTATION_FAILED",
|
|
1747
|
-
"message": "Resident daemon could not resolve the memory.",
|
|
1748
|
-
})
|
|
1749
|
-
sys.exit(1)
|
|
1750
|
-
raise RuntimeError("Resident daemon could not resolve the memory.")
|
|
1645
|
+
_daemon_unavailable("delete", use_json)
|
|
1751
1646
|
content = str(detail.get("content") or "")
|
|
1752
1647
|
if use_json:
|
|
1753
1648
|
from superlocalmemory.cli.json_output import json_print
|
|
@@ -1773,14 +1668,7 @@ def cmd_delete(args: Namespace) -> None:
|
|
|
1773
1668
|
|
|
1774
1669
|
result = daemon_request("DELETE", path)
|
|
1775
1670
|
if not isinstance(result, dict) or not result.get("success"):
|
|
1776
|
-
|
|
1777
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1778
|
-
json_print("delete", error={
|
|
1779
|
-
"code": "DAEMON_MUTATION_FAILED",
|
|
1780
|
-
"message": "Resident daemon rejected the delete operation.",
|
|
1781
|
-
})
|
|
1782
|
-
sys.exit(1)
|
|
1783
|
-
raise RuntimeError("Resident daemon rejected the delete operation.")
|
|
1671
|
+
_daemon_unavailable("delete", use_json)
|
|
1784
1672
|
if use_json:
|
|
1785
1673
|
from superlocalmemory.cli.json_output import json_print
|
|
1786
1674
|
json_print(
|
|
@@ -1796,86 +1684,18 @@ def cmd_delete(args: Namespace) -> None:
|
|
|
1796
1684
|
else:
|
|
1797
1685
|
print(f"Deleted: {fact_id}")
|
|
1798
1686
|
return
|
|
1799
|
-
|
|
1800
|
-
try:
|
|
1801
|
-
config = SLMConfig.load()
|
|
1802
|
-
engine = MemoryEngine(config)
|
|
1803
|
-
engine.initialize()
|
|
1804
|
-
|
|
1805
|
-
rows = engine._db.execute(
|
|
1806
|
-
"SELECT content FROM atomic_facts WHERE fact_id = ? AND profile_id = ?",
|
|
1807
|
-
(fact_id, engine.profile_id),
|
|
1808
|
-
)
|
|
1809
|
-
except Exception as exc:
|
|
1810
|
-
if use_json:
|
|
1811
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1812
|
-
json_print("delete", error={"code": "ENGINE_ERROR", "message": str(exc)})
|
|
1813
|
-
sys.exit(1)
|
|
1814
|
-
raise
|
|
1815
|
-
|
|
1816
|
-
if use_json:
|
|
1817
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1818
|
-
if not rows:
|
|
1819
|
-
json_print("delete", error={
|
|
1820
|
-
"code": "NOT_FOUND", "message": f"Memory not found: {fact_id}",
|
|
1821
|
-
})
|
|
1822
|
-
sys.exit(1)
|
|
1823
|
-
content = dict(rows[0]).get("content", "")
|
|
1824
|
-
if getattr(args, "yes", False):
|
|
1825
|
-
from superlocalmemory.core.engine_ingestion import local_trusted_actor_id
|
|
1826
|
-
from superlocalmemory.core.mutations import delete_fact_authorized
|
|
1827
|
-
|
|
1828
|
-
delete_fact_authorized(
|
|
1829
|
-
engine,
|
|
1830
|
-
fact_id,
|
|
1831
|
-
trusted_actor_id=local_trusted_actor_id("cli"),
|
|
1832
|
-
source_agent_id="cli",
|
|
1833
|
-
)
|
|
1834
|
-
json_print("delete", data={"deleted": fact_id, "content": content[:120]},
|
|
1835
|
-
next_actions=[
|
|
1836
|
-
{"command": "slm list --json", "description": "Verify remaining memories"},
|
|
1837
|
-
])
|
|
1838
|
-
else:
|
|
1839
|
-
json_print("delete", data={
|
|
1840
|
-
"fact_id": fact_id, "content": content[:120], "deleted": False,
|
|
1841
|
-
"hint": "Add --yes to confirm deletion",
|
|
1842
|
-
}, next_actions=[
|
|
1843
|
-
{"command": f"slm delete {fact_id} --json --yes", "description": "Confirm deletion"},
|
|
1844
|
-
])
|
|
1845
|
-
return
|
|
1846
|
-
|
|
1847
|
-
if not rows:
|
|
1848
|
-
print(f"Memory not found: {fact_id}")
|
|
1849
|
-
return
|
|
1850
|
-
|
|
1851
|
-
content_preview = dict(rows[0]).get("content", "")[:120]
|
|
1852
|
-
print(f"Memory: {content_preview}")
|
|
1853
|
-
|
|
1854
|
-
if not getattr(args, "yes", False):
|
|
1855
|
-
confirm = input("Delete this memory? [y/N] ").strip().lower()
|
|
1856
|
-
if confirm not in ("y", "yes"):
|
|
1857
|
-
print("Cancelled.")
|
|
1858
|
-
return
|
|
1859
|
-
|
|
1860
|
-
from superlocalmemory.core.engine_ingestion import local_trusted_actor_id
|
|
1861
|
-
from superlocalmemory.core.mutations import delete_fact_authorized
|
|
1862
|
-
|
|
1863
|
-
delete_fact_authorized(
|
|
1864
|
-
engine,
|
|
1865
|
-
fact_id,
|
|
1866
|
-
trusted_actor_id=local_trusted_actor_id("cli"),
|
|
1867
|
-
source_agent_id="cli",
|
|
1868
|
-
)
|
|
1869
|
-
print(f"Deleted: {fact_id}")
|
|
1687
|
+
_daemon_unavailable("delete", use_json)
|
|
1870
1688
|
|
|
1871
1689
|
|
|
1872
1690
|
def cmd_update(args: Namespace) -> None:
|
|
1873
1691
|
"""Update the content of a specific memory by exact fact ID."""
|
|
1874
1692
|
import urllib.parse
|
|
1875
1693
|
|
|
1876
|
-
from superlocalmemory.cli.daemon import
|
|
1877
|
-
|
|
1878
|
-
|
|
1694
|
+
from superlocalmemory.cli.daemon import (
|
|
1695
|
+
daemon_request,
|
|
1696
|
+
ensure_daemon,
|
|
1697
|
+
is_daemon_running,
|
|
1698
|
+
)
|
|
1879
1699
|
|
|
1880
1700
|
use_json = getattr(args, 'json', False)
|
|
1881
1701
|
fact_id = args.fact_id.strip()
|
|
@@ -1889,18 +1709,11 @@ def cmd_update(args: Namespace) -> None:
|
|
|
1889
1709
|
print("Error: content cannot be empty")
|
|
1890
1710
|
return
|
|
1891
1711
|
|
|
1892
|
-
if is_daemon_running():
|
|
1712
|
+
if is_daemon_running() or ensure_daemon():
|
|
1893
1713
|
path = "/api/memories/" + urllib.parse.quote(fact_id, safe="")
|
|
1894
1714
|
result = daemon_request("PATCH", path, {"content": new_content})
|
|
1895
1715
|
if not isinstance(result, dict) or not result.get("success"):
|
|
1896
|
-
|
|
1897
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1898
|
-
json_print("update", error={
|
|
1899
|
-
"code": "DAEMON_MUTATION_FAILED",
|
|
1900
|
-
"message": "Resident daemon rejected the update operation.",
|
|
1901
|
-
})
|
|
1902
|
-
sys.exit(1)
|
|
1903
|
-
raise RuntimeError("Resident daemon rejected the update operation.")
|
|
1716
|
+
_daemon_unavailable("update", use_json)
|
|
1904
1717
|
if use_json:
|
|
1905
1718
|
from superlocalmemory.cli.json_output import json_print
|
|
1906
1719
|
json_print("update", data={
|
|
@@ -1916,59 +1729,7 @@ def cmd_update(args: Namespace) -> None:
|
|
|
1916
1729
|
print(f"New: {new_content[:100]}")
|
|
1917
1730
|
print(f"Updated: {fact_id}")
|
|
1918
1731
|
return
|
|
1919
|
-
|
|
1920
|
-
try:
|
|
1921
|
-
config = SLMConfig.load()
|
|
1922
|
-
engine = MemoryEngine(config)
|
|
1923
|
-
engine.initialize()
|
|
1924
|
-
|
|
1925
|
-
rows = engine._db.execute(
|
|
1926
|
-
"SELECT content FROM atomic_facts WHERE fact_id = ? AND profile_id = ?",
|
|
1927
|
-
(fact_id, engine.profile_id),
|
|
1928
|
-
)
|
|
1929
|
-
except Exception as exc:
|
|
1930
|
-
if use_json:
|
|
1931
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1932
|
-
json_print("update", error={"code": "ENGINE_ERROR", "message": str(exc)})
|
|
1933
|
-
sys.exit(1)
|
|
1934
|
-
raise
|
|
1935
|
-
|
|
1936
|
-
if not rows:
|
|
1937
|
-
if use_json:
|
|
1938
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1939
|
-
json_print("update", error={
|
|
1940
|
-
"code": "NOT_FOUND", "message": f"Memory not found: {fact_id}",
|
|
1941
|
-
})
|
|
1942
|
-
sys.exit(1)
|
|
1943
|
-
print(f"Memory not found: {fact_id}")
|
|
1944
|
-
return
|
|
1945
|
-
|
|
1946
|
-
old_content = dict(rows[0]).get("content", "")
|
|
1947
|
-
from superlocalmemory.core.engine_ingestion import local_trusted_actor_id
|
|
1948
|
-
from superlocalmemory.core.mutations import update_fact_authorized
|
|
1949
|
-
|
|
1950
|
-
update_fact_authorized(
|
|
1951
|
-
engine,
|
|
1952
|
-
fact_id,
|
|
1953
|
-
new_content,
|
|
1954
|
-
trusted_actor_id=local_trusted_actor_id("cli"),
|
|
1955
|
-
source_agent_id="cli",
|
|
1956
|
-
)
|
|
1957
|
-
|
|
1958
|
-
if use_json:
|
|
1959
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1960
|
-
json_print("update", data={
|
|
1961
|
-
"fact_id": fact_id,
|
|
1962
|
-
"old_content": old_content[:120],
|
|
1963
|
-
"new_content": new_content[:120],
|
|
1964
|
-
}, next_actions=[
|
|
1965
|
-
{"command": "slm list --json", "description": "List recent memories"},
|
|
1966
|
-
])
|
|
1967
|
-
return
|
|
1968
|
-
|
|
1969
|
-
print(f"Old: {old_content[:100]}")
|
|
1970
|
-
print(f"New: {new_content[:100]}")
|
|
1971
|
-
print(f"Updated: {fact_id}")
|
|
1732
|
+
_daemon_unavailable("update", use_json)
|
|
1972
1733
|
|
|
1973
1734
|
|
|
1974
1735
|
# -- Diagnostics (all support --json) -------------------------------------
|
|
@@ -2131,12 +1892,10 @@ def cmd_health(args: Namespace) -> None:
|
|
|
2131
1892
|
# second MemoryEngine re-runs schema initialization and can lock
|
|
2132
1893
|
# the user's database. Health only needs aggregate counts, so use
|
|
2133
1894
|
# a read-only snapshot connection instead.
|
|
2134
|
-
import
|
|
1895
|
+
from superlocalmemory.storage.memory_write import memory_read
|
|
1896
|
+
|
|
2135
1897
|
db_path = config.db_path
|
|
2136
|
-
|
|
2137
|
-
f"file:{db_path}?mode=ro", uri=True, timeout=5,
|
|
2138
|
-
)
|
|
2139
|
-
try:
|
|
1898
|
+
with memory_read(db_path) as conn:
|
|
2140
1899
|
row = conn.execute(
|
|
2141
1900
|
"SELECT COUNT(*), "
|
|
2142
1901
|
"SUM(CASE WHEN fisher_mean IS NOT NULL THEN 1 ELSE 0 END), "
|
|
@@ -2145,8 +1904,6 @@ def cmd_health(args: Namespace) -> None:
|
|
|
2145
1904
|
(config.active_profile,),
|
|
2146
1905
|
).fetchone()
|
|
2147
1906
|
total_facts, fisher_count, langevin_count = row or (0, 0, 0)
|
|
2148
|
-
finally:
|
|
2149
|
-
conn.close()
|
|
2150
1907
|
facts = [None] * int(total_facts or 0)
|
|
2151
1908
|
fisher_count = int(fisher_count or 0)
|
|
2152
1909
|
langevin_count = int(langevin_count or 0)
|
|
@@ -2731,10 +2488,10 @@ def cmd_doctor(args: Namespace) -> None:
|
|
|
2731
2488
|
db_path = slm_home / "memory.db"
|
|
2732
2489
|
if db_path.exists():
|
|
2733
2490
|
try:
|
|
2734
|
-
import
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2491
|
+
from superlocalmemory.storage.memory_write import memory_read
|
|
2492
|
+
|
|
2493
|
+
with memory_read(db_path) as conn:
|
|
2494
|
+
result = conn.execute("PRAGMA integrity_check").fetchone()
|
|
2738
2495
|
if result and result[0] == "ok":
|
|
2739
2496
|
size_mb = db_path.stat().st_size / (1024 * 1024)
|
|
2740
2497
|
_check("Database", "PASS", f"OK ({size_mb:.2f} MB)")
|
|
@@ -3601,7 +3358,6 @@ def cmd_session_context(args: Namespace) -> None:
|
|
|
3601
3358
|
output across MCP and CLI surfaces. --json flag returns structured JSON.
|
|
3602
3359
|
"""
|
|
3603
3360
|
import sqlite3
|
|
3604
|
-
from pathlib import Path
|
|
3605
3361
|
from superlocalmemory.core.config import SLMConfig
|
|
3606
3362
|
|
|
3607
3363
|
use_json = getattr(args, "json", False)
|
|
@@ -3644,8 +3400,11 @@ def cmd_session_context(args: Namespace) -> None:
|
|
|
3644
3400
|
return
|
|
3645
3401
|
|
|
3646
3402
|
pid = config.active_profile
|
|
3647
|
-
|
|
3648
|
-
|
|
3403
|
+
from superlocalmemory.storage.memory_write import memory_read
|
|
3404
|
+
|
|
3405
|
+
def _read_rows(query: str, params: tuple[object, ...]) -> list[sqlite3.Row]:
|
|
3406
|
+
with memory_read(db_path) as conn:
|
|
3407
|
+
return conn.execute(query, params).fetchall()
|
|
3649
3408
|
|
|
3650
3409
|
# Collect facts for injection — same queries as pre-v3.4.65 but
|
|
3651
3410
|
# mapped into InjectableMemory for the shared formatter.
|
|
@@ -3653,11 +3412,11 @@ def cmd_session_context(args: Namespace) -> None:
|
|
|
3653
3412
|
|
|
3654
3413
|
# Core Memory blocks (compiled high-value context)
|
|
3655
3414
|
try:
|
|
3656
|
-
cm_rows =
|
|
3415
|
+
cm_rows = _read_rows(
|
|
3657
3416
|
"SELECT block_type, content FROM core_memory_blocks "
|
|
3658
3417
|
"WHERE profile_id = ? ORDER BY block_type",
|
|
3659
3418
|
(pid,),
|
|
3660
|
-
)
|
|
3419
|
+
)
|
|
3661
3420
|
for r in cm_rows:
|
|
3662
3421
|
content = f"[{r['block_type']}] {r['content']}"
|
|
3663
3422
|
inj_mems.append(InjectableMemory(
|
|
@@ -3675,14 +3434,14 @@ def cmd_session_context(args: Namespace) -> None:
|
|
|
3675
3434
|
if max_age > 0 else ""
|
|
3676
3435
|
)
|
|
3677
3436
|
try:
|
|
3678
|
-
fact_rows =
|
|
3437
|
+
fact_rows = _read_rows(
|
|
3679
3438
|
"SELECT fact_id, content, importance, access_count, fact_type FROM atomic_facts "
|
|
3680
3439
|
"WHERE profile_id = ? "
|
|
3681
3440
|
f"{age_clause}"
|
|
3682
3441
|
"AND lifecycle = 'active' "
|
|
3683
3442
|
"ORDER BY importance DESC, created_at DESC LIMIT 10",
|
|
3684
3443
|
(pid,),
|
|
3685
|
-
)
|
|
3444
|
+
)
|
|
3686
3445
|
for r in fact_rows:
|
|
3687
3446
|
inj_mems.append(InjectableMemory(
|
|
3688
3447
|
content=r["content"],
|
|
@@ -3696,12 +3455,12 @@ def cmd_session_context(args: Namespace) -> None:
|
|
|
3696
3455
|
|
|
3697
3456
|
# Session markers (last session summary)
|
|
3698
3457
|
try:
|
|
3699
|
-
sess_rows =
|
|
3458
|
+
sess_rows = _read_rows(
|
|
3700
3459
|
"SELECT fact_id, content, importance, access_count FROM atomic_facts "
|
|
3701
3460
|
"WHERE profile_id = ? AND content LIKE 'Session%' "
|
|
3702
3461
|
"ORDER BY created_at DESC LIMIT 3",
|
|
3703
3462
|
(pid,),
|
|
3704
|
-
)
|
|
3463
|
+
)
|
|
3705
3464
|
for r in sess_rows:
|
|
3706
3465
|
inj_mems.append(InjectableMemory(
|
|
3707
3466
|
content=r["content"],
|
|
@@ -3713,22 +3472,17 @@ def cmd_session_context(args: Namespace) -> None:
|
|
|
3713
3472
|
except sqlite3.OperationalError:
|
|
3714
3473
|
pass
|
|
3715
3474
|
|
|
3716
|
-
conn.close()
|
|
3717
|
-
|
|
3718
3475
|
if not inj_mems:
|
|
3719
3476
|
return
|
|
3720
3477
|
|
|
3721
3478
|
# V3.3 Soft prompts (auto-learned patterns) — append as high-importance
|
|
3722
3479
|
try:
|
|
3723
|
-
|
|
3724
|
-
conn2.row_factory = sqlite3.Row
|
|
3725
|
-
sp_rows = conn2.execute(
|
|
3480
|
+
sp_rows = _read_rows(
|
|
3726
3481
|
"SELECT category, content FROM soft_prompt_templates "
|
|
3727
3482
|
"WHERE profile_id = ? AND active = 1 "
|
|
3728
3483
|
"ORDER BY confidence DESC LIMIT 5",
|
|
3729
3484
|
(pid,),
|
|
3730
|
-
)
|
|
3731
|
-
conn2.close()
|
|
3485
|
+
)
|
|
3732
3486
|
for r in sp_rows:
|
|
3733
3487
|
inj_mems.append(InjectableMemory(
|
|
3734
3488
|
content=f"[{r['category']}] {r['content']}",
|