superlocalmemory 3.8.5 → 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 +34 -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 -4
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/commands.py +139 -404
- 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
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
# Licensed under AGPL-3.0-or-later - see LICENSE file
|
|
3
|
+
|
|
4
|
+
"""Production encryption codec for durable remember admission commands."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import stat
|
|
10
|
+
import time
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
14
|
+
|
|
15
|
+
from superlocalmemory.infra.data_root import state_path
|
|
16
|
+
|
|
17
|
+
_KEY_BYTES = 32
|
|
18
|
+
_NONCE_BYTES = 12
|
|
19
|
+
_FORMAT_VERSION = b"\x01"
|
|
20
|
+
_ASSOCIATED_DATA = b"superlocalmemory:remember-admission:v1"
|
|
21
|
+
_KEY_READ_ATTEMPTS = 100
|
|
22
|
+
_KEY_READ_RETRY_SECONDS = 0.005
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class AdmissionKeyError(RuntimeError):
|
|
26
|
+
"""The machine-local admission key cannot be created or loaded safely."""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class MachineKeyCommandCodec:
|
|
30
|
+
"""AES-256-GCM codec backed by a machine-local, permission-bound key.
|
|
31
|
+
|
|
32
|
+
The key is intentionally separate from cache encryption keys. Exclusive
|
|
33
|
+
creation makes concurrent daemon startup converge on one key, while the
|
|
34
|
+
format byte and AEAD associated data prevent cross-protocol ciphertext use.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, key_path: str | Path | None = None) -> None:
|
|
38
|
+
self.key_path = Path(key_path) if key_path is not None else state_path(
|
|
39
|
+
"admission-key.bin"
|
|
40
|
+
)
|
|
41
|
+
self._key = _load_or_create_key(self.key_path)
|
|
42
|
+
|
|
43
|
+
def encrypt(self, plaintext: bytes) -> bytes:
|
|
44
|
+
if not isinstance(plaintext, bytes):
|
|
45
|
+
raise TypeError("plaintext must be bytes")
|
|
46
|
+
nonce = os.urandom(_NONCE_BYTES)
|
|
47
|
+
ciphertext = AESGCM(self._key).encrypt(nonce, plaintext, _ASSOCIATED_DATA)
|
|
48
|
+
return _FORMAT_VERSION + nonce + ciphertext
|
|
49
|
+
|
|
50
|
+
def decrypt(self, ciphertext: bytes) -> bytes:
|
|
51
|
+
if not isinstance(ciphertext, bytes):
|
|
52
|
+
raise TypeError("ciphertext must be bytes")
|
|
53
|
+
minimum = len(_FORMAT_VERSION) + _NONCE_BYTES + 16
|
|
54
|
+
if len(ciphertext) < minimum or ciphertext[:1] != _FORMAT_VERSION:
|
|
55
|
+
raise ValueError("unsupported admission ciphertext format")
|
|
56
|
+
nonce = ciphertext[1 : 1 + _NONCE_BYTES]
|
|
57
|
+
payload = ciphertext[1 + _NONCE_BYTES :]
|
|
58
|
+
return AESGCM(self._key).decrypt(nonce, payload, _ASSOCIATED_DATA)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _load_or_create_key(path: Path) -> bytes:
|
|
62
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
63
|
+
if os.name != "nt":
|
|
64
|
+
os.chmod(path.parent, 0o700)
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
fd = os.open(
|
|
68
|
+
path,
|
|
69
|
+
os.O_WRONLY
|
|
70
|
+
| os.O_CREAT
|
|
71
|
+
| os.O_EXCL
|
|
72
|
+
| getattr(os, "O_BINARY", 0),
|
|
73
|
+
0o600,
|
|
74
|
+
)
|
|
75
|
+
except FileExistsError:
|
|
76
|
+
fd = -1
|
|
77
|
+
else:
|
|
78
|
+
try:
|
|
79
|
+
key = os.urandom(_KEY_BYTES)
|
|
80
|
+
view = memoryview(key)
|
|
81
|
+
written = 0
|
|
82
|
+
while written < _KEY_BYTES:
|
|
83
|
+
count = os.write(fd, view[written:])
|
|
84
|
+
if count <= 0:
|
|
85
|
+
raise AdmissionKeyError("admission key write was incomplete")
|
|
86
|
+
written += count
|
|
87
|
+
os.fsync(fd)
|
|
88
|
+
except BaseException:
|
|
89
|
+
try:
|
|
90
|
+
path.unlink()
|
|
91
|
+
except OSError:
|
|
92
|
+
pass
|
|
93
|
+
raise
|
|
94
|
+
finally:
|
|
95
|
+
os.close(fd)
|
|
96
|
+
|
|
97
|
+
for attempt in range(_KEY_READ_ATTEMPTS):
|
|
98
|
+
try:
|
|
99
|
+
info = path.lstat()
|
|
100
|
+
except OSError as exc:
|
|
101
|
+
raise AdmissionKeyError("admission key is unavailable") from exc
|
|
102
|
+
if stat.S_ISLNK(info.st_mode) or not stat.S_ISREG(info.st_mode):
|
|
103
|
+
raise AdmissionKeyError("admission key path must be a regular file")
|
|
104
|
+
if os.name != "nt":
|
|
105
|
+
if info.st_uid != os.getuid():
|
|
106
|
+
raise AdmissionKeyError("admission key is not owned by the current user")
|
|
107
|
+
os.chmod(path, 0o600)
|
|
108
|
+
try:
|
|
109
|
+
key = path.read_bytes()
|
|
110
|
+
except OSError as exc:
|
|
111
|
+
raise AdmissionKeyError("admission key cannot be read") from exc
|
|
112
|
+
if len(key) == _KEY_BYTES:
|
|
113
|
+
return key
|
|
114
|
+
# A process that lost O_EXCL may observe the winning creator between
|
|
115
|
+
# file creation and its final fsync/close. Wait only for that bounded
|
|
116
|
+
# window; a persistently truncated or oversized key still fails closed.
|
|
117
|
+
if attempt + 1 < _KEY_READ_ATTEMPTS:
|
|
118
|
+
time.sleep(_KEY_READ_RETRY_SECONDS)
|
|
119
|
+
raise AdmissionKeyError("admission key has an invalid length")
|