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.
Files changed (81) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +3 -3
  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/cli/commands.py +139 -404
  34. package/src/superlocalmemory/core/component_registry.py +4 -2
  35. package/src/superlocalmemory/core/embeddings.py +33 -6
  36. package/src/superlocalmemory/core/engine.py +94 -49
  37. package/src/superlocalmemory/core/engine_ingestion.py +150 -63
  38. package/src/superlocalmemory/core/ingestion_command.py +133 -21
  39. package/src/superlocalmemory/core/mutations.py +32 -10
  40. package/src/superlocalmemory/core/recall_pipeline.py +111 -77
  41. package/src/superlocalmemory/core/remember_admission.py +152 -0
  42. package/src/superlocalmemory/core/remember_runtime.py +712 -0
  43. package/src/superlocalmemory/graph/cozo_backend.py +5 -5
  44. package/src/superlocalmemory/learning/bandit.py +50 -1
  45. package/src/superlocalmemory/learning/source_quality.py +38 -35
  46. package/src/superlocalmemory/mcp/_daemon_proxy.py +38 -15
  47. package/src/superlocalmemory/mcp/tools_active.py +4 -41
  48. package/src/superlocalmemory/mcp/tools_core.py +26 -87
  49. package/src/superlocalmemory/mcp/tools_evolution.py +5 -10
  50. package/src/superlocalmemory/optimize/proxy/capture.py +196 -8
  51. package/src/superlocalmemory/retrieval/engine.py +8 -3
  52. package/src/superlocalmemory/retrieval/reranker.py +35 -10
  53. package/src/superlocalmemory/server/loopback.py +7 -13
  54. package/src/superlocalmemory/server/profile_runtime.py +14 -0
  55. package/src/superlocalmemory/server/routes/abstraction.py +2 -4
  56. package/src/superlocalmemory/server/routes/agents.py +3 -5
  57. package/src/superlocalmemory/server/routes/behavioral.py +5 -13
  58. package/src/superlocalmemory/server/routes/brain.py +6 -9
  59. package/src/superlocalmemory/server/routes/entity.py +3 -7
  60. package/src/superlocalmemory/server/routes/evolution.py +3 -5
  61. package/src/superlocalmemory/server/routes/helpers.py +44 -23
  62. package/src/superlocalmemory/server/routes/insights.py +2 -4
  63. package/src/superlocalmemory/server/routes/learning.py +2 -5
  64. package/src/superlocalmemory/server/routes/lifecycle.py +2 -4
  65. package/src/superlocalmemory/server/routes/memories.py +122 -100
  66. package/src/superlocalmemory/server/routes/tiers.py +3 -22
  67. package/src/superlocalmemory/server/routes/timeline.py +2 -4
  68. package/src/superlocalmemory/server/routes/v3_api.py +18 -16
  69. package/src/superlocalmemory/server/unified_daemon.py +200 -109
  70. package/src/superlocalmemory/storage/admission_codec.py +119 -0
  71. package/src/superlocalmemory/storage/admission_journal.py +728 -0
  72. package/src/superlocalmemory/storage/database.py +59 -0
  73. package/src/superlocalmemory/storage/deferred_writes.py +67 -11
  74. package/src/superlocalmemory/storage/memory_write.py +8 -12
  75. package/src/superlocalmemory/storage/migration_runner.py +37 -0
  76. package/src/superlocalmemory/storage/migrations/M032_write_coordinator_admission.py +188 -0
  77. package/src/superlocalmemory/storage/read_connection.py +115 -0
  78. package/src/superlocalmemory/storage/write_coordinator.py +756 -0
  79. package/src/superlocalmemory/ui/index.html +1 -1
  80. package/src/superlocalmemory/ui/js/auto-settings.js +14 -1
  81. package/src/superlocalmemory/ui/js/od-settings.js +9 -3
