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.
Files changed (125) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/README.md +3 -2
  3. package/package.json +1 -1
  4. package/plugin/.claude-plugin/plugin.json +1 -1
  5. package/plugin/CLAUDE.md +3 -3
  6. package/plugin/agents/slm-governance-advisor.md +1 -1
  7. package/plugin/agents/slm-loop-runner.md +1 -1
  8. package/plugin/agents/slm-memory-advisor.md +1 -1
  9. package/plugin/agents/slm-optimize-advisor.md +1 -1
  10. package/plugin/requirements.txt +1 -1
  11. package/plugin/skills/slm-cache/SKILL.md +1 -1
  12. package/plugin/skills/slm-compress/SKILL.md +1 -1
  13. package/plugin/skills/slm-governance/SKILL.md +1 -1
  14. package/plugin/skills/slm-graph/SKILL.md +1 -1
  15. package/plugin/skills/slm-loop/SKILL.md +1 -1
  16. package/plugin/skills/slm-mesh/SKILL.md +1 -1
  17. package/plugin/skills/slm-profile/SKILL.md +1 -1
  18. package/plugin/skills/slm-recall/SKILL.md +1 -1
  19. package/plugin/skills/slm-remember/SKILL.md +1 -1
  20. package/plugin/skills/slm-scope/SKILL.md +1 -1
  21. package/plugin/skills/slm-session/SKILL.md +1 -1
  22. package/plugin/skills/slm-status/SKILL.md +1 -1
  23. package/plugin-src/rules/AGENTS.md +1 -1
  24. package/plugin-src/skills/slm-cache/SKILL.md +1 -1
  25. package/plugin-src/skills/slm-compress/SKILL.md +1 -1
  26. package/plugin-src/skills/slm-graph/SKILL.md +1 -1
  27. package/plugin-src/skills/slm-recall/SKILL.md +1 -1
  28. package/plugin-src/skills/slm-remember/SKILL.md +1 -1
  29. package/plugin-src/skills/slm-session/SKILL.md +1 -1
  30. package/plugin-src/skills/slm-status/SKILL.md +1 -1
  31. package/pyproject.toml +9 -4
  32. package/src/superlocalmemory/__init__.py +1 -1
  33. package/src/superlocalmemory/access/rbac.py +68 -76
  34. package/src/superlocalmemory/cli/commands.py +158 -404
  35. package/src/superlocalmemory/cli/ingest_cmd.py +11 -1
  36. package/src/superlocalmemory/cli/main.py +30 -0
  37. package/src/superlocalmemory/cli/pending_store.py +39 -14
  38. package/src/superlocalmemory/core/backend_orchestrator.py +93 -0
  39. package/src/superlocalmemory/core/component_registry.py +4 -2
  40. package/src/superlocalmemory/core/config.py +78 -0
  41. package/src/superlocalmemory/core/consolidation_engine.py +79 -73
  42. package/src/superlocalmemory/core/embeddings.py +33 -6
  43. package/src/superlocalmemory/core/engine.py +186 -60
  44. package/src/superlocalmemory/core/engine_ingestion.py +150 -63
  45. package/src/superlocalmemory/core/fact_consolidator.py +148 -30
  46. package/src/superlocalmemory/core/graph_pruner.py +436 -39
  47. package/src/superlocalmemory/core/ingestion_command.py +273 -32
  48. package/src/superlocalmemory/core/maintenance_scheduler.py +61 -1
  49. package/src/superlocalmemory/core/mutations.py +32 -10
  50. package/src/superlocalmemory/core/recall_pipeline.py +111 -74
  51. package/src/superlocalmemory/core/registry.py +5 -1
  52. package/src/superlocalmemory/core/remember_admission.py +152 -0
  53. package/src/superlocalmemory/core/remember_runtime.py +712 -0
  54. package/src/superlocalmemory/core/remote_mode.py +3 -1
  55. package/src/superlocalmemory/core/scale_engine.py +41 -18
  56. package/src/superlocalmemory/core/store_pipeline.py +18 -4
  57. package/src/superlocalmemory/encoding/entity_resolver.py +18 -11
  58. package/src/superlocalmemory/graph/cozo_backend.py +5 -5
  59. package/src/superlocalmemory/hooks/_outcome_common.py +9 -2
  60. package/src/superlocalmemory/hooks/adapter_base.py +58 -44
  61. package/src/superlocalmemory/hooks/ide_connector.py +26 -8
  62. package/src/superlocalmemory/hooks/portable_kit.py +105 -9
  63. package/src/superlocalmemory/hooks/prewarm_auth.py +21 -2
  64. package/src/superlocalmemory/infra/auth_middleware.py +3 -1
  65. package/src/superlocalmemory/infra/cloud_backup.py +26 -27
  66. package/src/superlocalmemory/infra/event_bus.py +250 -88
  67. package/src/superlocalmemory/learning/bandit.py +50 -1
  68. package/src/superlocalmemory/learning/consolidation_cycle.py +33 -16
  69. package/src/superlocalmemory/learning/entity_compiler.py +148 -132
  70. package/src/superlocalmemory/learning/memory_merge.py +97 -82
  71. package/src/superlocalmemory/learning/reward_archive.py +98 -90
  72. package/src/superlocalmemory/learning/reward_boost.py +40 -30
  73. package/src/superlocalmemory/learning/source_quality.py +38 -35
  74. package/src/superlocalmemory/mcp/_daemon_proxy.py +38 -15
  75. package/src/superlocalmemory/mcp/http_transport.py +335 -3
  76. package/src/superlocalmemory/mcp/tools_active.py +4 -41
  77. package/src/superlocalmemory/mcp/tools_core.py +26 -87
  78. package/src/superlocalmemory/mcp/tools_evolution.py +5 -10
  79. package/src/superlocalmemory/optimize/proxy/capture.py +196 -8
  80. package/src/superlocalmemory/retrieval/engine.py +15 -4
  81. package/src/superlocalmemory/retrieval/entity_channel.py +25 -1
  82. package/src/superlocalmemory/retrieval/reranker.py +130 -22
  83. package/src/superlocalmemory/retrieval/spreading_activation.py +20 -12
  84. package/src/superlocalmemory/retrieval/vector_store.py +84 -69
  85. package/src/superlocalmemory/server/loopback.py +85 -0
  86. package/src/superlocalmemory/server/origin.py +9 -4
  87. package/src/superlocalmemory/server/profile_runtime.py +14 -0
  88. package/src/superlocalmemory/server/routes/abstraction.py +2 -4
  89. package/src/superlocalmemory/server/routes/agents.py +3 -5
  90. package/src/superlocalmemory/server/routes/backup.py +6 -2
  91. package/src/superlocalmemory/server/routes/behavioral.py +11 -25
  92. package/src/superlocalmemory/server/routes/brain.py +6 -9
  93. package/src/superlocalmemory/server/routes/compliance.py +20 -23
  94. package/src/superlocalmemory/server/routes/config_api.py +83 -0
  95. package/src/superlocalmemory/server/routes/entity.py +3 -7
  96. package/src/superlocalmemory/server/routes/evolution.py +3 -5
  97. package/src/superlocalmemory/server/routes/helpers.py +57 -25
  98. package/src/superlocalmemory/server/routes/insights.py +2 -4
  99. package/src/superlocalmemory/server/routes/learning.py +2 -5
  100. package/src/superlocalmemory/server/routes/lifecycle.py +2 -4
  101. package/src/superlocalmemory/server/routes/memories.py +119 -98
  102. package/src/superlocalmemory/server/routes/mesh.py +7 -2
  103. package/src/superlocalmemory/server/routes/profiles.py +20 -21
  104. package/src/superlocalmemory/server/routes/rbac.py +0 -1
  105. package/src/superlocalmemory/server/routes/tiers.py +28 -35
  106. package/src/superlocalmemory/server/routes/timeline.py +2 -4
  107. package/src/superlocalmemory/server/routes/v3_api.py +85 -93
  108. package/src/superlocalmemory/server/unified_daemon.py +400 -140
  109. package/src/superlocalmemory/server/write_identity.py +22 -4
  110. package/src/superlocalmemory/storage/admission_codec.py +119 -0
  111. package/src/superlocalmemory/storage/admission_journal.py +728 -0
  112. package/src/superlocalmemory/storage/database.py +168 -19
  113. package/src/superlocalmemory/storage/deferred_writes.py +209 -0
  114. package/src/superlocalmemory/storage/embedding_migrator.py +19 -0
  115. package/src/superlocalmemory/storage/memory_write.py +115 -0
  116. package/src/superlocalmemory/storage/migration_runner.py +44 -0
  117. package/src/superlocalmemory/storage/migrations/M028_fact_entity_associations.py +113 -78
  118. package/src/superlocalmemory/storage/migrations/M031_dead_letter_operations.py +80 -0
  119. package/src/superlocalmemory/storage/migrations/M032_write_coordinator_admission.py +188 -0
  120. package/src/superlocalmemory/storage/read_connection.py +115 -0
  121. package/src/superlocalmemory/storage/write_coordinator.py +756 -0
  122. package/src/superlocalmemory/storage/write_lock.py +88 -0
  123. package/src/superlocalmemory/ui/index.html +1 -1
  124. package/src/superlocalmemory/ui/js/auto-settings.js +14 -1
  125. package/src/superlocalmemory/ui/js/od-settings.js +9 -3
