superlocalmemory 3.6.23 → 3.7.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/CHANGELOG.md +52 -0
- package/README.md +271 -71
- package/bin/slm-npm +43 -89
- package/ide/configs/antigravity-mcp.json +2 -2
- package/ide/configs/chatgpt-desktop-mcp.json +1 -1
- package/ide/configs/claude-desktop-mcp.json +2 -2
- package/ide/configs/windsurf-mcp.json +2 -2
- package/ide/hooks/context-hook.js +6 -2
- package/ide/hooks/post-recall-hook.js +7 -3
- package/ide/hooks/tool-event-hook.sh +2 -1
- package/package.json +18 -10
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/_GENERATED.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin-src/rules/AGENTS.md +1 -1
- package/pyproject.toml +40 -8
- package/scripts/postinstall-interactive.js +17 -94
- package/scripts/postinstall.js +185 -258
- package/scripts/preuninstall.js +9 -50
- package/src/superlocalmemory/__init__.py +2 -2
- package/src/superlocalmemory/attribution/mathematical_dna.py +1 -1
- package/src/superlocalmemory/attribution/signer.py +34 -19
- package/src/superlocalmemory/attribution/watermark.py +1 -1
- package/src/superlocalmemory/cli/_lazy_init.py +3 -5
- package/src/superlocalmemory/cli/commands.py +453 -191
- package/src/superlocalmemory/cli/context_commands.py +5 -4
- package/src/superlocalmemory/cli/daemon.py +282 -187
- package/src/superlocalmemory/cli/db_migrate.py +3 -1
- package/src/superlocalmemory/cli/diagnostics_cmd.py +28 -0
- package/src/superlocalmemory/cli/evidence_cmd.py +103 -0
- package/src/superlocalmemory/cli/ingest_cmd.py +7 -3
- package/src/superlocalmemory/cli/main.py +128 -31
- package/src/superlocalmemory/cli/pending_store.py +54 -38
- package/src/superlocalmemory/cli/scale_engine_cmd.py +37 -0
- package/src/superlocalmemory/cli/service_installer.py +57 -52
- package/src/superlocalmemory/cli/setup_wizard.py +142 -88
- package/src/superlocalmemory/cli/version_banner.py +2 -1
- package/src/superlocalmemory/code_graph/config.py +3 -1
- package/src/superlocalmemory/core/backend_orchestrator.py +81 -21
- package/src/superlocalmemory/core/config.py +65 -20
- package/src/superlocalmemory/core/consolidation_engine.py +9 -7
- package/src/superlocalmemory/core/context_cache.py +56 -8
- package/src/superlocalmemory/core/derivation_lineage.py +246 -0
- package/src/superlocalmemory/core/embedding_worker.py +32 -20
- package/src/superlocalmemory/core/embeddings.py +54 -18
- package/src/superlocalmemory/core/engine.py +150 -104
- package/src/superlocalmemory/core/engine_ingestion.py +513 -0
- package/src/superlocalmemory/core/engine_wiring.py +2 -0
- package/src/superlocalmemory/core/evidence_bundle.py +526 -0
- package/src/superlocalmemory/core/fact_consolidator.py +5 -11
- package/src/superlocalmemory/core/graph_analyzer.py +2 -2
- package/src/superlocalmemory/core/health_monitor.py +4 -2
- package/src/superlocalmemory/core/ingestion_command.py +636 -0
- package/src/superlocalmemory/core/injection.py +69 -18
- package/src/superlocalmemory/core/lifecycle_state.py +153 -0
- package/src/superlocalmemory/core/maintenance.py +1 -1
- package/src/superlocalmemory/core/maintenance_scheduler.py +51 -35
- package/src/superlocalmemory/core/mutations.py +143 -0
- package/src/superlocalmemory/core/platform_utils.py +7 -4
- package/src/superlocalmemory/core/ram_lock.py +16 -5
- package/src/superlocalmemory/core/rate_limit.py +1 -1
- package/src/superlocalmemory/core/recall_pipeline.py +60 -101
- package/src/superlocalmemory/core/recall_worker.py +76 -59
- package/src/superlocalmemory/core/registry.py +1 -1
- package/src/superlocalmemory/core/scale_engine.py +293 -0
- package/src/superlocalmemory/core/score_contract.py +62 -0
- package/src/superlocalmemory/core/security_primitives.py +3 -1
- package/src/superlocalmemory/core/slm_disabled.py +3 -5
- package/src/superlocalmemory/core/store_pipeline.py +172 -40
- package/src/superlocalmemory/core/tier_manager.py +32 -20
- package/src/superlocalmemory/core/worker_pool.py +13 -4
- package/src/superlocalmemory/dynamics/activation_guided_quantization.py +1 -1
- package/src/superlocalmemory/dynamics/eap_scheduler.py +10 -3
- package/src/superlocalmemory/dynamics/ebbinghaus_langevin_coupling.py +1 -1
- package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +1 -1
- package/src/superlocalmemory/encoding/auto_linker.py +1 -1
- package/src/superlocalmemory/encoding/cognitive_consolidator.py +7 -16
- package/src/superlocalmemory/encoding/consolidator.py +22 -5
- package/src/superlocalmemory/encoding/fact_extractor.py +1 -1
- package/src/superlocalmemory/encoding/foresight.py +2 -0
- package/src/superlocalmemory/encoding/graph_builder.py +1 -1
- package/src/superlocalmemory/encoding/temporal_parser.py +2 -0
- package/src/superlocalmemory/evaluation/__init__.py +13 -0
- package/src/superlocalmemory/evaluation/calibration.py +308 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +2 -1
- package/src/superlocalmemory/graph/cozo_backend.py +256 -23
- package/src/superlocalmemory/hooks/_outcome_common.py +21 -11
- package/src/superlocalmemory/hooks/antigravity_adapter.py +10 -31
- package/src/superlocalmemory/hooks/auto_invoker.py +25 -27
- package/src/superlocalmemory/hooks/auto_recall.py +31 -6
- package/src/superlocalmemory/hooks/auto_recall_hook.py +13 -33
- package/src/superlocalmemory/hooks/before_web_hook.py +9 -7
- package/src/superlocalmemory/hooks/claude_code_hooks.py +123 -35
- package/src/superlocalmemory/hooks/codex_assets.py +59 -0
- package/src/superlocalmemory/hooks/codex_hooks.py +186 -0
- package/src/superlocalmemory/hooks/context_payload.py +1 -1
- package/src/superlocalmemory/hooks/copilot_adapter.py +9 -24
- package/src/superlocalmemory/hooks/cursor_adapter.py +10 -32
- package/src/superlocalmemory/hooks/hook_daemon.py +4 -2
- package/src/superlocalmemory/hooks/hook_handlers.py +219 -32
- package/src/superlocalmemory/hooks/memory_protocol.py +5 -3
- package/src/superlocalmemory/hooks/post_tool_async_hook.py +4 -2
- package/src/superlocalmemory/hooks/session_registry.py +15 -8
- package/src/superlocalmemory/hooks/stop_outcome_hook.py +10 -6
- package/src/superlocalmemory/hooks/topic_shift_hook.py +42 -12
- package/src/superlocalmemory/hooks/user_prompt_hook.py +9 -14
- package/src/superlocalmemory/hooks/user_prompt_rehash_hook.py +19 -11
- package/src/superlocalmemory/infra/auth_middleware.py +38 -5
- package/src/superlocalmemory/infra/backup.py +7 -5
- package/src/superlocalmemory/infra/cloud_backup.py +18 -8
- package/src/superlocalmemory/infra/daemon_identity.py +248 -0
- package/src/superlocalmemory/infra/data_root.py +199 -0
- package/src/superlocalmemory/infra/event_bus.py +3 -1
- package/src/superlocalmemory/infra/local_diagnostics.py +327 -0
- package/src/superlocalmemory/infra/process_reaper.py +23 -0
- package/src/superlocalmemory/ingestion/adapter_manager.py +27 -9
- package/src/superlocalmemory/ingestion/base_adapter.py +25 -31
- package/src/superlocalmemory/ingestion/calendar_adapter.py +13 -4
- package/src/superlocalmemory/ingestion/credentials.py +14 -7
- package/src/superlocalmemory/ingestion/gmail_adapter.py +13 -4
- package/src/superlocalmemory/ingestion/transcript_adapter.py +7 -2
- package/src/superlocalmemory/learning/consolidation_quantization_worker.py +1 -1
- package/src/superlocalmemory/learning/ensemble.py +11 -0
- package/src/superlocalmemory/learning/entity_compiler.py +1 -1
- package/src/superlocalmemory/learning/feedback.py +1 -1
- package/src/superlocalmemory/learning/forgetting_scheduler.py +12 -7
- package/src/superlocalmemory/learning/quantization_scheduler.py +1 -1
- package/src/superlocalmemory/learning/ranker.py +4 -1
- package/src/superlocalmemory/learning/source_quality.py +1 -1
- package/src/superlocalmemory/learning/trigram_index.py +3 -2
- package/src/superlocalmemory/llm/backbone.py +1 -1
- package/src/superlocalmemory/math/ebbinghaus.py +1 -1
- package/src/superlocalmemory/math/fisher.py +1 -1
- package/src/superlocalmemory/math/fisher_quantized.py +1 -1
- package/src/superlocalmemory/math/hopfield.py +1 -1
- package/src/superlocalmemory/math/langevin.py +1 -1
- package/src/superlocalmemory/math/polar_quant.py +3 -4
- package/src/superlocalmemory/math/qjl.py +1 -1
- package/src/superlocalmemory/math/sheaf.py +1 -1
- package/src/superlocalmemory/math/turbo_quant.py +3 -2
- package/src/superlocalmemory/mcp/_daemon_proxy.py +12 -11
- package/src/superlocalmemory/mcp/_pool_adapter.py +27 -0
- package/src/superlocalmemory/mcp/http_transport.py +53 -0
- package/src/superlocalmemory/mcp/server.py +39 -13
- package/src/superlocalmemory/mcp/shared.py +69 -3
- package/src/superlocalmemory/mcp/tools_active.py +141 -31
- package/src/superlocalmemory/mcp/tools_core.py +128 -29
- package/src/superlocalmemory/mcp/tools_evolution.py +5 -7
- package/src/superlocalmemory/mcp/tools_learning.py +42 -2
- package/src/superlocalmemory/mcp/tools_mesh.py +7 -23
- package/src/superlocalmemory/mcp/tools_optimize.py +8 -1
- package/src/superlocalmemory/mcp/tools_v28.py +23 -2
- package/src/superlocalmemory/mcp/tools_v3.py +26 -1
- package/src/superlocalmemory/mcp/tools_v33.py +56 -17
- package/src/superlocalmemory/mesh/broker.py +2 -0
- package/src/superlocalmemory/mesh/remote_sync.py +50 -12
- package/src/superlocalmemory/optimize/cache/manager.py +77 -1
- package/src/superlocalmemory/optimize/cache/semantic.py +23 -3
- package/src/superlocalmemory/optimize/compress/ccr.py +4 -0
- package/src/superlocalmemory/optimize/compress/router.py +6 -1
- package/src/superlocalmemory/optimize/config/__init__.py +5 -0
- package/src/superlocalmemory/optimize/config/store.py +6 -4
- package/src/superlocalmemory/optimize/proxy/_helpers.py +15 -5
- package/src/superlocalmemory/optimize/proxy/capture.py +3 -2
- package/src/superlocalmemory/optimize/proxy/server.py +2 -2
- package/src/superlocalmemory/optimize/storage/db.py +12 -12
- package/src/superlocalmemory/retrieval/agentic.py +1 -1
- package/src/superlocalmemory/retrieval/ann_index.py +1 -1
- package/src/superlocalmemory/retrieval/bm25_channel.py +35 -11
- package/src/superlocalmemory/retrieval/bridge_discovery.py +73 -8
- package/src/superlocalmemory/retrieval/engine.py +169 -79
- package/src/superlocalmemory/retrieval/entity_channel.py +289 -67
- package/src/superlocalmemory/retrieval/forgetting_filter.py +1 -1
- package/src/superlocalmemory/retrieval/fusion.py +1 -1
- package/src/superlocalmemory/retrieval/hopfield_channel.py +118 -30
- package/src/superlocalmemory/retrieval/profile_channel.py +1 -1
- package/src/superlocalmemory/retrieval/quantization_aware_search.py +16 -10
- package/src/superlocalmemory/retrieval/reranker.py +56 -20
- package/src/superlocalmemory/retrieval/scope_policy.py +85 -0
- package/src/superlocalmemory/retrieval/semantic_channel.py +122 -14
- package/src/superlocalmemory/retrieval/spreading_activation.py +141 -25
- package/src/superlocalmemory/retrieval/strategy.py +1 -1
- package/src/superlocalmemory/retrieval/temporal_channel.py +30 -15
- package/src/superlocalmemory/retrieval/vector_store.py +1 -1
- package/src/superlocalmemory/server/api.py +10 -7
- package/src/superlocalmemory/server/bandit_loops.py +4 -2
- package/src/superlocalmemory/server/recall_serializer.py +24 -0
- package/src/superlocalmemory/server/route_mutations.py +84 -0
- package/src/superlocalmemory/server/routes/agents.py +8 -6
- package/src/superlocalmemory/server/routes/brain.py +14 -12
- package/src/superlocalmemory/server/routes/chat.py +29 -12
- package/src/superlocalmemory/server/routes/data_io.py +55 -24
- package/src/superlocalmemory/server/routes/helpers.py +8 -63
- package/src/superlocalmemory/server/routes/ingest.py +53 -36
- package/src/superlocalmemory/server/routes/memories.py +104 -43
- package/src/superlocalmemory/server/routes/mesh.py +31 -0
- package/src/superlocalmemory/server/routes/profiles.py +26 -4
- package/src/superlocalmemory/server/routes/tiers.py +43 -11
- package/src/superlocalmemory/server/routes/timeline.py +5 -1
- package/src/superlocalmemory/server/routes/v3_api.py +76 -21
- package/src/superlocalmemory/server/security_middleware.py +1 -1
- package/src/superlocalmemory/server/unified_daemon.py +680 -293
- package/src/superlocalmemory/server/write_identity.py +147 -0
- package/src/superlocalmemory/storage/access_log.py +4 -3
- package/src/superlocalmemory/storage/database.py +118 -25
- package/src/superlocalmemory/storage/migration_runner.py +84 -1
- package/src/superlocalmemory/storage/migration_v33.py +1 -1
- package/src/superlocalmemory/storage/migrations/M002_model_state_history.py +6 -60
- package/src/superlocalmemory/storage/migrations/M018_ingestion_operations.py +120 -0
- package/src/superlocalmemory/storage/migrations/M019_derivation_lineage.py +54 -0
- package/src/superlocalmemory/storage/migrations/M020_model_state_integrity.py +52 -0
- package/src/superlocalmemory/storage/migrations/__init__.py +5 -0
- package/src/superlocalmemory/storage/models.py +16 -0
- package/src/superlocalmemory/storage/quantized_store.py +20 -3
- package/src/superlocalmemory/storage/v2_migrator.py +5 -3
- package/src/superlocalmemory/ui/favicon.svg +5 -0
- package/src/superlocalmemory/ui/index.html +1 -0
- package/src/superlocalmemory/ui/js/compliance.js +1 -1
- package/src/superlocalmemory/ui/js/core.js +49 -8
- package/src/superlocalmemory/ui/js/dashboard.js +23 -2
- package/src/superlocalmemory/ui/js/feedback.js +1 -1
- package/src/superlocalmemory/ui/js/graph-filters.js +1 -1
- package/src/superlocalmemory/ui/js/graph-ui.js +1 -1
- package/src/superlocalmemory/ui/js/lifecycle.js +1 -1
- package/src/superlocalmemory/ui/js/ng-mesh.js +15 -49
- package/src/superlocalmemory/ui/js/settings.js +4 -2
- package/src/superlocalmemory/vector/lancedb_backend.py +57 -9
- package/bin/slm +0 -59
- package/bin/slm.bat +0 -77
- package/bin/slm.cmd +0 -5
- package/ide/integrations/langchain/README.md +0 -106
- package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +0 -9
- package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +0 -201
- package/ide/integrations/langchain/pyproject.toml +0 -38
- package/ide/integrations/langchain/tests/__init__.py +0 -3
- package/ide/integrations/langchain/tests/test_chat_message_history.py +0 -215
- package/ide/integrations/langchain/tests/test_security.py +0 -117
- package/ide/integrations/llamaindex/README.md +0 -81
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +0 -9
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +0 -316
- package/ide/integrations/llamaindex/pyproject.toml +0 -43
- package/ide/integrations/llamaindex/tests/__init__.py +0 -3
- package/ide/integrations/llamaindex/tests/test_chat_store.py +0 -294
- package/ide/integrations/llamaindex/tests/test_security.py +0 -241
- package/plugin-src/.mcp.json +0 -12
- package/plugin-src/agents/slm-memory-advisor.md +0 -44
- package/plugin-src/agents/slm-optimize-advisor.md +0 -38
- package/plugin-src/hooks/.gitkeep +0 -0
- package/plugin-src/hooks/hooks.json +0 -23
- package/plugin-src/manifest.json +0 -25
- package/plugin-src/requirements.txt +0 -1
- package/plugin-src/rules/CLAUDE.md.fragment +0 -44
- package/plugin-src/scripts/ensure-venv.bat +0 -122
- package/plugin-src/scripts/ensure-venv.sh +0 -105
- package/plugin-src/scripts/slm-launch +0 -15
- package/plugin-src/scripts/slm-launch.bat +0 -17
- package/plugin-src/settings.json +0 -16
- package/plugin-src/skills/slm-cache/SKILL.md +0 -140
- package/plugin-src/skills/slm-compress/SKILL.md +0 -143
- package/plugin-src/skills/slm-graph/SKILL.md +0 -300
- package/plugin-src/skills/slm-recall/SKILL.md +0 -204
- package/plugin-src/skills/slm-remember/SKILL.md +0 -194
- package/plugin-src/skills/slm-session/SKILL.md +0 -207
- package/plugin-src/skills/slm-status/SKILL.md +0 -149
- package/scripts/__tests__/build-plugin.test.mjs +0 -613
- package/scripts/_savings_math.py +0 -270
- package/scripts/build-dmg.sh +0 -417
- package/scripts/build-plugin.js +0 -742
- package/scripts/build-slm-hook.ps1 +0 -40
- package/scripts/build-slm-hook.sh +0 -45
- package/scripts/build_entry.py +0 -452
- package/scripts/ci/stage5b_gate.sh +0 -50
- package/scripts/dogfood_savings.py +0 -490
- package/scripts/generate-thumbnails.py +0 -218
- package/scripts/install-skills.ps1 +0 -4
- package/scripts/install-skills.sh +0 -5
- package/scripts/install.ps1 +0 -701
- package/scripts/install.sh +0 -1015
- package/scripts/postinstall_binary.js +0 -287
- package/scripts/prepack.js +0 -33
- package/scripts/release_manifest.py +0 -273
- package/scripts/slm-hook.spec +0 -56
- package/scripts/start-dashboard.ps1 +0 -52
- package/scripts/start-dashboard.sh +0 -41
- package/scripts/sync-wiki.ps1 +0 -127
- package/scripts/sync-wiki.sh +0 -82
- package/scripts/test-dmg.sh +0 -161
- package/scripts/test-npm-package.ps1 +0 -252
- package/scripts/test-npm-package.sh +0 -207
- package/scripts/verify-install.ps1 +0 -294
- package/scripts/verify-install.sh +0 -266
- package/scripts/verify-v27.ps1 +0 -301
- package/scripts/verify-v27.sh +0 -233
- package/src/superlocalmemory.egg-info/PKG-INFO +0 -516
- package/src/superlocalmemory.egg-info/SOURCES.txt +0 -529
- package/src/superlocalmemory.egg-info/dependency_links.txt +0 -1
- package/src/superlocalmemory.egg-info/entry_points.txt +0 -2
- package/src/superlocalmemory.egg-info/requires.txt +0 -71
- package/src/superlocalmemory.egg-info/top_level.txt +0 -1
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under AGPL-3.0-or-later - see LICENSE file
|
|
3
|
+
|
|
4
|
+
"""CLI for versioned evidence export, verification, import, and rebuild."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from argparse import Namespace
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _database():
|
|
14
|
+
from superlocalmemory.core.config import SLMConfig
|
|
15
|
+
from superlocalmemory.storage.database import DatabaseManager
|
|
16
|
+
|
|
17
|
+
config = SLMConfig.load()
|
|
18
|
+
return DatabaseManager(config.db_path)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _emit(value: dict, *, as_json: bool) -> None:
|
|
22
|
+
if as_json:
|
|
23
|
+
print(json.dumps(value, sort_keys=True))
|
|
24
|
+
return
|
|
25
|
+
for key, item in value.items():
|
|
26
|
+
print(f"{key}: {item}")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def cmd_evidence(args: Namespace) -> None:
|
|
30
|
+
"""Dispatch ``slm evidence`` without implicit destructive behavior."""
|
|
31
|
+
from superlocalmemory.core.evidence_bundle import (
|
|
32
|
+
export_evidence_bundle,
|
|
33
|
+
import_evidence_bundle,
|
|
34
|
+
rebuild_derived_state,
|
|
35
|
+
verify_evidence_bundle,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
action = getattr(args, "evidence_command", None)
|
|
39
|
+
as_json = bool(getattr(args, "json", False))
|
|
40
|
+
profile = str(getattr(args, "profile", "default") or "default")
|
|
41
|
+
|
|
42
|
+
if action == "verify":
|
|
43
|
+
report = verify_evidence_bundle(args.bundle)
|
|
44
|
+
_emit({
|
|
45
|
+
"valid": report.valid,
|
|
46
|
+
"bundle_id": report.bundle_id,
|
|
47
|
+
"counts": report.counts,
|
|
48
|
+
"errors": list(report.errors),
|
|
49
|
+
"warnings": list(report.warnings),
|
|
50
|
+
}, as_json=as_json)
|
|
51
|
+
if not report.valid:
|
|
52
|
+
raise SystemExit(1)
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
if action == "export":
|
|
56
|
+
db = _database()
|
|
57
|
+
if not Path(db.db_path).is_file():
|
|
58
|
+
raise SystemExit(f"memory database not found: {db.db_path}")
|
|
59
|
+
manifest = export_evidence_bundle(db, profile, args.destination)
|
|
60
|
+
_emit({
|
|
61
|
+
"bundle_id": manifest["bundle_id"],
|
|
62
|
+
"profile_id": profile,
|
|
63
|
+
"destination": str(Path(args.destination).resolve()),
|
|
64
|
+
"unresolved_source_links": manifest["unresolved_source_links"],
|
|
65
|
+
}, as_json=as_json)
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
if action == "import":
|
|
69
|
+
if not bool(getattr(args, "execute", False)):
|
|
70
|
+
print(
|
|
71
|
+
"Dry run only. Verify the bundle, then re-run with --execute; "
|
|
72
|
+
"replacement also requires --rollback-dir."
|
|
73
|
+
)
|
|
74
|
+
return
|
|
75
|
+
report = import_evidence_bundle(
|
|
76
|
+
_database(),
|
|
77
|
+
args.bundle,
|
|
78
|
+
target_profile_id=profile,
|
|
79
|
+
replace=bool(getattr(args, "replace", False)),
|
|
80
|
+
rollback_dir=getattr(args, "rollback_dir", None),
|
|
81
|
+
)
|
|
82
|
+
_emit({
|
|
83
|
+
"valid": report.valid,
|
|
84
|
+
"bundle_id": report.bundle_id,
|
|
85
|
+
"profile_id": profile,
|
|
86
|
+
"counts": report.counts,
|
|
87
|
+
}, as_json=as_json)
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
if action == "rebuild":
|
|
91
|
+
if not bool(getattr(args, "execute", False)):
|
|
92
|
+
print(
|
|
93
|
+
"Dry run only. Re-run with --execute to rebuild derived lexical state."
|
|
94
|
+
)
|
|
95
|
+
return
|
|
96
|
+
result = rebuild_derived_state(_database(), profile)
|
|
97
|
+
_emit({"profile_id": profile, **result}, as_json=as_json)
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
raise SystemExit("choose an evidence subcommand: export, verify, import, or rebuild")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
__all__ = ["cmd_evidence"]
|
|
@@ -24,10 +24,14 @@ from argparse import Namespace
|
|
|
24
24
|
from datetime import datetime, timezone
|
|
25
25
|
from pathlib import Path
|
|
26
26
|
|
|
27
|
+
from superlocalmemory.infra.data_root import DynamicStatePath
|
|
28
|
+
|
|
27
29
|
logger = logging.getLogger(__name__)
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
# Backward-compatible public paths that resolve at call time instead of
|
|
32
|
+
# freezing Path.home() during module import.
|
|
33
|
+
MEMORY_DIR = DynamicStatePath()
|
|
34
|
+
MEMORY_DB = DynamicStatePath("memory.db")
|
|
31
35
|
|
|
32
36
|
|
|
33
37
|
def cmd_ingest(args: Namespace) -> None:
|
|
@@ -278,7 +282,7 @@ def _write_tool_events(events: list[dict]) -> int:
|
|
|
278
282
|
v3.4.10: Preserves input_summary, output_summary, and project_path
|
|
279
283
|
from enriched sources (ECC observations, enriched hook).
|
|
280
284
|
"""
|
|
281
|
-
db_path = MEMORY_DB
|
|
285
|
+
db_path = Path(MEMORY_DB)
|
|
282
286
|
if not db_path.exists():
|
|
283
287
|
return 0
|
|
284
288
|
|
|
@@ -28,18 +28,16 @@ from superlocalmemory.core.config import CANONICAL_RECALL_LIMIT
|
|
|
28
28
|
|
|
29
29
|
_HELP_EPILOG = """\
|
|
30
30
|
operating modes:
|
|
31
|
-
Mode A Local Guardian —
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
Retrieval score: 74.8% on LoCoMo benchmark.
|
|
31
|
+
Mode A Local Guardian — No model-provider call in the core memory path.
|
|
32
|
+
Optional integrations, backup, and downloads have separate network
|
|
33
|
+
behavior; assess the complete deployment for privacy requirements.
|
|
35
34
|
|
|
36
35
|
Mode B Smart Local — Uses a local Ollama LLM for summarization and
|
|
37
|
-
enrichment.
|
|
38
|
-
Requires: ollama running
|
|
36
|
+
enrichment. The Ollama endpoint and enabled integrations determine
|
|
37
|
+
the data path. Requires: ollama running with a model pulled.
|
|
39
38
|
|
|
40
39
|
Mode C Full Power — Uses a cloud LLM (OpenAI, Anthropic, etc.) for
|
|
41
|
-
|
|
42
|
-
Retrieval score: 87.7% on LoCoMo benchmark.
|
|
40
|
+
provider-assisted enrichment and retrieval behavior.
|
|
43
41
|
|
|
44
42
|
quick start:
|
|
45
43
|
slm setup Interactive first-time setup
|
|
@@ -70,6 +68,34 @@ documentation:
|
|
|
70
68
|
"""
|
|
71
69
|
|
|
72
70
|
|
|
71
|
+
_NO_DAEMON_COMMANDS = {
|
|
72
|
+
"setup", "mode", "provider", "connect", "migrate", "mcp", "warmup", "hooks", "codex",
|
|
73
|
+
"config", "evolve", "db",
|
|
74
|
+
# v3.4.22 escape hatches — never auto-start the daemon on these.
|
|
75
|
+
"disable", "enable", "clear-cache", "reconfigure", "benchmark",
|
|
76
|
+
"rotate-token",
|
|
77
|
+
"evidence",
|
|
78
|
+
"diagnostics",
|
|
79
|
+
# LLD-06 — agents launched through wrap start the daemon on demand.
|
|
80
|
+
"wrap",
|
|
81
|
+
# V3.6 Optimize commands that are config read/write only.
|
|
82
|
+
"optimize", "cache", "compress", "help-optimize",
|
|
83
|
+
# Lifecycle orchestration must run before any global auto-start hook.
|
|
84
|
+
"serve", "restart",
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _command_requires_daemon(args: argparse.Namespace) -> bool:
|
|
89
|
+
"""Return whether global dispatch should ensure the daemon first.
|
|
90
|
+
|
|
91
|
+
All ``serve`` actions and ``restart`` own daemon inspection or mutation;
|
|
92
|
+
global dispatch must not race or duplicate those handlers.
|
|
93
|
+
"""
|
|
94
|
+
if args.command in _NO_DAEMON_COMMANDS:
|
|
95
|
+
return False
|
|
96
|
+
return True
|
|
97
|
+
|
|
98
|
+
|
|
73
99
|
def main() -> None:
|
|
74
100
|
"""Parse CLI arguments and dispatch to command handlers."""
|
|
75
101
|
# Fast path: hook invocations bypass argparse entirely (stdlib only, ~30ms)
|
|
@@ -78,11 +104,20 @@ def main() -> None:
|
|
|
78
104
|
handle_hook(sys.argv[2])
|
|
79
105
|
return
|
|
80
106
|
|
|
107
|
+
# Metadata inspection is activation-free. Package managers, users, and CI
|
|
108
|
+
# must be able to inspect a newly installed executable without creating an
|
|
109
|
+
# SLM data root, config, version marker, daemon, hooks, or models.
|
|
110
|
+
_is_metadata_cmd = (
|
|
111
|
+
len(sys.argv) == 1
|
|
112
|
+
or any(arg in {"-h", "--help"} for arg in sys.argv[1:])
|
|
113
|
+
or (len(sys.argv) == 2 and sys.argv[1] in {"-v", "--version"})
|
|
114
|
+
)
|
|
115
|
+
|
|
81
116
|
# WP-07: lazy first-run init — runs after hook/mcp fast-paths so stdout
|
|
82
117
|
# is never polluted on those paths (CRIT-3, MCP JSON-RPC purity).
|
|
83
118
|
# Guarded: any failure must not crash the CLI (AC4).
|
|
84
119
|
_is_mcp_cmd = len(sys.argv) >= 2 and sys.argv[1] == "mcp"
|
|
85
|
-
if not _is_mcp_cmd:
|
|
120
|
+
if not _is_mcp_cmd and not _is_metadata_cmd:
|
|
86
121
|
try:
|
|
87
122
|
from superlocalmemory.cli._lazy_init import _ensure_initialized
|
|
88
123
|
_ensure_initialized()
|
|
@@ -102,7 +137,7 @@ def main() -> None:
|
|
|
102
137
|
|
|
103
138
|
# One-time post-upgrade banner — silent for fresh installs and
|
|
104
139
|
# same-version runs. Guarded against I/O errors internally.
|
|
105
|
-
if not _is_mcp_stdio:
|
|
140
|
+
if not _is_mcp_stdio and not _is_metadata_cmd:
|
|
106
141
|
from superlocalmemory.cli.version_banner import check_and_emit_upgrade_banner
|
|
107
142
|
if check_and_emit_upgrade_banner(_ver):
|
|
108
143
|
# First post-upgrade invocation: apply the data-dir migration if
|
|
@@ -110,7 +145,6 @@ def main() -> None:
|
|
|
110
145
|
# we defer — the next daemon start picks it up.
|
|
111
146
|
try:
|
|
112
147
|
import logging as _logging
|
|
113
|
-
from pathlib import Path as _P
|
|
114
148
|
from superlocalmemory.migrations.v3_4_25_to_v3_4_26 import (
|
|
115
149
|
migrate_if_safe as _migrate_if_safe,
|
|
116
150
|
)
|
|
@@ -176,8 +210,13 @@ def main() -> None:
|
|
|
176
210
|
provider_p.add_argument(
|
|
177
211
|
"action", nargs="?", choices=["set"], help="Action",
|
|
178
212
|
)
|
|
213
|
+
provider_p.add_argument(
|
|
214
|
+
"provider", nargs="?",
|
|
215
|
+
choices=["openai", "anthropic", "azure", "ollama", "openrouter"],
|
|
216
|
+
help="Provider to configure; omit to use the interactive selector",
|
|
217
|
+
)
|
|
179
218
|
|
|
180
|
-
connect_p = sub.add_parser("connect", help="Auto-configure
|
|
219
|
+
connect_p = sub.add_parser("connect", help="Auto-configure a supported IDE integration")
|
|
181
220
|
connect_p.add_argument("ide", nargs="?", help="Specific IDE to configure (e.g. cursor, codex, continue)")
|
|
182
221
|
connect_p.add_argument(
|
|
183
222
|
"--list", action="store_true", help="List all supported IDEs",
|
|
@@ -225,6 +264,17 @@ def main() -> None:
|
|
|
225
264
|
"--dry-run", action="store_true",
|
|
226
265
|
help="Report what would change without applying",
|
|
227
266
|
)
|
|
267
|
+
db_scale_p = db_sub.add_parser(
|
|
268
|
+
"scale",
|
|
269
|
+
help="Safely stage and promote optional CozoDB/LanceDB projections",
|
|
270
|
+
)
|
|
271
|
+
db_scale_p.add_argument(
|
|
272
|
+
"scale_action",
|
|
273
|
+
choices=("status", "prepare", "verify", "promote", "rollback"),
|
|
274
|
+
help="Lifecycle action. Prepare and verify never mutate active projection paths.",
|
|
275
|
+
)
|
|
276
|
+
db_scale_p.add_argument("--stage-id", help="Stage identifier required by verify/promote")
|
|
277
|
+
db_scale_p.add_argument("--backup-id", help="Backup identifier required by rollback")
|
|
228
278
|
|
|
229
279
|
# -- Memory Operations ---------------------------------------------
|
|
230
280
|
remember_p = sub.add_parser("remember", help="Store a memory (extracts facts, builds graph)")
|
|
@@ -247,7 +297,7 @@ def main() -> None:
|
|
|
247
297
|
|
|
248
298
|
# v3.6.12 (parity-3): `search` is an alias of `recall` so the CLI has the
|
|
249
299
|
# same search verb the MCP exposes (handlers dict maps both to cmd_recall).
|
|
250
|
-
recall_p = sub.add_parser("recall", aliases=["search"], help="
|
|
300
|
+
recall_p = sub.add_parser("recall", aliases=["search"], help="Multi-channel memory retrieval")
|
|
251
301
|
recall_p.add_argument("query", help="Search query")
|
|
252
302
|
recall_p.add_argument(
|
|
253
303
|
"--limit", type=int, default=CANONICAL_RECALL_LIMIT,
|
|
@@ -256,8 +306,8 @@ def main() -> None:
|
|
|
256
306
|
recall_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
|
|
257
307
|
recall_p.add_argument(
|
|
258
308
|
"--fast", action="store_true",
|
|
259
|
-
help="Skip
|
|
260
|
-
"
|
|
309
|
+
help="Skip spreading activation and remote agentic verification for a "
|
|
310
|
+
"latency-bounded response. Other configured retrieval channels still run. "
|
|
261
311
|
"Use when you need recall before a tool call (e.g. before WebSearch).",
|
|
262
312
|
)
|
|
263
313
|
# v3.6.15: shared memory is opt-in. Unset (None) → resolve the configured
|
|
@@ -386,7 +436,7 @@ def main() -> None:
|
|
|
386
436
|
profile_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
|
|
387
437
|
|
|
388
438
|
# -- Active Memory (V3.1) ------------------------------------------
|
|
389
|
-
hooks_p = sub.add_parser("hooks", help="Manage Claude Code
|
|
439
|
+
hooks_p = sub.add_parser("hooks", help="Manage additive Claude Code or Codex hooks")
|
|
390
440
|
hooks_p.add_argument(
|
|
391
441
|
"action", nargs="?", default="status",
|
|
392
442
|
choices=["install", "remove", "status"], help="Action (default: status)",
|
|
@@ -395,6 +445,18 @@ def main() -> None:
|
|
|
395
445
|
"--gate", action="store_true",
|
|
396
446
|
help="Enable PreToolUse gate (experimental — blocks tools until session_init)",
|
|
397
447
|
)
|
|
448
|
+
hooks_p.add_argument(
|
|
449
|
+
"--agent", choices=["claude", "codex"], default="claude",
|
|
450
|
+
help="Target agent (default: claude)",
|
|
451
|
+
)
|
|
452
|
+
hooks_p.add_argument(
|
|
453
|
+
"--dry-run", action="store_true",
|
|
454
|
+
help="Show whether the additive change is valid without writing files",
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
codex_p = sub.add_parser("codex", help="Install or inspect explicit Codex SLM add-ons")
|
|
458
|
+
codex_p.add_argument("action", nargs="?", default="status", choices=["install", "remove", "status"])
|
|
459
|
+
codex_p.add_argument("--dry-run", action="store_true", help="Validate without writing user files")
|
|
398
460
|
|
|
399
461
|
ctx_p = sub.add_parser("session-context", help="Print session context (for hooks)")
|
|
400
462
|
ctx_p.add_argument("query", nargs="?", default="", help="Optional context query")
|
|
@@ -568,6 +630,55 @@ def main() -> None:
|
|
|
568
630
|
help="Emit JSON result instead of human-readable summary",
|
|
569
631
|
)
|
|
570
632
|
|
|
633
|
+
evidence_p = sub.add_parser(
|
|
634
|
+
"evidence",
|
|
635
|
+
help="Export, verify, import, or rebuild versioned memory evidence",
|
|
636
|
+
)
|
|
637
|
+
evidence_sub = evidence_p.add_subparsers(
|
|
638
|
+
dest="evidence_command", title="evidence subcommands",
|
|
639
|
+
)
|
|
640
|
+
evidence_export = evidence_sub.add_parser(
|
|
641
|
+
"export", help="Write a deterministic checksummed JSONL bundle",
|
|
642
|
+
)
|
|
643
|
+
evidence_export.add_argument("destination")
|
|
644
|
+
evidence_export.add_argument("--profile", default="default")
|
|
645
|
+
evidence_export.add_argument("--json", action="store_true")
|
|
646
|
+
evidence_verify = evidence_sub.add_parser(
|
|
647
|
+
"verify", help="Verify checksums and source reconciliation",
|
|
648
|
+
)
|
|
649
|
+
evidence_verify.add_argument("bundle")
|
|
650
|
+
evidence_verify.add_argument("--json", action="store_true")
|
|
651
|
+
evidence_import = evidence_sub.add_parser(
|
|
652
|
+
"import", help="Import relational truth (dry-run unless --execute)",
|
|
653
|
+
)
|
|
654
|
+
evidence_import.add_argument("bundle")
|
|
655
|
+
evidence_import.add_argument("--profile", default="default")
|
|
656
|
+
evidence_import.add_argument("--replace", action="store_true")
|
|
657
|
+
evidence_import.add_argument("--rollback-dir", default=None)
|
|
658
|
+
evidence_import.add_argument("--execute", action="store_true")
|
|
659
|
+
evidence_import.add_argument("--json", action="store_true")
|
|
660
|
+
evidence_rebuild = evidence_sub.add_parser(
|
|
661
|
+
"rebuild", help="Rebuild derived lexical state (dry-run unless --execute)",
|
|
662
|
+
)
|
|
663
|
+
evidence_rebuild.add_argument("--profile", default="default")
|
|
664
|
+
evidence_rebuild.add_argument("--execute", action="store_true")
|
|
665
|
+
evidence_rebuild.add_argument("--json", action="store_true")
|
|
666
|
+
|
|
667
|
+
diagnostics_p = sub.add_parser(
|
|
668
|
+
"diagnostics",
|
|
669
|
+
help="Export bounded local operational aggregates (manual only)",
|
|
670
|
+
)
|
|
671
|
+
diagnostics_sub = diagnostics_p.add_subparsers(
|
|
672
|
+
dest="diagnostics_command", title="diagnostics subcommands",
|
|
673
|
+
)
|
|
674
|
+
diagnostics_export = diagnostics_sub.add_parser(
|
|
675
|
+
"export", help="Write a deterministic content-free JSON report",
|
|
676
|
+
)
|
|
677
|
+
diagnostics_export.add_argument("destination")
|
|
678
|
+
diagnostics_export.add_argument(
|
|
679
|
+
"--json", action="store_true", help="Output structured JSON",
|
|
680
|
+
)
|
|
681
|
+
|
|
571
682
|
# S-M07: install-token rotation.
|
|
572
683
|
sub.add_parser(
|
|
573
684
|
"rotate-token",
|
|
@@ -676,21 +787,7 @@ def main() -> None:
|
|
|
676
787
|
# V3.4.4: Auto-start daemon for all commands that need it.
|
|
677
788
|
# SLM is always-on — close laptop, reboot, crash: daemon auto-recovers.
|
|
678
789
|
# Cross-platform: macOS + Windows + Linux.
|
|
679
|
-
|
|
680
|
-
"setup", "mode", "provider", "connect", "migrate", "mcp", "warmup",
|
|
681
|
-
"config", "evolve", "db",
|
|
682
|
-
# v3.4.22 escape hatches — never auto-start the daemon on these.
|
|
683
|
-
"disable", "enable", "clear-cache", "reconfigure", "benchmark",
|
|
684
|
-
"rotate-token",
|
|
685
|
-
# LLD-06 — `slm wrap` may launch the agent binary directly without
|
|
686
|
-
# needing the daemon running (the agent will start the daemon on
|
|
687
|
-
# first LLM call, or the wrap command can be --dry-run).
|
|
688
|
-
"wrap",
|
|
689
|
-
# V3.6 Optimize commands that are config read/write only (no daemon needed)
|
|
690
|
-
"optimize", "cache", "compress", "help-optimize",
|
|
691
|
-
# NOTE: "proxy" NOT here — proxy needs daemon running
|
|
692
|
-
}
|
|
693
|
-
if args.command not in _NO_DAEMON_COMMANDS:
|
|
790
|
+
if _command_requires_daemon(args):
|
|
694
791
|
try:
|
|
695
792
|
from superlocalmemory.cli.daemon import ensure_daemon
|
|
696
793
|
ensure_daemon() # Starts daemon if not running; no-op if already up
|
|
@@ -18,27 +18,27 @@ Uses a separate `pending.db` file — never touches memory.db directly.
|
|
|
18
18
|
Stdlib only — no SLM imports (must be fast).
|
|
19
19
|
|
|
20
20
|
Part of Qualixar | Author: Varun Pratap Bhardwaj
|
|
21
|
-
License:
|
|
21
|
+
License: AGPL-3.0-or-later
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
24
|
from __future__ import annotations
|
|
25
25
|
|
|
26
26
|
import json
|
|
27
|
-
import os
|
|
28
27
|
import sqlite3
|
|
29
28
|
import time
|
|
30
29
|
from pathlib import Path
|
|
31
30
|
|
|
32
31
|
|
|
33
32
|
def _default_dir() -> Path:
|
|
34
|
-
"""
|
|
35
|
-
|
|
33
|
+
"""Resolve the pending queue inside the canonical process namespace."""
|
|
34
|
+
from superlocalmemory.infra.data_root import canonical_data_root
|
|
35
|
+
return canonical_data_root()
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
_PENDING_DB = "pending.db"
|
|
39
39
|
_MAX_RETRIES = 3
|
|
40
40
|
_STUCK_DAYS = 7
|
|
41
|
-
|
|
41
|
+
_MAX_RETRY_DELAY_SECONDS = 3600
|
|
42
42
|
|
|
43
43
|
_SCHEMA = """
|
|
44
44
|
CREATE TABLE IF NOT EXISTS pending_memories (
|
|
@@ -49,7 +49,8 @@ CREATE TABLE IF NOT EXISTS pending_memories (
|
|
|
49
49
|
created_at TEXT NOT NULL,
|
|
50
50
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
51
51
|
error TEXT DEFAULT NULL,
|
|
52
|
-
retry_count INTEGER DEFAULT 0
|
|
52
|
+
retry_count INTEGER DEFAULT 0,
|
|
53
|
+
next_retry_at REAL DEFAULT 0
|
|
53
54
|
);
|
|
54
55
|
"""
|
|
55
56
|
|
|
@@ -62,6 +63,24 @@ def _get_db(base_dir: Path | None = None) -> sqlite3.Connection:
|
|
|
62
63
|
conn = sqlite3.connect(str(db_path), timeout=5)
|
|
63
64
|
conn.execute("PRAGMA journal_mode=WAL")
|
|
64
65
|
conn.execute(_SCHEMA)
|
|
66
|
+
columns = {
|
|
67
|
+
row[1]
|
|
68
|
+
for row in conn.execute("PRAGMA table_info(pending_memories)").fetchall()
|
|
69
|
+
}
|
|
70
|
+
if "next_retry_at" not in columns:
|
|
71
|
+
conn.execute(
|
|
72
|
+
"ALTER TABLE pending_memories ADD COLUMN "
|
|
73
|
+
"next_retry_at REAL DEFAULT 0"
|
|
74
|
+
)
|
|
75
|
+
conn.commit()
|
|
76
|
+
# Pre-V3.7 rows were terminally hidden after three failures. Restore them
|
|
77
|
+
# to the retry queue; M018 makes replay idempotent and no raw evidence may
|
|
78
|
+
# remain stranded solely because an older version exhausted its counter.
|
|
79
|
+
conn.execute(
|
|
80
|
+
"UPDATE pending_memories SET status='pending', next_retry_at=0 "
|
|
81
|
+
"WHERE status='failed'"
|
|
82
|
+
)
|
|
83
|
+
conn.commit()
|
|
65
84
|
return conn
|
|
66
85
|
|
|
67
86
|
|
|
@@ -96,8 +115,9 @@ def get_pending(base_dir: Path | None = None, limit: int = 50) -> list[dict]:
|
|
|
96
115
|
rows = conn.execute(
|
|
97
116
|
"SELECT id, content, tags, metadata, created_at, retry_count "
|
|
98
117
|
"FROM pending_memories WHERE status = 'pending' "
|
|
118
|
+
"AND COALESCE(next_retry_at, 0) <= ? "
|
|
99
119
|
"ORDER BY id ASC LIMIT ?",
|
|
100
|
-
(limit
|
|
120
|
+
(time.time(), limit),
|
|
101
121
|
).fetchall()
|
|
102
122
|
return [
|
|
103
123
|
{"id": r[0], "content": r[1], "tags": r[2], "metadata": r[3],
|
|
@@ -124,20 +144,29 @@ def mark_done(row_id: int, base_dir: Path | None = None) -> None:
|
|
|
124
144
|
def mark_failed(row_id: int, error: str, base_dir: Path | None = None) -> None:
|
|
125
145
|
"""Mark a pending memory as failed with error message.
|
|
126
146
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
permanently lost 18 memories between April 15-26, 2026 to transient errors.
|
|
147
|
+
Unprocessed evidence is never deleted or terminally hidden. Failures stay
|
|
148
|
+
pending with bounded exponential backoff; M018 idempotency makes repeated
|
|
149
|
+
replay safe once the canonical operation has been created.
|
|
131
150
|
"""
|
|
132
151
|
conn = _get_db(base_dir)
|
|
133
152
|
try:
|
|
134
|
-
|
|
153
|
+
row = conn.execute(
|
|
154
|
+
"SELECT retry_count FROM pending_memories WHERE id=?",
|
|
155
|
+
(row_id,),
|
|
156
|
+
).fetchone()
|
|
157
|
+
if row is None:
|
|
158
|
+
return
|
|
159
|
+
next_count = int(row[0] or 0) + 1
|
|
160
|
+
delay = 0 if next_count == 1 else min(
|
|
161
|
+
2 ** min(next_count - 1, 12),
|
|
162
|
+
_MAX_RETRY_DELAY_SECONDS,
|
|
163
|
+
)
|
|
135
164
|
conn.execute(
|
|
136
165
|
"UPDATE pending_memories SET error = ?, "
|
|
137
166
|
"retry_count = retry_count + 1, "
|
|
138
|
-
"status =
|
|
167
|
+
"status = 'pending', next_retry_at = ? "
|
|
139
168
|
"WHERE id = ?",
|
|
140
|
-
(error,
|
|
169
|
+
(error, time.time() + delay, row_id),
|
|
141
170
|
)
|
|
142
171
|
conn.commit()
|
|
143
172
|
finally:
|
|
@@ -179,11 +208,8 @@ def cleanup_done(days: int = 7, base_dir: Path | None = None) -> int:
|
|
|
179
208
|
def cleanup_stale(base_dir: Path | None = None) -> dict[str, int]:
|
|
180
209
|
"""Sweep stale rows from pending.db. Runs periodically from the daemon.
|
|
181
210
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
- `failed` rows that exceeded max retries (moved to dead-letter via deletion)
|
|
185
|
-
- `pending` rows stuck more than 7 days (test pollution, crashed workers)
|
|
186
|
-
- Everything older than 30 days regardless of status (hard cap)
|
|
211
|
+
Deletes only completed receipts. Pending and failed raw evidence is retained
|
|
212
|
+
regardless of age so maintenance can never become a data-loss path.
|
|
187
213
|
"""
|
|
188
214
|
conn = _get_db(base_dir)
|
|
189
215
|
try:
|
|
@@ -192,28 +218,18 @@ def cleanup_stale(base_dir: Path | None = None) -> dict[str, int]:
|
|
|
192
218
|
"AND created_at < datetime('now', ?)",
|
|
193
219
|
(f"-{_STUCK_DAYS} days",),
|
|
194
220
|
).rowcount
|
|
195
|
-
|
|
196
|
-
"
|
|
197
|
-
"
|
|
198
|
-
|
|
199
|
-
).rowcount
|
|
200
|
-
stuck = conn.execute(
|
|
201
|
-
"DELETE FROM pending_memories WHERE status = 'pending' "
|
|
202
|
-
"AND created_at < datetime('now', ?)",
|
|
203
|
-
(f"-{_STUCK_DAYS} days",),
|
|
204
|
-
).rowcount
|
|
205
|
-
hard_cap = conn.execute(
|
|
206
|
-
"DELETE FROM pending_memories "
|
|
207
|
-
"WHERE created_at < datetime('now', ?)",
|
|
208
|
-
(f"-{_DEAD_LETTER_DAYS} days",),
|
|
209
|
-
).rowcount
|
|
221
|
+
retained = conn.execute(
|
|
222
|
+
"SELECT COUNT(*) FROM pending_memories "
|
|
223
|
+
"WHERE status != 'done'"
|
|
224
|
+
).fetchone()[0]
|
|
210
225
|
conn.commit()
|
|
211
226
|
return {
|
|
212
227
|
"done": done,
|
|
213
|
-
"failed_over_retries":
|
|
214
|
-
"stuck_pending":
|
|
215
|
-
"hard_cap_expired":
|
|
216
|
-
"
|
|
228
|
+
"failed_over_retries": 0,
|
|
229
|
+
"stuck_pending": 0,
|
|
230
|
+
"hard_cap_expired": 0,
|
|
231
|
+
"retained_unprocessed": int(retained),
|
|
232
|
+
"total": done,
|
|
217
233
|
}
|
|
218
234
|
finally:
|
|
219
235
|
conn.close()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""CLI surface for the staged Scale Engine lifecycle."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
from argparse import Namespace
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def cmd_db_scale(args: Namespace) -> int:
|
|
9
|
+
from superlocalmemory.core.config import SLMConfig
|
|
10
|
+
from superlocalmemory.core.scale_engine import ScaleEngineError, ScaleEngineManager
|
|
11
|
+
|
|
12
|
+
manager = ScaleEngineManager(SLMConfig.load())
|
|
13
|
+
action = args.scale_action
|
|
14
|
+
try:
|
|
15
|
+
if action == "status":
|
|
16
|
+
result = manager.status()
|
|
17
|
+
elif action == "prepare":
|
|
18
|
+
result = manager.prepare()
|
|
19
|
+
elif action == "verify":
|
|
20
|
+
if not args.stage_id:
|
|
21
|
+
raise ScaleEngineError("verify requires --stage-id (see `slm db scale status`)")
|
|
22
|
+
result = manager.verify(args.stage_id)
|
|
23
|
+
elif action == "promote":
|
|
24
|
+
if not args.stage_id:
|
|
25
|
+
raise ScaleEngineError("promote requires --stage-id (see `slm db scale status`)")
|
|
26
|
+
result = manager.promote(args.stage_id)
|
|
27
|
+
elif action == "rollback":
|
|
28
|
+
if not args.backup_id:
|
|
29
|
+
raise ScaleEngineError("rollback requires --backup-id (see `slm db scale status`)")
|
|
30
|
+
result = manager.rollback(args.backup_id)
|
|
31
|
+
else:
|
|
32
|
+
raise ScaleEngineError(f"unknown Scale Engine action: {action}")
|
|
33
|
+
except ScaleEngineError as exc:
|
|
34
|
+
print(f"Scale Engine: {exc}")
|
|
35
|
+
return 1
|
|
36
|
+
print(json.dumps(result, indent=2, sort_keys=True))
|
|
37
|
+
return 0
|