@@ -0,0 +1,152 @@
1
+ # Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
2
+ # Licensed under AGPL-3.0-or-later - see LICENSE file
3
+
4
+ """Journal-first service boundary for a durable, immediately-queryable remember."""
5
+
6
+ from __future__ import annotations
7
+
8
+ import math
9
+ import time
10
+ from collections.abc import Mapping
11
+ from dataclasses import dataclass
12
+ from typing import Any, Protocol
13
+
14
+ from superlocalmemory.storage.admission_journal import (
15
+ Actor,
16
+ AdmissionJournal,
17
+ AdmissionJournalUnavailable,
18
+ PreparedAdmission,
19
+ RememberRequest,
20
+ TerminalAdmissionError,
21
+ )
22
+
23
+
24
+ class AdmissionRejected(RuntimeError):
25
+ """Canonical admission was not committed; the journal records its outcome."""
26
+
27
+ def __init__(self, error_code: str, *, retryable: bool = False) -> None:
28
+ super().__init__(error_code)
29
+ self.error_code = error_code
30
+ self.retryable = retryable
31
+
32
+
33
+ @dataclass(frozen=True, slots=True)
34
+ class RememberReceipt:
35
+ """Canonical receipt returned only after admission is queryable."""
36
+
37
+ payload: dict[str, Any]
38
+
39
+ @classmethod
40
+ def from_mapping(cls, receipt: Mapping[str, Any]) -> RememberReceipt:
41
+ return cls(payload=dict(receipt))
42
+
43
+
44
+ @dataclass(frozen=True, slots=True)
45
+ class RememberAdmissionCommand:
46
+ """Typed foreground input for a coordinator's bounded canonical transaction."""
47
+
48
+ journal_id: str
49
+ request_hash: str
50
+ request: RememberRequest
51
+ profile_id: str
52
+ idempotency_key: str
53
+
54
+ @classmethod
55
+ def from_prepared(
56
+ cls, prepared: PreparedAdmission, request: RememberRequest
57
+ ) -> RememberAdmissionCommand:
58
+ return cls(
59
+ journal_id=prepared.journal_id,
60
+ request_hash=prepared.request_hash,
61
+ request=request,
62
+ profile_id=prepared.profile_id,
63
+ idempotency_key=prepared.idempotency_key,
64
+ )
65
+
66
+
67
+ class RememberCoordinator(Protocol):
68
+ def submit(self, command: RememberAdmissionCommand, *, wait_ms: int) -> Any: ...
69
+
70
+
71
+ class RememberService:
72
+ """Apply the journaling pattern without doing enrichment inside admission."""
73
+
74
+ def __init__(self, journal: AdmissionJournal, coordinator: RememberCoordinator) -> None:
75
+ self._journal = journal
76
+ self._coordinator = coordinator
77
+
78
+ def remember(
79
+ self, request: RememberRequest, actor: Actor, *, deadline_ms: int
80
+ ) -> RememberReceipt:
81
+ if deadline_ms <= 0:
82
+ raise ValueError("deadline_ms must be greater than zero")
83
+ deadline = time.monotonic() + deadline_ms / 1_000
84
+ prepared = self._journal.prepare(
85
+ request,
86
+ actor,
87
+ deadline=deadline,
88
+ )
89
+ if prepared.original_receipt is not None:
90
+ return RememberReceipt.from_mapping(prepared.original_receipt)
91
+ if prepared.state == "rejected":
92
+ raise AdmissionRejected(prepared.error_code or "COMMAND_REJECTED")
93
+
94
+ command_request = self._journal.request_for(
95
+ prepared,
96
+ deadline=deadline,
97
+ )
98
+ dispatched = self._journal.mark_dispatched(
99
+ prepared.journal_id,
100
+ deadline=deadline,
101
+ )
102
+ if dispatched.original_receipt is not None:
103
+ return RememberReceipt.from_mapping(dispatched.original_receipt)
104
+ try:
105
+ result = self._coordinator.submit(
106
+ RememberAdmissionCommand.from_prepared(prepared, command_request),
107
+ wait_ms=_remaining_milliseconds(deadline),
108
+ )
109
+ except TerminalAdmissionError as exc:
110
+ self._journal.mark_rejected(
111
+ prepared.journal_id,
112
+ exc.error_code,
113
+ deadline=deadline,
114
+ )
115
+ raise AdmissionRejected(exc.error_code) from exc
116
+ state = _result_value(result, "state")
117
+ receipt = _result_value(result, "receipt") or {}
118
+ if state in {"committed", "duplicate"}:
119
+ if not isinstance(receipt, Mapping):
120
+ raise AdmissionRejected("COMMAND_REJECTED: canonical result had no receipt")
121
+ committed = self._journal.mark_committed(
122
+ prepared.journal_id,
123
+ receipt,
124
+ deadline=deadline,
125
+ )
126
+ return RememberReceipt.from_mapping(committed.original_receipt or receipt)
127
+
128
+ error_code = str(_result_value(result, "error_code") or "COMMAND_REJECTED")
129
+ if state == "rejected":
130
+ self._journal.mark_rejected(
131
+ prepared.journal_id,
132
+ error_code,
133
+ deadline=deadline,
134
+ )
135
+ raise AdmissionRejected(error_code, retryable=state != "rejected")
136
+
137
+
138
+ def _result_value(result: Any, key: str) -> Any:
139
+ if isinstance(result, Mapping):
140
+ return result.get(key)
141
+ return getattr(result, key, None)
142
+
143
+
144
+ def _remaining_seconds(deadline: float) -> float:
145
+ remaining = deadline - time.monotonic()
146
+ if remaining <= 0:
147
+ raise AdmissionJournalUnavailable("remember admission deadline expired")
148
+ return remaining
149
+
150
+
151
+ def _remaining_milliseconds(deadline: float) -> int:
152
+ return max(1, math.ceil(_remaining_seconds(deadline) * 1_000))