superlocalmemory 3.8.5 → 3.8.7
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 +47 -0
- package/README.md +3 -3
- 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 -1
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/commands.py +139 -404
- package/src/superlocalmemory/core/backend_orchestrator.py +7 -1
- package/src/superlocalmemory/core/component_registry.py +4 -2
- package/src/superlocalmemory/core/embeddings.py +33 -6
- package/src/superlocalmemory/core/engine.py +94 -49
- package/src/superlocalmemory/core/engine_ingestion.py +150 -63
- package/src/superlocalmemory/core/ingestion_command.py +133 -21
- package/src/superlocalmemory/core/mutations.py +32 -10
- package/src/superlocalmemory/core/recall_pipeline.py +111 -77
- package/src/superlocalmemory/core/remember_admission.py +152 -0
- package/src/superlocalmemory/core/remember_runtime.py +712 -0
- package/src/superlocalmemory/graph/cozo_backend.py +5 -5
- package/src/superlocalmemory/learning/bandit.py +50 -1
- package/src/superlocalmemory/learning/source_quality.py +38 -35
- package/src/superlocalmemory/mcp/_daemon_proxy.py +38 -15
- 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 +8 -3
- package/src/superlocalmemory/retrieval/reranker.py +35 -10
- package/src/superlocalmemory/server/loopback.py +7 -13
- 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/behavioral.py +5 -13
- package/src/superlocalmemory/server/routes/brain.py +6 -9
- 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 +44 -23
- 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 +122 -100
- package/src/superlocalmemory/server/routes/tiers.py +3 -22
- package/src/superlocalmemory/server/routes/timeline.py +2 -4
- package/src/superlocalmemory/server/routes/v3_api.py +18 -16
- package/src/superlocalmemory/server/unified_daemon.py +200 -109
- package/src/superlocalmemory/storage/admission_codec.py +119 -0
- package/src/superlocalmemory/storage/admission_journal.py +728 -0
- package/src/superlocalmemory/storage/database.py +59 -0
- package/src/superlocalmemory/storage/deferred_writes.py +67 -11
- package/src/superlocalmemory/storage/memory_write.py +8 -12
- package/src/superlocalmemory/storage/migration_runner.py +37 -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/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:
|
|
@@ -1339,8 +1358,7 @@ def cmd_list(args: Namespace) -> None:
|
|
|
1339
1358
|
|
|
1340
1359
|
|
|
1341
1360
|
def cmd_remember(args: Namespace) -> None:
|
|
1342
|
-
"""Store a memory
|
|
1343
|
-
from superlocalmemory.core.config import SLMConfig
|
|
1361
|
+
"""Store a memory through the owned daemon."""
|
|
1344
1362
|
|
|
1345
1363
|
use_json = getattr(args, 'json', False)
|
|
1346
1364
|
sync_mode = getattr(args, 'sync_mode', False)
|
|
@@ -1358,128 +1376,43 @@ def cmd_remember(args: Namespace) -> None:
|
|
|
1358
1376
|
if isinstance(_sw_raw, str) and _sw_raw.strip() else _sw_raw
|
|
1359
1377
|
)
|
|
1360
1378
|
|
|
1361
|
-
# Both paths use the one owned daemon. A second local engine for --sync
|
|
1362
|
-
# duplicates heavyweight workers and can block for minutes on cold models.
|
|
1363
|
-
daemon_owned = False
|
|
1364
1379
|
try:
|
|
1365
1380
|
from superlocalmemory.cli.daemon import (
|
|
1366
1381
|
daemon_request, ensure_daemon, is_daemon_running,
|
|
1367
1382
|
)
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
if use_json:
|
|
1382
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1383
|
-
json_print("remember", data=result)
|
|
1384
|
-
else:
|
|
1385
|
-
state = result.get("materialization_state", "queryable")
|
|
1386
|
-
operation_id = result.get("operation_id", "unknown")
|
|
1387
|
-
print(
|
|
1388
|
-
f"{state.capitalize()} \u2713 {result['count']} facts "
|
|
1389
|
-
f"(operation={operation_id})."
|
|
1390
|
-
)
|
|
1391
|
-
return
|
|
1392
|
-
if sync_mode:
|
|
1393
|
-
if use_json:
|
|
1394
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1395
|
-
json_print("remember", error={
|
|
1396
|
-
"code": "SYNC_TIMEOUT",
|
|
1397
|
-
"message": (
|
|
1398
|
-
"Canonical ingestion did not complete within 30s; "
|
|
1399
|
-
"the durable operation remains available for retry."
|
|
1400
|
-
),
|
|
1401
|
-
})
|
|
1402
|
-
else:
|
|
1403
|
-
print(
|
|
1404
|
-
"Synchronous ingestion did not complete within 30s; "
|
|
1405
|
-
"the durable operation remains queued.",
|
|
1406
|
-
file=sys.stderr,
|
|
1407
|
-
)
|
|
1408
|
-
sys.exit(1)
|
|
1409
|
-
except SystemExit:
|
|
1410
|
-
raise
|
|
1411
|
-
except Exception:
|
|
1412
|
-
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:
|
|
1413
1396
|
if use_json:
|
|
1414
1397
|
from superlocalmemory.cli.json_output import json_print
|
|
1415
|
-
json_print("remember",
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
try:
|
|
1426
|
-
config = SLMConfig.load()
|
|
1427
|
-
engine = MemoryEngine(config)
|
|
1428
|
-
engine.initialize()
|
|
1429
|
-
|
|
1430
|
-
# v3.6.15: resolve an unset scope to the configured default_scope.
|
|
1431
|
-
_scope = scope or getattr(getattr(config, "scope", None), "default_scope", "personal")
|
|
1432
|
-
from superlocalmemory.core.engine_ingestion import (
|
|
1433
|
-
canonical_store,
|
|
1434
|
-
local_trusted_actor_id,
|
|
1435
|
-
)
|
|
1436
|
-
|
|
1437
|
-
metadata = {"tags": args.tags} if args.tags else {}
|
|
1438
|
-
operation = canonical_store(
|
|
1439
|
-
engine,
|
|
1440
|
-
args.content,
|
|
1441
|
-
source_type="cli-sync" if sync_mode else "cli-offline-canonical",
|
|
1442
|
-
trusted_actor_id=local_trusted_actor_id("cli"),
|
|
1443
|
-
metadata=metadata,
|
|
1444
|
-
scope=_scope,
|
|
1445
|
-
shared_with=shared_with,
|
|
1446
|
-
return_receipt=True,
|
|
1447
|
-
)
|
|
1448
|
-
except Exception as exc:
|
|
1449
|
-
if use_json:
|
|
1450
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1451
|
-
json_print("remember", error={"code": "STORE_ERROR", "message": str(exc)})
|
|
1452
|
-
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:
|
|
1453
1408
|
raise
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
"fact_ids": fact_ids,
|
|
1458
|
-
"count": len(fact_ids),
|
|
1459
|
-
"materialization_state": getattr(
|
|
1460
|
-
getattr(operation, "state", None), "value", "complete"
|
|
1461
|
-
),
|
|
1462
|
-
}
|
|
1463
|
-
if getattr(operation, "operation_id", None):
|
|
1464
|
-
operation_data["operation_id"] = operation.operation_id
|
|
1465
|
-
|
|
1466
|
-
if use_json:
|
|
1467
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1468
|
-
json_print("remember", data=operation_data,
|
|
1469
|
-
next_actions=[
|
|
1470
|
-
{"command": "slm recall '<query>' --json", "description": "Search your memories"},
|
|
1471
|
-
{"command": "slm list --json -n 5", "description": "See recent memories"},
|
|
1472
|
-
])
|
|
1473
|
-
return
|
|
1474
|
-
|
|
1475
|
-
print(
|
|
1476
|
-
f"Complete \u2713 {len(fact_ids)} facts "
|
|
1477
|
-
f"(operation={operation_data.get('operation_id', 'none')})."
|
|
1478
|
-
)
|
|
1409
|
+
except Exception as exc:
|
|
1410
|
+
logger.warning("owned daemon remember request failed: %s", exc)
|
|
1411
|
+
_daemon_unavailable("remember", use_json)
|
|
1479
1412
|
|
|
1480
1413
|
|
|
1481
1414
|
def cmd_recall(args: Namespace) -> None:
|
|
1482
|
-
"""Search memories
|
|
1415
|
+
"""Search memories through the owned daemon without a local engine."""
|
|
1483
1416
|
use_json = getattr(args, 'json', False)
|
|
1484
1417
|
# v3.6.15: None = "not specified" → daemon/engine resolves the configured
|
|
1485
1418
|
# default (shared-off). Only an explicit --include-global / --no-global
|
|
@@ -1488,7 +1421,6 @@ def cmd_recall(args: Namespace) -> None:
|
|
|
1488
1421
|
include_shared = getattr(args, 'include_shared', None)
|
|
1489
1422
|
|
|
1490
1423
|
# V3.3.21: Route through daemon for instant response (no cold start).
|
|
1491
|
-
# Falls back to direct engine if daemon not running.
|
|
1492
1424
|
# S9-DASH-02: pass a stable session_id derived from the shell's
|
|
1493
1425
|
# parent PID so a sequence of CLI recalls in one terminal can be
|
|
1494
1426
|
# grouped. The Stop hook on session end won't fire for CLI, so
|
|
@@ -1534,76 +1466,10 @@ def cmd_recall(args: Namespace) -> None:
|
|
|
1534
1466
|
return
|
|
1535
1467
|
except Exception as _exc: # noqa: BLE001
|
|
1536
1468
|
logger.warning(
|
|
1537
|
-
"
|
|
1469
|
+
"owned daemon recall request failed: %s", _exc
|
|
1538
1470
|
)
|
|
1539
1471
|
|
|
1540
|
-
|
|
1541
|
-
from superlocalmemory.core.engine import MemoryEngine
|
|
1542
|
-
|
|
1543
|
-
try:
|
|
1544
|
-
config = SLMConfig.load()
|
|
1545
|
-
engine = MemoryEngine(config)
|
|
1546
|
-
engine.initialize()
|
|
1547
|
-
|
|
1548
|
-
response = engine.recall(
|
|
1549
|
-
args.query, limit=args.limit,
|
|
1550
|
-
# v3.8.2: --fast → True; unset → None so engine resolves the
|
|
1551
|
-
# client-driven-agentic default (parity with the daemon path, which
|
|
1552
|
-
# omits the fast query param when --fast is absent).
|
|
1553
|
-
fast=(True if getattr(args, "fast", False) else None),
|
|
1554
|
-
include_global=include_global,
|
|
1555
|
-
include_shared=include_shared,
|
|
1556
|
-
window=getattr(args, "window", "") or None,
|
|
1557
|
-
)
|
|
1558
|
-
except Exception as exc:
|
|
1559
|
-
if use_json:
|
|
1560
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1561
|
-
json_print("recall", error={"code": "RECALL_ERROR", "message": str(exc)})
|
|
1562
|
-
sys.exit(1)
|
|
1563
|
-
raise
|
|
1564
|
-
|
|
1565
|
-
# v3.6.6: route the direct-fallback path through the SAME shared
|
|
1566
|
-
# serializer the daemon uses, so CLI-without-daemon output is identical
|
|
1567
|
-
# to CLI/MCP-with-daemon (budget + source discipline + no_confident_match).
|
|
1568
|
-
from superlocalmemory.server.recall_serializer import (
|
|
1569
|
-
recall_response_metadata,
|
|
1570
|
-
serialize_recall_response,
|
|
1571
|
-
)
|
|
1572
|
-
_rc = getattr(config, "retrieval", None)
|
|
1573
|
-
_ser, _no_match = serialize_recall_response(
|
|
1574
|
-
response,
|
|
1575
|
-
limit=args.limit,
|
|
1576
|
-
per_fact_max=getattr(_rc, "recall_per_fact_max_chars", 2400),
|
|
1577
|
-
total_max=getattr(_rc, "recall_total_max_chars", 12000),
|
|
1578
|
-
full=getattr(args, "full", False),
|
|
1579
|
-
)
|
|
1580
|
-
|
|
1581
|
-
if use_json:
|
|
1582
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1583
|
-
items = []
|
|
1584
|
-
for d in _ser:
|
|
1585
|
-
items.append(dict(d))
|
|
1586
|
-
json_print("recall", data={
|
|
1587
|
-
"results": items, "count": len(items),
|
|
1588
|
-
"query_type": getattr(response, "query_type", "unknown"),
|
|
1589
|
-
"no_confident_match": _no_match,
|
|
1590
|
-
**recall_response_metadata(response),
|
|
1591
|
-
}, next_actions=[
|
|
1592
|
-
{"command": "slm list --json", "description": "List recent memories"},
|
|
1593
|
-
])
|
|
1594
|
-
return
|
|
1595
|
-
|
|
1596
|
-
# Record learning signals (CLI path — works without MCP)
|
|
1597
|
-
try:
|
|
1598
|
-
_cli_record_signals(config, args.query, response.results)
|
|
1599
|
-
except Exception:
|
|
1600
|
-
pass
|
|
1601
|
-
|
|
1602
|
-
if not _ser:
|
|
1603
|
-
print("No confident match." if _no_match else "No memories found.")
|
|
1604
|
-
return
|
|
1605
|
-
for i, d in enumerate(_ser, 1):
|
|
1606
|
-
print(f" {i}. [relevance {d['relevance_score']:.2f}] {d['content']}")
|
|
1472
|
+
_daemon_unavailable("recall", use_json)
|
|
1607
1473
|
|
|
1608
1474
|
|
|
1609
1475
|
def _cli_record_signals(config, query, results):
|
|
@@ -1633,9 +1499,14 @@ def _cli_record_signals(config, query, results):
|
|
|
1633
1499
|
|
|
1634
1500
|
|
|
1635
1501
|
def cmd_forget(args: Namespace) -> None:
|
|
1636
|
-
"""Delete memories matching a query."""
|
|
1637
|
-
|
|
1638
|
-
|
|
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
|
+
)
|
|
1639
1510
|
|
|
1640
1511
|
use_json = getattr(args, 'json', False)
|
|
1641
1512
|
dry_run = getattr(args, 'dry_run', False)
|
|
@@ -1659,38 +1530,45 @@ def cmd_forget(args: Namespace) -> None:
|
|
|
1659
1530
|
query_lower = "" if raw_query is None else raw_query.lower()
|
|
1660
1531
|
query_label = raw_query if raw_query is not None else "*all*"
|
|
1661
1532
|
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
if
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
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)
|
|
1674
1549
|
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1550
|
+
matches = [
|
|
1551
|
+
fact for fact in memories
|
|
1552
|
+
if query_lower in str(fact.get("content", "")).lower()
|
|
1553
|
+
]
|
|
1678
1554
|
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
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=""),
|
|
1684
1559
|
)
|
|
1685
|
-
if not result.get("
|
|
1686
|
-
|
|
1560
|
+
if not isinstance(result, dict) or not result.get("success"):
|
|
1561
|
+
_daemon_unavailable("forget", use_json)
|
|
1687
1562
|
|
|
1688
1563
|
if use_json:
|
|
1689
1564
|
from superlocalmemory.cli.json_output import json_print
|
|
1690
1565
|
if not matches:
|
|
1691
1566
|
json_print("forget", data={"matched_count": 0, "deleted_count": 0, "matches": []})
|
|
1692
1567
|
return
|
|
1693
|
-
match_items = [
|
|
1568
|
+
match_items = [
|
|
1569
|
+
{"fact_id": f["id"], "content": str(f.get("content", ""))[:120]}
|
|
1570
|
+
for f in matches[:20]
|
|
1571
|
+
]
|
|
1694
1572
|
if dry_run:
|
|
1695
1573
|
json_print("forget", data={
|
|
1696
1574
|
"matched_count": len(matches), "deleted_count": 0,
|
|
@@ -1699,10 +1577,10 @@ def cmd_forget(args: Namespace) -> None:
|
|
|
1699
1577
|
return
|
|
1700
1578
|
if getattr(args, 'yes', False):
|
|
1701
1579
|
for f in matches:
|
|
1702
|
-
|
|
1580
|
+
delete_from_daemon(str(f["id"]))
|
|
1703
1581
|
json_print("forget", data={
|
|
1704
1582
|
"matched_count": len(matches), "deleted_count": len(matches),
|
|
1705
|
-
"deleted": [f
|
|
1583
|
+
"deleted": [f["id"] for f in matches],
|
|
1706
1584
|
}, next_actions=[
|
|
1707
1585
|
{"command": "slm list --json", "description": "Verify remaining memories"},
|
|
1708
1586
|
])
|
|
@@ -1712,7 +1590,10 @@ def cmd_forget(args: Namespace) -> None:
|
|
|
1712
1590
|
"matches": match_items,
|
|
1713
1591
|
"hint": "Add --yes to confirm deletion",
|
|
1714
1592
|
}, next_actions=[
|
|
1715
|
-
{
|
|
1593
|
+
{
|
|
1594
|
+
"command": f"slm forget '{query_label}' --json --yes",
|
|
1595
|
+
"description": "Confirm deletion",
|
|
1596
|
+
},
|
|
1716
1597
|
])
|
|
1717
1598
|
return
|
|
1718
1599
|
|
|
@@ -1721,19 +1602,19 @@ def cmd_forget(args: Namespace) -> None:
|
|
|
1721
1602
|
return
|
|
1722
1603
|
print(f"Found {len(matches)} matching memories:")
|
|
1723
1604
|
for f in matches[:10]:
|
|
1724
|
-
print(f" - {f
|
|
1605
|
+
print(f" - {str(f['id'])[:8]}... {str(f.get('content', ''))[:80]}")
|
|
1725
1606
|
if dry_run:
|
|
1726
1607
|
print(f"(dry run — {len(matches)} would be deleted)")
|
|
1727
1608
|
return
|
|
1728
1609
|
if getattr(args, 'yes', False):
|
|
1729
1610
|
for f in matches:
|
|
1730
|
-
|
|
1611
|
+
delete_from_daemon(str(f["id"]))
|
|
1731
1612
|
print(f"Deleted {len(matches)} memories.")
|
|
1732
1613
|
return
|
|
1733
1614
|
confirm = input(f"Delete {len(matches)} memories? [y/N] ").strip().lower()
|
|
1734
1615
|
if confirm in ("y", "yes"):
|
|
1735
1616
|
for f in matches:
|
|
1736
|
-
|
|
1617
|
+
delete_from_daemon(str(f["id"]))
|
|
1737
1618
|
print(f"Deleted {len(matches)} memories.")
|
|
1738
1619
|
else:
|
|
1739
1620
|
print("Cancelled.")
|
|
@@ -1743,13 +1624,15 @@ def cmd_delete(args: Namespace) -> None:
|
|
|
1743
1624
|
"""Delete a specific memory by exact fact ID."""
|
|
1744
1625
|
import urllib.parse
|
|
1745
1626
|
|
|
1746
|
-
from superlocalmemory.cli.daemon import
|
|
1747
|
-
|
|
1748
|
-
|
|
1627
|
+
from superlocalmemory.cli.daemon import (
|
|
1628
|
+
daemon_request,
|
|
1629
|
+
ensure_daemon,
|
|
1630
|
+
is_daemon_running,
|
|
1631
|
+
)
|
|
1749
1632
|
|
|
1750
1633
|
use_json = getattr(args, 'json', False)
|
|
1751
1634
|
fact_id = args.fact_id.strip()
|
|
1752
|
-
if is_daemon_running():
|
|
1635
|
+
if is_daemon_running() or ensure_daemon():
|
|
1753
1636
|
path = "/api/memories/" + urllib.parse.quote(fact_id, safe="")
|
|
1754
1637
|
confirmed = getattr(args, "yes", False)
|
|
1755
1638
|
content = ""
|
|
@@ -1759,14 +1642,7 @@ def cmd_delete(args: Namespace) -> None:
|
|
|
1759
1642
|
"/api/facts/" + urllib.parse.quote(fact_id, safe=""),
|
|
1760
1643
|
)
|
|
1761
1644
|
if not isinstance(detail, dict):
|
|
1762
|
-
|
|
1763
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1764
|
-
json_print("delete", error={
|
|
1765
|
-
"code": "DAEMON_MUTATION_FAILED",
|
|
1766
|
-
"message": "Resident daemon could not resolve the memory.",
|
|
1767
|
-
})
|
|
1768
|
-
sys.exit(1)
|
|
1769
|
-
raise RuntimeError("Resident daemon could not resolve the memory.")
|
|
1645
|
+
_daemon_unavailable("delete", use_json)
|
|
1770
1646
|
content = str(detail.get("content") or "")
|
|
1771
1647
|
if use_json:
|
|
1772
1648
|
from superlocalmemory.cli.json_output import json_print
|
|
@@ -1792,14 +1668,7 @@ def cmd_delete(args: Namespace) -> None:
|
|
|
1792
1668
|
|
|
1793
1669
|
result = daemon_request("DELETE", path)
|
|
1794
1670
|
if not isinstance(result, dict) or not result.get("success"):
|
|
1795
|
-
|
|
1796
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1797
|
-
json_print("delete", error={
|
|
1798
|
-
"code": "DAEMON_MUTATION_FAILED",
|
|
1799
|
-
"message": "Resident daemon rejected the delete operation.",
|
|
1800
|
-
})
|
|
1801
|
-
sys.exit(1)
|
|
1802
|
-
raise RuntimeError("Resident daemon rejected the delete operation.")
|
|
1671
|
+
_daemon_unavailable("delete", use_json)
|
|
1803
1672
|
if use_json:
|
|
1804
1673
|
from superlocalmemory.cli.json_output import json_print
|
|
1805
1674
|
json_print(
|
|
@@ -1815,86 +1684,18 @@ def cmd_delete(args: Namespace) -> None:
|
|
|
1815
1684
|
else:
|
|
1816
1685
|
print(f"Deleted: {fact_id}")
|
|
1817
1686
|
return
|
|
1818
|
-
|
|
1819
|
-
try:
|
|
1820
|
-
config = SLMConfig.load()
|
|
1821
|
-
engine = MemoryEngine(config)
|
|
1822
|
-
engine.initialize()
|
|
1823
|
-
|
|
1824
|
-
rows = engine._db.execute(
|
|
1825
|
-
"SELECT content FROM atomic_facts WHERE fact_id = ? AND profile_id = ?",
|
|
1826
|
-
(fact_id, engine.profile_id),
|
|
1827
|
-
)
|
|
1828
|
-
except Exception as exc:
|
|
1829
|
-
if use_json:
|
|
1830
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1831
|
-
json_print("delete", error={"code": "ENGINE_ERROR", "message": str(exc)})
|
|
1832
|
-
sys.exit(1)
|
|
1833
|
-
raise
|
|
1834
|
-
|
|
1835
|
-
if use_json:
|
|
1836
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1837
|
-
if not rows:
|
|
1838
|
-
json_print("delete", error={
|
|
1839
|
-
"code": "NOT_FOUND", "message": f"Memory not found: {fact_id}",
|
|
1840
|
-
})
|
|
1841
|
-
sys.exit(1)
|
|
1842
|
-
content = dict(rows[0]).get("content", "")
|
|
1843
|
-
if getattr(args, "yes", False):
|
|
1844
|
-
from superlocalmemory.core.engine_ingestion import local_trusted_actor_id
|
|
1845
|
-
from superlocalmemory.core.mutations import delete_fact_authorized
|
|
1846
|
-
|
|
1847
|
-
delete_fact_authorized(
|
|
1848
|
-
engine,
|
|
1849
|
-
fact_id,
|
|
1850
|
-
trusted_actor_id=local_trusted_actor_id("cli"),
|
|
1851
|
-
source_agent_id="cli",
|
|
1852
|
-
)
|
|
1853
|
-
json_print("delete", data={"deleted": fact_id, "content": content[:120]},
|
|
1854
|
-
next_actions=[
|
|
1855
|
-
{"command": "slm list --json", "description": "Verify remaining memories"},
|
|
1856
|
-
])
|
|
1857
|
-
else:
|
|
1858
|
-
json_print("delete", data={
|
|
1859
|
-
"fact_id": fact_id, "content": content[:120], "deleted": False,
|
|
1860
|
-
"hint": "Add --yes to confirm deletion",
|
|
1861
|
-
}, next_actions=[
|
|
1862
|
-
{"command": f"slm delete {fact_id} --json --yes", "description": "Confirm deletion"},
|
|
1863
|
-
])
|
|
1864
|
-
return
|
|
1865
|
-
|
|
1866
|
-
if not rows:
|
|
1867
|
-
print(f"Memory not found: {fact_id}")
|
|
1868
|
-
return
|
|
1869
|
-
|
|
1870
|
-
content_preview = dict(rows[0]).get("content", "")[:120]
|
|
1871
|
-
print(f"Memory: {content_preview}")
|
|
1872
|
-
|
|
1873
|
-
if not getattr(args, "yes", False):
|
|
1874
|
-
confirm = input("Delete this memory? [y/N] ").strip().lower()
|
|
1875
|
-
if confirm not in ("y", "yes"):
|
|
1876
|
-
print("Cancelled.")
|
|
1877
|
-
return
|
|
1878
|
-
|
|
1879
|
-
from superlocalmemory.core.engine_ingestion import local_trusted_actor_id
|
|
1880
|
-
from superlocalmemory.core.mutations import delete_fact_authorized
|
|
1881
|
-
|
|
1882
|
-
delete_fact_authorized(
|
|
1883
|
-
engine,
|
|
1884
|
-
fact_id,
|
|
1885
|
-
trusted_actor_id=local_trusted_actor_id("cli"),
|
|
1886
|
-
source_agent_id="cli",
|
|
1887
|
-
)
|
|
1888
|
-
print(f"Deleted: {fact_id}")
|
|
1687
|
+
_daemon_unavailable("delete", use_json)
|
|
1889
1688
|
|
|
1890
1689
|
|
|
1891
1690
|
def cmd_update(args: Namespace) -> None:
|
|
1892
1691
|
"""Update the content of a specific memory by exact fact ID."""
|
|
1893
1692
|
import urllib.parse
|
|
1894
1693
|
|
|
1895
|
-
from superlocalmemory.cli.daemon import
|
|
1896
|
-
|
|
1897
|
-
|
|
1694
|
+
from superlocalmemory.cli.daemon import (
|
|
1695
|
+
daemon_request,
|
|
1696
|
+
ensure_daemon,
|
|
1697
|
+
is_daemon_running,
|
|
1698
|
+
)
|
|
1898
1699
|
|
|
1899
1700
|
use_json = getattr(args, 'json', False)
|
|
1900
1701
|
fact_id = args.fact_id.strip()
|
|
@@ -1908,18 +1709,11 @@ def cmd_update(args: Namespace) -> None:
|
|
|
1908
1709
|
print("Error: content cannot be empty")
|
|
1909
1710
|
return
|
|
1910
1711
|
|
|
1911
|
-
if is_daemon_running():
|
|
1712
|
+
if is_daemon_running() or ensure_daemon():
|
|
1912
1713
|
path = "/api/memories/" + urllib.parse.quote(fact_id, safe="")
|
|
1913
1714
|
result = daemon_request("PATCH", path, {"content": new_content})
|
|
1914
1715
|
if not isinstance(result, dict) or not result.get("success"):
|
|
1915
|
-
|
|
1916
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1917
|
-
json_print("update", error={
|
|
1918
|
-
"code": "DAEMON_MUTATION_FAILED",
|
|
1919
|
-
"message": "Resident daemon rejected the update operation.",
|
|
1920
|
-
})
|
|
1921
|
-
sys.exit(1)
|
|
1922
|
-
raise RuntimeError("Resident daemon rejected the update operation.")
|
|
1716
|
+
_daemon_unavailable("update", use_json)
|
|
1923
1717
|
if use_json:
|
|
1924
1718
|
from superlocalmemory.cli.json_output import json_print
|
|
1925
1719
|
json_print("update", data={
|
|
@@ -1935,59 +1729,7 @@ def cmd_update(args: Namespace) -> None:
|
|
|
1935
1729
|
print(f"New: {new_content[:100]}")
|
|
1936
1730
|
print(f"Updated: {fact_id}")
|
|
1937
1731
|
return
|
|
1938
|
-
|
|
1939
|
-
try:
|
|
1940
|
-
config = SLMConfig.load()
|
|
1941
|
-
engine = MemoryEngine(config)
|
|
1942
|
-
engine.initialize()
|
|
1943
|
-
|
|
1944
|
-
rows = engine._db.execute(
|
|
1945
|
-
"SELECT content FROM atomic_facts WHERE fact_id = ? AND profile_id = ?",
|
|
1946
|
-
(fact_id, engine.profile_id),
|
|
1947
|
-
)
|
|
1948
|
-
except Exception as exc:
|
|
1949
|
-
if use_json:
|
|
1950
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1951
|
-
json_print("update", error={"code": "ENGINE_ERROR", "message": str(exc)})
|
|
1952
|
-
sys.exit(1)
|
|
1953
|
-
raise
|
|
1954
|
-
|
|
1955
|
-
if not rows:
|
|
1956
|
-
if use_json:
|
|
1957
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1958
|
-
json_print("update", error={
|
|
1959
|
-
"code": "NOT_FOUND", "message": f"Memory not found: {fact_id}",
|
|
1960
|
-
})
|
|
1961
|
-
sys.exit(1)
|
|
1962
|
-
print(f"Memory not found: {fact_id}")
|
|
1963
|
-
return
|
|
1964
|
-
|
|
1965
|
-
old_content = dict(rows[0]).get("content", "")
|
|
1966
|
-
from superlocalmemory.core.engine_ingestion import local_trusted_actor_id
|
|
1967
|
-
from superlocalmemory.core.mutations import update_fact_authorized
|
|
1968
|
-
|
|
1969
|
-
update_fact_authorized(
|
|
1970
|
-
engine,
|
|
1971
|
-
fact_id,
|
|
1972
|
-
new_content,
|
|
1973
|
-
trusted_actor_id=local_trusted_actor_id("cli"),
|
|
1974
|
-
source_agent_id="cli",
|
|
1975
|
-
)
|
|
1976
|
-
|
|
1977
|
-
if use_json:
|
|
1978
|
-
from superlocalmemory.cli.json_output import json_print
|
|
1979
|
-
json_print("update", data={
|
|
1980
|
-
"fact_id": fact_id,
|
|
1981
|
-
"old_content": old_content[:120],
|
|
1982
|
-
"new_content": new_content[:120],
|
|
1983
|
-
}, next_actions=[
|
|
1984
|
-
{"command": "slm list --json", "description": "List recent memories"},
|
|
1985
|
-
])
|
|
1986
|
-
return
|
|
1987
|
-
|
|
1988
|
-
print(f"Old: {old_content[:100]}")
|
|
1989
|
-
print(f"New: {new_content[:100]}")
|
|
1990
|
-
print(f"Updated: {fact_id}")
|
|
1732
|
+
_daemon_unavailable("update", use_json)
|
|
1991
1733
|
|
|
1992
1734
|
|
|
1993
1735
|
# -- Diagnostics (all support --json) -------------------------------------
|
|
@@ -2150,12 +1892,10 @@ def cmd_health(args: Namespace) -> None:
|
|
|
2150
1892
|
# second MemoryEngine re-runs schema initialization and can lock
|
|
2151
1893
|
# the user's database. Health only needs aggregate counts, so use
|
|
2152
1894
|
# a read-only snapshot connection instead.
|
|
2153
|
-
import
|
|
1895
|
+
from superlocalmemory.storage.memory_write import memory_read
|
|
1896
|
+
|
|
2154
1897
|
db_path = config.db_path
|
|
2155
|
-
|
|
2156
|
-
f"file:{db_path}?mode=ro", uri=True, timeout=5,
|
|
2157
|
-
)
|
|
2158
|
-
try:
|
|
1898
|
+
with memory_read(db_path) as conn:
|
|
2159
1899
|
row = conn.execute(
|
|
2160
1900
|
"SELECT COUNT(*), "
|
|
2161
1901
|
"SUM(CASE WHEN fisher_mean IS NOT NULL THEN 1 ELSE 0 END), "
|
|
@@ -2164,8 +1904,6 @@ def cmd_health(args: Namespace) -> None:
|
|
|
2164
1904
|
(config.active_profile,),
|
|
2165
1905
|
).fetchone()
|
|
2166
1906
|
total_facts, fisher_count, langevin_count = row or (0, 0, 0)
|
|
2167
|
-
finally:
|
|
2168
|
-
conn.close()
|
|
2169
1907
|
facts = [None] * int(total_facts or 0)
|
|
2170
1908
|
fisher_count = int(fisher_count or 0)
|
|
2171
1909
|
langevin_count = int(langevin_count or 0)
|
|
@@ -2750,10 +2488,10 @@ def cmd_doctor(args: Namespace) -> None:
|
|
|
2750
2488
|
db_path = slm_home / "memory.db"
|
|
2751
2489
|
if db_path.exists():
|
|
2752
2490
|
try:
|
|
2753
|
-
import
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
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()
|
|
2757
2495
|
if result and result[0] == "ok":
|
|
2758
2496
|
size_mb = db_path.stat().st_size / (1024 * 1024)
|
|
2759
2497
|
_check("Database", "PASS", f"OK ({size_mb:.2f} MB)")
|
|
@@ -3620,7 +3358,6 @@ def cmd_session_context(args: Namespace) -> None:
|
|
|
3620
3358
|
output across MCP and CLI surfaces. --json flag returns structured JSON.
|
|
3621
3359
|
"""
|
|
3622
3360
|
import sqlite3
|
|
3623
|
-
from pathlib import Path
|
|
3624
3361
|
from superlocalmemory.core.config import SLMConfig
|
|
3625
3362
|
|
|
3626
3363
|
use_json = getattr(args, "json", False)
|
|
@@ -3663,8 +3400,11 @@ def cmd_session_context(args: Namespace) -> None:
|
|
|
3663
3400
|
return
|
|
3664
3401
|
|
|
3665
3402
|
pid = config.active_profile
|
|
3666
|
-
|
|
3667
|
-
|
|
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()
|
|
3668
3408
|
|
|
3669
3409
|
# Collect facts for injection — same queries as pre-v3.4.65 but
|
|
3670
3410
|
# mapped into InjectableMemory for the shared formatter.
|
|
@@ -3672,11 +3412,11 @@ def cmd_session_context(args: Namespace) -> None:
|
|
|
3672
3412
|
|
|
3673
3413
|
# Core Memory blocks (compiled high-value context)
|
|
3674
3414
|
try:
|
|
3675
|
-
cm_rows =
|
|
3415
|
+
cm_rows = _read_rows(
|
|
3676
3416
|
"SELECT block_type, content FROM core_memory_blocks "
|
|
3677
3417
|
"WHERE profile_id = ? ORDER BY block_type",
|
|
3678
3418
|
(pid,),
|
|
3679
|
-
)
|
|
3419
|
+
)
|
|
3680
3420
|
for r in cm_rows:
|
|
3681
3421
|
content = f"[{r['block_type']}] {r['content']}"
|
|
3682
3422
|
inj_mems.append(InjectableMemory(
|
|
@@ -3694,14 +3434,14 @@ def cmd_session_context(args: Namespace) -> None:
|
|
|
3694
3434
|
if max_age > 0 else ""
|
|
3695
3435
|
)
|
|
3696
3436
|
try:
|
|
3697
|
-
fact_rows =
|
|
3437
|
+
fact_rows = _read_rows(
|
|
3698
3438
|
"SELECT fact_id, content, importance, access_count, fact_type FROM atomic_facts "
|
|
3699
3439
|
"WHERE profile_id = ? "
|
|
3700
3440
|
f"{age_clause}"
|
|
3701
3441
|
"AND lifecycle = 'active' "
|
|
3702
3442
|
"ORDER BY importance DESC, created_at DESC LIMIT 10",
|
|
3703
3443
|
(pid,),
|
|
3704
|
-
)
|
|
3444
|
+
)
|
|
3705
3445
|
for r in fact_rows:
|
|
3706
3446
|
inj_mems.append(InjectableMemory(
|
|
3707
3447
|
content=r["content"],
|
|
@@ -3715,12 +3455,12 @@ def cmd_session_context(args: Namespace) -> None:
|
|
|
3715
3455
|
|
|
3716
3456
|
# Session markers (last session summary)
|
|
3717
3457
|
try:
|
|
3718
|
-
sess_rows =
|
|
3458
|
+
sess_rows = _read_rows(
|
|
3719
3459
|
"SELECT fact_id, content, importance, access_count FROM atomic_facts "
|
|
3720
3460
|
"WHERE profile_id = ? AND content LIKE 'Session%' "
|
|
3721
3461
|
"ORDER BY created_at DESC LIMIT 3",
|
|
3722
3462
|
(pid,),
|
|
3723
|
-
)
|
|
3463
|
+
)
|
|
3724
3464
|
for r in sess_rows:
|
|
3725
3465
|
inj_mems.append(InjectableMemory(
|
|
3726
3466
|
content=r["content"],
|
|
@@ -3732,22 +3472,17 @@ def cmd_session_context(args: Namespace) -> None:
|
|
|
3732
3472
|
except sqlite3.OperationalError:
|
|
3733
3473
|
pass
|
|
3734
3474
|
|
|
3735
|
-
conn.close()
|
|
3736
|
-
|
|
3737
3475
|
if not inj_mems:
|
|
3738
3476
|
return
|
|
3739
3477
|
|
|
3740
3478
|
# V3.3 Soft prompts (auto-learned patterns) — append as high-importance
|
|
3741
3479
|
try:
|
|
3742
|
-
|
|
3743
|
-
conn2.row_factory = sqlite3.Row
|
|
3744
|
-
sp_rows = conn2.execute(
|
|
3480
|
+
sp_rows = _read_rows(
|
|
3745
3481
|
"SELECT category, content FROM soft_prompt_templates "
|
|
3746
3482
|
"WHERE profile_id = ? AND active = 1 "
|
|
3747
3483
|
"ORDER BY confidence DESC LIMIT 5",
|
|
3748
3484
|
(pid,),
|
|
3749
|
-
)
|
|
3750
|
-
conn2.close()
|
|
3485
|
+
)
|
|
3751
3486
|
for r in sp_rows:
|
|
3752
3487
|
inj_mems.append(InjectableMemory(
|
|
3753
3488
|
content=f"[{r['category']}] {r['content']}",
|