seren-memory 2.2.2__tar.gz → 2.2.3__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.
Files changed (48) hide show
  1. {seren_memory-2.2.2 → seren_memory-2.2.3}/PKG-INFO +1 -1
  2. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren_memory/_version.py +3 -3
  3. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren_memory/collections.py +70 -12
  4. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren_memory/routes/search.py +1 -1
  5. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren_memory.egg-info/PKG-INFO +1 -1
  6. {seren_memory-2.2.2 → seren_memory-2.2.3}/SerenMemory.pyproj +0 -0
  7. {seren_memory-2.2.2 → seren_memory-2.2.3}/pyproject.toml +0 -0
  8. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren-memory.service.sample +0 -0
  9. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren-memory.yaml.sample +0 -0
  10. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren_memory/__init__.py +0 -0
  11. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren_memory/__main__.py +0 -0
  12. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren_memory/_supervised.py +0 -0
  13. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren_memory/app.py +0 -0
  14. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren_memory/config.py +0 -0
  15. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren_memory/consolidator/__init__.py +0 -0
  16. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren_memory/consolidator/service.py +0 -0
  17. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren_memory/embedder.py +0 -0
  18. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren_memory/mcp/__init__.py +0 -0
  19. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren_memory/mcp/server.py +0 -0
  20. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren_memory/mcp/tools.py +0 -0
  21. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren_memory/models/__init__.py +0 -0
  22. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren_memory/models/schemas.py +0 -0
  23. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren_memory/routes/__init__.py +0 -0
  24. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren_memory/routes/long.py +0 -0
  25. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren_memory/routes/near.py +0 -0
  26. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren_memory/routes/short.py +0 -0
  27. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren_memory/viewer/halls.html +0 -0
  28. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren_memory.egg-info/SOURCES.txt +0 -0
  29. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren_memory.egg-info/dependency_links.txt +0 -0
  30. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren_memory.egg-info/entry_points.txt +0 -0
  31. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren_memory.egg-info/requires.txt +0 -0
  32. {seren_memory-2.2.2 → seren_memory-2.2.3}/seren_memory.egg-info/top_level.txt +0 -0
  33. {seren_memory-2.2.2 → seren_memory-2.2.3}/setup.cfg +0 -0
  34. {seren_memory-2.2.2 → seren_memory-2.2.3}/tests/__init__.py +0 -0
  35. {seren_memory-2.2.2 → seren_memory-2.2.3}/tests/conftest.py +0 -0
  36. {seren_memory-2.2.2 → seren_memory-2.2.3}/tests/test_auth.py +0 -0
  37. {seren_memory-2.2.2 → seren_memory-2.2.3}/tests/test_brief.py +0 -0
  38. {seren_memory-2.2.2 → seren_memory-2.2.3}/tests/test_drafts.py +0 -0
  39. {seren_memory-2.2.2 → seren_memory-2.2.3}/tests/test_embedder.py +0 -0
  40. {seren_memory-2.2.2 → seren_memory-2.2.3}/tests/test_mcp_endpoint.py +0 -0
  41. {seren_memory-2.2.2 → seren_memory-2.2.3}/tests/test_mcp_fallback.py +0 -0
  42. {seren_memory-2.2.2 → seren_memory-2.2.3}/tests/test_mcp_mount.py +0 -0
  43. {seren_memory-2.2.2 → seren_memory-2.2.3}/tests/test_mcp_tools.py +0 -0
  44. {seren_memory-2.2.2 → seren_memory-2.2.3}/tests/test_near_listing.py +0 -0
  45. {seren_memory-2.2.2 → seren_memory-2.2.3}/tests/test_search.py +0 -0
  46. {seren_memory-2.2.2 → seren_memory-2.2.3}/tests/test_smoke.py +0 -0
  47. {seren_memory-2.2.2 → seren_memory-2.2.3}/tests/test_tls.py +0 -0
  48. {seren_memory-2.2.2 → seren_memory-2.2.3}/tests/test_validation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: seren-memory
3
- Version: 2.2.2
3
+ Version: 2.2.3
4
4
  Summary: Three-tier LLM memory with consolidation. The Halls of Memory.
5
5
  Author: Chad Roesler
6
6
  License: GPL-3.0-or-later
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '2.2.2'
22
- __version_tuple__ = version_tuple = (2, 2, 2)
21
+ __version__ = version = '2.2.3'
22
+ __version_tuple__ = version_tuple = (2, 2, 3)
23
23
 
24
- __commit_id__ = commit_id = 'g12a1081ca'
24
+ __commit_id__ = commit_id = 'gfb84dcf2f'
@@ -23,6 +23,7 @@ METADATA FLATTENING:
23
23
  from __future__ import annotations
24
24
 
25
25
  import json
26
+ import math
26
27
  import time
27
28
  from typing import Any, Optional
28
29
  from pathlib import Path
@@ -702,29 +703,86 @@ class MemoryStore:
702
703
  # ------------------------------------------------------------------
