superlocalmemory 3.3.2 → 3.3.3
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
CHANGED
|
@@ -16,6 +16,18 @@ SuperLocalMemory V3 - Intelligent local memory system for AI coding assistants.
|
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
+
## [3.3.3] - 2026-04-01 — Langevin Awakening
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- **Langevin dynamics now active** — positions were never initialized at store time, causing the entire Langevin lifecycle system to be inert (0 positioned facts). New facts now receive near-origin positions (Strategy A).
|
|
23
|
+
- **Backfill for existing facts** — maintenance now initializes unpositioned facts using metadata-aware equilibrium seeding (Strategy B) followed by 50-step burn-in (Strategy C). Old, rarely-accessed facts land in their correct lifecycle zones immediately.
|
|
24
|
+
|
|
25
|
+
### Improved
|
|
26
|
+
- Maintenance returns `langevin_backfilled` count for observability
|
|
27
|
+
- Health check now reports positioned facts accurately after backfill
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
19
31
|
## [3.3.0] - 2026-03-31 — The Living Brain
|
|
20
32
|
|
|
21
33
|
### New Features
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "superlocalmemory",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.3",
|
|
4
4
|
"description": "Information-geometric agent memory with mathematical guarantees. 4-channel retrieval, Fisher-Rao similarity, zero-LLM mode, EU AI Act compliant. Works with Claude, Cursor, Windsurf, and 17+ AI tools.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-memory",
|
package/pyproject.toml
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
Periodic batch processing for mathematical layers:
|
|
8
8
|
1. Langevin batch_step on all active facts (self-organization)
|
|
9
|
+
1a. Backfill: seed uninitialized facts with metadata-aware positions (B+C)
|
|
9
10
|
2. Sheaf batch consistency check on recent facts
|
|
10
11
|
3. Fisher adaptive temperature recalculation
|
|
11
12
|
|
|
@@ -18,15 +19,72 @@ License: MIT
|
|
|
18
19
|
from __future__ import annotations
|
|
19
20
|
|
|
20
21
|
import logging
|
|
22
|
+
import math as _math
|
|
21
23
|
from datetime import UTC, datetime, timedelta
|
|
22
24
|
from typing import TYPE_CHECKING
|
|
23
25
|
|
|
26
|
+
import numpy as np
|
|
27
|
+
|
|
24
28
|
if TYPE_CHECKING:
|
|
25
29
|
from superlocalmemory.core.config import SLMConfig
|
|
26
30
|
from superlocalmemory.storage.database import DatabaseManager
|
|
27
31
|
|
|
28
32
|
logger = logging.getLogger(__name__)
|
|
29
33
|
|
|
34
|
+
# Backfill constants
|
|
35
|
+
_BACKFILL_BURN_IN_STEPS = 50
|
|
36
|
+
_LANGEVIN_DIM = 8
|
|
37
|
+
_MAX_NORM = 0.99
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _compute_equilibrium_radius(
|
|
41
|
+
access_count: int,
|
|
42
|
+
age_days: float,
|
|
43
|
+
importance: float,
|
|
44
|
+
temperature: float = 0.3,
|
|
45
|
+
dim: int = 8,
|
|
46
|
+
) -> float:
|
|
47
|
+
"""Compute metadata-aware equilibrium radius (Strategy B).
|
|
48
|
+
|
|
49
|
+
Uses the Langevin potential coefficients to estimate where a fact
|
|
50
|
+
would settle if it had been in the dynamics from the start.
|
|
51
|
+
|
|
52
|
+
r_eq ≈ sqrt(T * dim / (2 * effective_alpha))
|
|
53
|
+
"""
|
|
54
|
+
alpha, beta, gamma, delta = 3.0, 0.8, 0.005, 0.5
|
|
55
|
+
effective_alpha = (
|
|
56
|
+
alpha
|
|
57
|
+
+ beta * _math.log(access_count + 1) / 10.0
|
|
58
|
+
- gamma * min(age_days, 365.0) / 365.0
|
|
59
|
+
+ delta * importance
|
|
60
|
+
)
|
|
61
|
+
effective_alpha = max(0.1, effective_alpha)
|
|
62
|
+
r_eq = _math.sqrt(temperature * dim / (2.0 * effective_alpha))
|
|
63
|
+
return min(r_eq, _MAX_NORM * 0.95)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _seed_langevin_position(
|
|
67
|
+
access_count: int,
|
|
68
|
+
age_days: float,
|
|
69
|
+
importance: float,
|
|
70
|
+
temperature: float = 0.3,
|
|
71
|
+
dim: int = 8,
|
|
72
|
+
) -> list[float]:
|
|
73
|
+
"""Create a metadata-aware initial position (Strategy B).
|
|
74
|
+
|
|
75
|
+
Places the fact at the equilibrium radius with a random direction.
|
|
76
|
+
"""
|
|
77
|
+
r_eq = _compute_equilibrium_radius(
|
|
78
|
+
access_count, age_days, importance, temperature, dim,
|
|
79
|
+
)
|
|
80
|
+
rng = np.random.default_rng()
|
|
81
|
+
direction = rng.standard_normal(dim)
|
|
82
|
+
norm = float(np.linalg.norm(direction))
|
|
83
|
+
if norm < 1e-8:
|
|
84
|
+
direction = np.ones(dim)
|
|
85
|
+
norm = float(np.linalg.norm(direction))
|
|
86
|
+
return (direction / norm * r_eq).tolist()
|
|
87
|
+
|
|
30
88
|
|
|
31
89
|
def run_maintenance(
|
|
32
90
|
db: DatabaseManager,
|
|
@@ -44,6 +102,7 @@ def run_maintenance(
|
|
|
44
102
|
Dict of counts: langevin_updated, sheaf_checked, etc.
|
|
45
103
|
"""
|
|
46
104
|
counts: dict[str, int] = {
|
|
105
|
+
"langevin_backfilled": 0,
|
|
47
106
|
"langevin_updated": 0,
|
|
48
107
|
"fisher_coupled": 0,
|
|
49
108
|
"sheaf_checked": 0,
|
|
@@ -53,13 +112,60 @@ def run_maintenance(
|
|
|
53
112
|
if not facts:
|
|
54
113
|
return counts
|
|
55
114
|
|
|
56
|
-
#
|
|
115
|
+
# 1a. Backfill: seed uninitialized facts with metadata-aware positions (B+C)
|
|
116
|
+
if config.math.langevin_persist_positions:
|
|
117
|
+
try:
|
|
118
|
+
from superlocalmemory.math.langevin import LangevinDynamics
|
|
119
|
+
|
|
120
|
+
ld = LangevinDynamics(
|
|
121
|
+
dim=_LANGEVIN_DIM,
|
|
122
|
+
dt=config.math.langevin_dt,
|
|
123
|
+
temperature=config.math.langevin_temperature,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
backfilled = 0
|
|
127
|
+
for f in facts:
|
|
128
|
+
if f.langevin_position is not None:
|
|
129
|
+
continue
|
|
130
|
+
created = datetime.fromisoformat(
|
|
131
|
+
f.created_at.replace("Z", "+00:00")
|
|
132
|
+
) if f.created_at else datetime.now(UTC)
|
|
133
|
+
age_days = max(
|
|
134
|
+
0.0,
|
|
135
|
+
(datetime.now(UTC) - created).total_seconds() / 86400.0,
|
|
136
|
+
)
|
|
137
|
+
# Strategy B: metadata-aware seed position
|
|
138
|
+
position = _seed_langevin_position(
|
|
139
|
+
f.access_count, age_days, f.importance,
|
|
140
|
+
config.math.langevin_temperature, _LANGEVIN_DIM,
|
|
141
|
+
)
|
|
142
|
+
# Strategy C: burn-in from the seeded position
|
|
143
|
+
for step_i in range(_BACKFILL_BURN_IN_STEPS):
|
|
144
|
+
position, _ = ld.step(
|
|
145
|
+
position, f.access_count, age_days, f.importance,
|
|
146
|
+
)
|
|
147
|
+
weight = ld.compute_lifecycle_weight(position)
|
|
148
|
+
lifecycle = ld.get_lifecycle_state(weight).value
|
|
149
|
+
db.update_fact(f.fact_id, {
|
|
150
|
+
"langevin_position": position,
|
|
151
|
+
"lifecycle": lifecycle,
|
|
152
|
+
})
|
|
153
|
+
f.langevin_position = position # update in-memory for step 1b
|
|
154
|
+
backfilled += 1
|
|
155
|
+
|
|
156
|
+
counts["langevin_backfilled"] = backfilled
|
|
157
|
+
if backfilled:
|
|
158
|
+
logger.info("Langevin backfill: %d facts initialized", backfilled)
|
|
159
|
+
except Exception as exc:
|
|
160
|
+
logger.warning("Langevin backfill failed: %s", exc)
|
|
161
|
+
|
|
162
|
+
# 1b. Langevin batch step on all positioned facts
|
|
57
163
|
if config.math.langevin_persist_positions:
|
|
58
164
|
try:
|
|
59
165
|
from superlocalmemory.math.langevin import LangevinDynamics
|
|
60
166
|
|
|
61
167
|
ld = LangevinDynamics(
|
|
62
|
-
dim=
|
|
168
|
+
dim=_LANGEVIN_DIM,
|
|
63
169
|
dt=config.math.langevin_dt,
|
|
64
170
|
temperature=config.math.langevin_temperature,
|
|
65
171
|
)
|
|
@@ -165,8 +271,8 @@ def run_maintenance(
|
|
|
165
271
|
logger.warning("Sheaf maintenance failed: %s", exc)
|
|
166
272
|
|
|
167
273
|
logger.info(
|
|
168
|
-
"Maintenance complete: %d Langevin, %d Fisher-coupled, %d Sheaf",
|
|
169
|
-
counts["
|
|
170
|
-
counts["sheaf_checked"],
|
|
274
|
+
"Maintenance complete: %d backfilled, %d Langevin, %d Fisher-coupled, %d Sheaf",
|
|
275
|
+
counts["langevin_backfilled"], counts["langevin_updated"],
|
|
276
|
+
counts["fisher_coupled"], counts["sheaf_checked"],
|
|
171
277
|
)
|
|
172
278
|
return counts
|
|
@@ -25,6 +25,25 @@ from superlocalmemory.storage.models import (
|
|
|
25
25
|
|
|
26
26
|
logger = logging.getLogger(__name__)
|
|
27
27
|
|
|
28
|
+
# Langevin initialization radius for new facts (ACTIVE zone < 0.3)
|
|
29
|
+
_INIT_LANGEVIN_RADIUS = 0.05
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _init_langevin_position(dim: int = 8) -> list[float]:
|
|
33
|
+
"""Initialize Langevin position near origin for a new fact.
|
|
34
|
+
|
|
35
|
+
Small random perturbation ensures each fact gets a unique position
|
|
36
|
+
while staying deep in the ACTIVE zone (radius < 0.3).
|
|
37
|
+
"""
|
|
38
|
+
import numpy as np
|
|
39
|
+
rng = np.random.default_rng()
|
|
40
|
+
direction = rng.standard_normal(dim)
|
|
41
|
+
norm = float(np.linalg.norm(direction))
|
|
42
|
+
if norm < 1e-8:
|
|
43
|
+
direction = np.ones(dim)
|
|
44
|
+
norm = float(np.linalg.norm(direction))
|
|
45
|
+
return (direction / norm * _INIT_LANGEVIN_RADIUS).tolist()
|
|
46
|
+
|
|
28
47
|
|
|
29
48
|
# ---------------------------------------------------------------------------
|
|
30
49
|
# enrich_fact (was MemoryEngine._enrich_fact)
|
|
@@ -59,6 +78,10 @@ def enrich_fact(
|
|
|
59
78
|
emotion = tag_emotion(fact.content)
|
|
60
79
|
signal = infer_signal(fact.content)
|
|
61
80
|
|
|
81
|
+
# Strategy A: initialize Langevin position near origin (ACTIVE zone).
|
|
82
|
+
# New facts start as ACTIVE; dynamics will evolve them based on access patterns.
|
|
83
|
+
langevin_pos = _init_langevin_position(dim=8)
|
|
84
|
+
|
|
62
85
|
return AtomicFact(
|
|
63
86
|
fact_id=fact.fact_id, memory_id=record.memory_id,
|
|
64
87
|
profile_id=profile_id, content=fact.content,
|
|
@@ -73,6 +96,7 @@ def enrich_fact(
|
|
|
73
96
|
evidence_count=fact.evidence_count,
|
|
74
97
|
source_turn_ids=fact.source_turn_ids, session_id=record.session_id,
|
|
75
98
|
embedding=embedding, fisher_mean=fisher_mean, fisher_variance=fisher_variance,
|
|
99
|
+
langevin_position=langevin_pos,
|
|
76
100
|
emotional_valence=emotion.valence, emotional_arousal=emotion.arousal,
|
|
77
101
|
signal_type=signal, created_at=fact.created_at,
|
|
78
102
|
)
|