yourmemory 1.4.75__tar.gz → 1.4.76__tar.gz
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.
- {yourmemory-1.4.75 → yourmemory-1.4.76}/PKG-INFO +1 -1
- {yourmemory-1.4.75 → yourmemory-1.4.76}/memory_mcp.py +0 -23
- {yourmemory-1.4.75 → yourmemory-1.4.76}/pyproject.toml +1 -1
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/app.py +16 -19
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/services/compaction.py +56 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/yourmemory.egg-info/PKG-INFO +1 -1
- {yourmemory-1.4.75 → yourmemory-1.4.76}/LICENSE +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/README.md +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/setup.cfg +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/__init__.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/db/connection.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/db/duckdb_schema.sql +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/db/migrate.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/db/schema.sql +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/db/sqlite_schema.sql +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/graph/__init__.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/graph/backend.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/graph/graph_store.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/graph/neo4j_backend.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/graph/networkx_backend.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/graph/svo_extract.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/hook_templates/__init__.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/hook_templates/yourmemory_observe.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/hook_templates/yourmemory_recall.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/hook_templates/yourmemory_recall.sh +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/hook_templates/yourmemory_server.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/hook_templates/yourmemory_store.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/hook_templates/yourmemory_user.sh +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/jobs/decay_job.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/routes/__init__.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/routes/agents.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/routes/audit.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/routes/compact.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/routes/dsar.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/routes/graph_viz.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/routes/memories.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/routes/proxy.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/routes/retrieve.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/routes/ui.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/services/__init__.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/services/agent_registry.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/services/api_keys.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/services/audit.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/services/decay.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/services/embed.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/services/extract.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/services/extract_fallback.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/services/resolve.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/services/resolve_fallback.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/services/retrieve.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/services/session.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/services/temporal.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/src/services/utils.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/tests/test_features.py +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/yourmemory.egg-info/SOURCES.txt +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/yourmemory.egg-info/dependency_links.txt +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/yourmemory.egg-info/entry_points.txt +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/yourmemory.egg-info/requires.txt +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/yourmemory.egg-info/top_level.txt +0 -0
- {yourmemory-1.4.75 → yourmemory-1.4.76}/yourmemory_run.py +0 -0
|
@@ -924,36 +924,13 @@ def _start_decay_scheduler():
|
|
|
924
924
|
except Exception:
|
|
925
925
|
pass
|
|
926
926
|
|
|
927
|
-
def _compact():
|
|
928
|
-
if os.getenv("YOURMEMORY_COMPACTION", "0") != "1":
|
|
929
|
-
return
|
|
930
|
-
try:
|
|
931
|
-
from src.services.compaction import compact_user
|
|
932
|
-
from src.db.connection import get_conn, get_backend
|
|
933
|
-
b = get_backend(); conn = get_conn()
|
|
934
|
-
try:
|
|
935
|
-
if b == "duckdb":
|
|
936
|
-
users = [r[0] for r in conn.execute("SELECT DISTINCT user_id FROM memories").fetchall()]
|
|
937
|
-
else:
|
|
938
|
-
cur = conn.cursor(); cur.execute("SELECT DISTINCT user_id FROM memories")
|
|
939
|
-
users = [r[0] for r in cur.fetchall()]; cur.close()
|
|
940
|
-
finally:
|
|
941
|
-
conn.close()
|
|
942
|
-
for u in users:
|
|
943
|
-
try: compact_user(u)
|
|
944
|
-
except Exception: pass
|
|
945
|
-
except Exception:
|
|
946
|
-
pass
|
|
947
|
-
|
|
948
927
|
def loop():
|
|
949
928
|
run_decay()
|
|
950
929
|
_audit_prune()
|
|
951
|
-
_compact()
|
|
952
930
|
timer = threading.Event()
|
|
953
931
|
while not timer.wait(timeout=86400):
|
|
954
932
|
run_decay()
|
|
955
933
|
_audit_prune()
|
|
956
|
-
_compact()
|
|
957
934
|
|
|
958
935
|
t = threading.Thread(target=loop, daemon=True, name="decay-scheduler")
|
|
959
936
|
t.start()
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "yourmemory"
|
|
7
|
-
version = "1.4.
|
|
7
|
+
version = "1.4.76"
|
|
8
8
|
description = "Persistent memory for Claude — Ebbinghaus forgetting curve, semantic deduplication, MCP-native"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -28,25 +28,8 @@ def _daily_jobs():
|
|
|
28
28
|
prune_audit() # retention: drop audit rows older than the (>=90-day) window
|
|
29
29
|
except Exception:
|
|
30
30
|
pass
|
|
31
|
-
#
|
|
32
|
-
|
|
33
|
-
try:
|
|
34
|
-
from src.services.compaction import compact_user
|
|
35
|
-
from src.db.connection import get_conn, get_backend
|
|
36
|
-
b = get_backend(); conn = get_conn()
|
|
37
|
-
try:
|
|
38
|
-
if b == "duckdb":
|
|
39
|
-
users = [r[0] for r in conn.execute("SELECT DISTINCT user_id FROM memories").fetchall()]
|
|
40
|
-
else:
|
|
41
|
-
cur = conn.cursor(); cur.execute("SELECT DISTINCT user_id FROM memories")
|
|
42
|
-
users = [r[0] for r in cur.fetchall()]; cur.close()
|
|
43
|
-
finally:
|
|
44
|
-
conn.close()
|
|
45
|
-
for u in users:
|
|
46
|
-
try: compact_user(u)
|
|
47
|
-
except Exception: pass
|
|
48
|
-
except Exception:
|
|
49
|
-
pass
|
|
31
|
+
# Note: memory compaction is event-driven (triggered on store when a cluster reaches
|
|
32
|
+
# N), not a daily sweep — see /auto-store and src/services/compaction.py.
|
|
50
33
|
|
|
51
34
|
|
|
52
35
|
@asynccontextmanager
|
|
@@ -677,4 +660,18 @@ def auto_store_endpoint(req: AutoStoreRequest):
|
|
|
677
660
|
except Exception:
|
|
678
661
|
pass
|
|
679
662
|
|
|
663
|
+
# Event-driven compaction: if a just-stored fact's cluster now has >= N closely
|
|
664
|
+
# related memories, compress that cluster immediately so the store stays lean —
|
|
665
|
+
# no daily sweep needed. On by default; disable with YOURMEMORY_COMPACTION=0.
|
|
666
|
+
if os.getenv("YOURMEMORY_COMPACTION", "1") == "1" and to_index:
|
|
667
|
+
try:
|
|
668
|
+
from src.services.compaction import maybe_compact_around
|
|
669
|
+
seen = set()
|
|
670
|
+
for _mid, content, _imp, _cat, _emb in to_index:
|
|
671
|
+
if content and content not in seen:
|
|
672
|
+
seen.add(content)
|
|
673
|
+
maybe_compact_around(user_id, content)
|
|
674
|
+
except Exception:
|
|
675
|
+
pass
|
|
676
|
+
|
|
680
677
|
return {"stored": len(stored), "facts": stored}
|
|
@@ -151,6 +151,62 @@ def compact_user(user_id: str, min_cluster: int = None, sim_threshold: float = N
|
|
|
151
151
|
"summaries": summaries, "scanned": len(mems)}
|
|
152
152
|
|
|
153
153
|
|
|
154
|
+
def _fetch_rows_by_ids(user_id: str, ids: list) -> list[dict]:
|
|
155
|
+
if not ids:
|
|
156
|
+
return []
|
|
157
|
+
backend = get_backend()
|
|
158
|
+
conn = get_conn()
|
|
159
|
+
cols = "id, content, category, importance, agent_id, visibility, created_at"
|
|
160
|
+
ph = ",".join(["%s" if backend == "postgres" else "?"] * len(ids))
|
|
161
|
+
sql = f"SELECT {cols} FROM memories WHERE user_id = {'%s' if backend=='postgres' else '?'} AND id IN ({ph})"
|
|
162
|
+
params = [user_id, *ids]
|
|
163
|
+
try:
|
|
164
|
+
if backend == "duckdb":
|
|
165
|
+
return duckdb_rows(conn.execute(sql, params))
|
|
166
|
+
cur = conn.cursor()
|
|
167
|
+
cur.execute(sql, tuple(params) if backend == "postgres" else params)
|
|
168
|
+
cn = [d[0] for d in cur.description]
|
|
169
|
+
rows = [dict(zip(cn, r)) for r in cur.fetchall()]; cur.close(); return rows
|
|
170
|
+
finally:
|
|
171
|
+
conn.close()
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def maybe_compact_around(user_id: str, seed_content: str,
|
|
175
|
+
min_cluster: int = None, sim_threshold: float = None) -> int | None:
|
|
176
|
+
"""Event-driven compaction: after a memory is stored, check whether its neighborhood
|
|
177
|
+
now has >= N closely-related memories; if so, compress just that cluster immediately.
|
|
178
|
+
Targeted (one similarity lookup, not an O(n^2) full scan). Returns the summary id."""
|
|
179
|
+
user_id = (user_id or "").strip().lower()
|
|
180
|
+
min_cluster = min_cluster or MIN_CLUSTER
|
|
181
|
+
sim = sim_threshold if sim_threshold is not None else SIM_THRESHOLD
|
|
182
|
+
if not seed_content or len(seed_content) < 4:
|
|
183
|
+
return None
|
|
184
|
+
try:
|
|
185
|
+
from src.services.retrieve import retrieve
|
|
186
|
+
res = retrieve(user_id, seed_content, top_k=max(min_cluster * 4, 20), no_graph=True)
|
|
187
|
+
except Exception:
|
|
188
|
+
return None
|
|
189
|
+
members = [m for m in res.get("memories", []) if m.get("similarity", 0) >= sim]
|
|
190
|
+
if len(members) < min_cluster:
|
|
191
|
+
return None # not enough of the same thing yet — leave it
|
|
192
|
+
rows = _fetch_rows_by_ids(user_id, [m["id"] for m in members])
|
|
193
|
+
if len(rows) < min_cluster:
|
|
194
|
+
return None
|
|
195
|
+
summary = _summarize([r["content"] for r in rows])
|
|
196
|
+
if not summary or len(summary) < 12:
|
|
197
|
+
return None
|
|
198
|
+
importance = max(float(r["importance"] or 0.5) for r in rows)
|
|
199
|
+
category = categorize(summary)
|
|
200
|
+
agent_id = rows[0].get("agent_id")
|
|
201
|
+
visibility = rows[0].get("visibility") or "shared"
|
|
202
|
+
summary_id = _apply_compaction(get_backend(), user_id, summary, importance, category,
|
|
203
|
+
agent_id, visibility, rows)
|
|
204
|
+
if summary_id is not None:
|
|
205
|
+
log_event("write", "compact", user_id,
|
|
206
|
+
detail={"clusters": 1, "archived": len(rows), "trigger": "count"})
|
|
207
|
+
return summary_id
|
|
208
|
+
|
|
209
|
+
|
|
154
210
|
def _apply_compaction(backend, user_id, summary, importance, category, agent_id,
|
|
155
211
|
visibility, members) -> int | None:
|
|
156
212
|
"""Insert the summary memory, archive the originals, delete them. Returns summary id."""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|