703
704
  def query(self, collection_name: str, query_text: str, n: int) -> list[dict[str, Any]]:
704
705
  """Similarity search against one collection. Returns hits with
705
- distance. collection_name in {short, near, long}."""
706
+ distance. collection_name in {short, near, long}.
707
+
708
+ ChromaDB's HNSW index is written asynchronously — count() reads from
709
+ SQLite (always current) but the .bin segment files may not exist yet
710
+ for entries added in the current session. When that happens we fall
711
+ back to a linear brute-force scan: fetch all docs via .get() (SQLite,
712
+ always available) then re-embed the query and rank by cosine distance.
713
+ For a personal memory store the counts are small enough this is fine.
714
+ """
706
715
  col = {"short": self.short, "near": self.near, "long": self.long}.get(collection_name)
707
716
  if col is None:
708
717
  raise ValueError(f"unknown collection '{collection_name}'")
709
718
  if col.count() == 0:
710
719
  return []
711
- res = col.query(
712
- query_texts=[query_text],
713
- n_results=min(n, col.count()),
714
- include=["documents", "metadatas", "distances"],
715
- )
720
+ try:
721
+ res = col.query(
722
+ query_texts=[query_text],
723
+ n_results=min(n, col.count()),
724
+ include=["documents", "metadatas", "distances"],
725
+ )
726
+ hits: list[dict[str, Any]] = []
727
+ ids = res.get("ids", [[]])[0]
728
+ docs = res.get("documents", [[]])[0]
729
+ metas = res.get("metadatas", [[]])[0]
730
+ dists = res.get("distances", [[]])[0]
731
+ for i in range(len(ids)):
732
+ meta = {k: _maybe_json(v) for k, v in (metas[i] or {}).items()}
733
+ hits.append({
734
+ "id": ids[i],
735
+ "content": docs[i],
736
+ "metadata": meta,
737
+ "distance": dists[i],
738
+ })
739
+ return hits
740
+ except Exception: # noqa: BLE001
741
+ # HNSW segment not flushed to disk yet — fall back to brute-force.
742
+ return self._query_brute(col, query_text, n)
743
+
744
+ def _query_brute(self, col: Any, query_text: str, n: int) -> list[dict[str, Any]]:
745
+ """Linear cosine-similarity fallback used when the HNSW index isn't
746
+ available (entries written but not yet persisted to disk). Re-embeds
747
+ the query using the collection's own embedding function so the vector
748
+ space is guaranteed to match."""
749
+ raw = col.get(include=["documents", "metadatas", "embeddings"])
750
+ ids = raw.get("ids") or []
751
+ docs = raw.get("documents") or []
752
+ metas = raw.get("metadatas") or []
753
+ embeddings = raw.get("embeddings") or []
754
+ if not ids or not embeddings:
755
+ return []
756
+
757
+ # Re-embed the query through the same EF the collection uses.
758
+ try:
759
+ q_vecs = col._embedding_function([query_text])
760
+ q_vec = q_vecs[0]
761
+ except Exception: # noqa: BLE001
762
+ return []
763
+
764
+ def _cosine_dist(a: list[float], b: list[float]) -> float:
765
+ dot = sum(x * y for x, y in zip(a, b))
766
+ na = math.sqrt(sum(x * x for x in a))
767
+ nb = math.sqrt(sum(x * x for x in b))
768
+ if na == 0.0 or nb == 0.0:
769
+ return 1.0
770
+ return 1.0 - dot / (na * nb)
771
+
772
+ scored: list[tuple[float, int]] = []
773
+ for i, emb in enumerate(embeddings):
774
+ if emb is not None:
775
+ scored.append((_cosine_dist(q_vec, emb), i))
776
+ scored.sort(key=lambda t: t[0])
777
+
716
778
  hits: list[dict[str, Any]] = []
717
- ids = res.get("ids", [[]])[0]
718
- docs = res.get("documents", [[]])[0]
719
- metas = res.get("metadatas", [[]])[0]
720
- dists = res.get("distances", [[]])[0]
721
- for i in range(len(ids)):
779
+ for dist, i in scored[:n]:
722
780
  meta = {k: _maybe_json(v) for k, v in (metas[i] or {}).items()}
723
781
  hits.append({
724
782
  "id": ids[i],
725
783
  "content": docs[i],
726
784
  "metadata": meta,
727
- "distance": dists[i],
785
+ "distance": dist,
728
786
  })
729
787
  return hits
730
788
 
@@ -59,7 +59,7 @@ async def search(request: Request, req: SearchRequest = Body(...)) -> SearchResp
59
59
  searched.append(tier)
60
60
  try:
61
61
  raw = store.query(tier, req.query, fetch_n)
62
- except ValueError:
62
+ except Exception: # noqa: BLE001
63
63
  continue
64
64
 
65
65
  for hit in raw:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: seren-memory
3
- Version: 2.2.2
3
+ Version: 2.2.3
4
4
  Summary: Three-tier LLM memory with consolidation. The Halls of Memory.
5
5
  Author: Chad Roesler
6
6
  License: GPL-3.0-or-later
File without changes