@@ -18,6 +18,8 @@ from typing import Any
18
18
 
19
19
  from fastapi import HTTPException, Request
20
20
 
21
+ from superlocalmemory.server.loopback import is_loopback as _is_loopback_host
22
+
21
23
 
22
24
  # H-04 (3.7.9): the test-client auth bypass must be impossible in a real daemon
23
25
  # even if SLM_TEST_ISOLATION=1 leaks into a production environment. Anchor it to
@@ -89,7 +91,7 @@ def require_write_actor(
89
91
  # missing peer address (e.g. behind a proxy that strips it) must not be
90
92
  # trusted just because an install token is presented. require_http_mutation_actor
91
93
  # already excludes "" from its loopback set; keep the two paths consistent.
92
- if is_test_client or client_host in ("127.0.0.1", "::1", "localhost"):
94
+ if is_test_client or _is_loopback_host(client_host):
93
95
  from superlocalmemory.core.engine_ingestion import local_trusted_actor_id
94
96
 
95
97
  return local_trusted_actor_id(actor_kind)
@@ -103,7 +105,15 @@ def require_write_actor(
103
105
  ).hexdigest()
104
106
  return f"api-key:{actor_kind}:{fingerprint}"
105
107
 
106
- raise HTTPException(403, detail="Authenticated write capability required")
108
+ raise HTTPException(
109
+ 403,
110
+ detail=(
111
+ "Write rejected: this origin requires an API key (X-SLM-API-Key). "
112
+ "The install token (X-Install-Token) is accepted only from loopback "
113
+ "(127.x.x.x / ::1 / localhost). If calling from a container or "
114
+ "remote host, configure SLM_API_KEY and present it as X-SLM-API-Key."
115
+ ),
116
+ )
107
117
 
108
118
 
109
119
  def require_http_mutation_actor(
@@ -134,7 +144,7 @@ def require_http_mutation_actor(
134
144
 
135
145
  client_host = request.client.host if request.client else ""
136
146
  is_test_client = client_host == "testclient" and _TEST_ISOLATION_ALLOWED
137
- loopback = client_host in ("127.0.0.1", "::1", "localhost")
147
+ loopback = _is_loopback_host(client_host)
138
148
  # H-01: the loopback trusted-actor bypass is suppressed when the operator
139
149
  # opts into SLM_REQUIRE_CREDENTIALS; in-process tests keep the bypass.
140
150
  if is_test_client or (loopback and not _REQUIRE_CREDENTIALS):
@@ -153,7 +163,15 @@ def require_http_mutation_actor(
153
163
  ).hexdigest()
154
164
  return f"mesh-secret:{fingerprint}"
155
165
 
156
- raise HTTPException(403, detail="Authenticated mutation capability required")
166
+ raise HTTPException(
167
+ 403,
168
+ detail=(
169
+ "Mutation rejected: present one of X-SLM-Daemon-Capability, "
170
+ "X-Install-Token (loopback only), or X-SLM-API-Key. "
171
+ "Uncredentialed writes are accepted only from loopback. "
172
+ "See SLM_API_KEY in the deployment guide for network access."
173
+ ),
174
+ )
157
175
 
158
176
 
159
177
  def authenticated_request_actor(
@@ -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")