An empty ID no longer wipes a project
An empty user ID no longer deletes
every memory in the project
client.delete_all(user_id="")
# before: DELETE /v1/memories/?user_id=
# after: ValueError, and no request is sent
In the hosted mem0 clients, calling delete all with an empty user ID sent a request the server reads as every memory in the project. This pull request stops that call inside the client, in Python and TypeScript, before anything goes over the wire. It closes the same hole in delete users, and three quieter ones around the same arguments.
Where the API reads entity IDs
The API reads entity IDs in two different places
delete_all()
get_all()
The platform API reads entity IDs in two different places. Add and delete all read the user, agent, app and run IDs at the top level of the request. Search and get all read them inside filters. Both clients let callers mix the two up, and a blank ID went straight through to the server.
What the old client sent
What the old Python client sent for five wrong inputs
Here is what the old Python client put on the wire. Two calls could wipe data. Delete all with an empty user ID, and delete users with one, which fell through a truthiness check and deleted every entity. Three more failed quietly. A filters dict on delete all went out as a string and deleted nothing, the typed add options dropped the user ID, and filters on add got a 400.
Two helpers in the Python client
Two helpers raise before any request is sent
def _reject_filters(method: str, params: Dict[str, Any]) -> None:
if params.get("filters") is not None:
raise ValueError(
f"filters is not supported in {method}(). Pass entity IDs at the top level, ..."
)
def _reject_blank_entity_ids(params: Dict[str, Any]) -> None:
for name, value in params.items():
if name in ENTITY_PARAMS and isinstance(value, str) and not value.strip():
raise ValueError(
f"{name} cannot be empty or whitespace-only: an empty ID would match every ..."
)
The Python fix is two module level helpers. The first raises a value error whenever filters is set. None counts as not provided, because the proxy always passes filters as None. The second raises on any entity ID that is empty after stripping whitespace. A missing ID passes, and so does the star wildcard.
Where each check runs
Each check runs where a wrong input does damage
Each check runs in the sync and the async client. Delete all gets both checks. Delete users gets the blank ID check. Add only rejects filters, because a blank ID on add can't delete anything, so it is left alone. And the star wildcard still works. It stays the explicit way to delete everything.
The TypeScript client matches
The TypeScript client runs the same checks, camelCase keys included
async deleteAll(
options: DeleteAllMemoryOptions = {},
): Promise<{ message: string }> {
rejectFilters(options, "deleteAll");
rejectBlankEntityIds(options);
...
}
On the TypeScript side, the same two helpers sit next to the existing top level check. They run in add, delete all and delete users, before any fetch. In review, the question was whether the two clients really agree on blank IDs. They do. Same four fields, plus the camelCase forms, the same trim rule, and the same error text pointing at the star.
Typed options carry the entity IDs
AddMemoryOptions and DeleteAllMemoryOptions now take the four entity IDs
class AddMemoryOptions(BaseModel): filters: Optional[Dict[str, Any]] = Field(...) user_id: Optional[str] = Field(default=None, ...) agent_id: Optional[str] = Field(default=None, ...) app_id: Optional[str] = Field(default=None, ...) run_id: Optional[str] = Field(default=None, ...) metadata: Optional[Dict[str, Any]] = Field(...)
The typed options had the contract backwards. Their docstrings said IDs go inside filters, so add memory options with a user ID, the call the migration guide shows, dropped it. The filters field is gone from both models, and the four entity IDs replace it. One gap is left. The models still allow extra fields, so a filters argument there is ignored, and the server answers with the same 400 as before.
The proxy stops passing filters to add
The proxy now sends filters to search() and keeps them out of add()
- self._async_add_to_memory(messages, user_id, agent_id, run_id, metadata, filters)+ self._async_add_to_memory(messages, user_id, agent_id, run_id, metadata) relevant_memories = self._fetch_relevant_memories(messages, user_id, agent_id, run_id, filters, top_k)
Also fixed: the Python example in docs/platform/features/custom-instructions.mdx now passes agent_id="support-agent" instead of filters.
A sweep of internal callers found two that passed filters to add. The proxy's chat completions forwarded filters to add from a daemon thread, where the new error would be raised and lost. Filters now go only to search, and a regression test pins that. The second was the custom instructions docs example, which now passes the agent ID directly.
The tests failed first, then passed
The new tests failed first, then passed
The tests were written before the fix. In Python, 66 new guard tests, and 58 of them failed against the old client. After the fix, they pass together with the existing client tests, just over a hundred in all. In TypeScript, 47 new tests, 40 failing before. After the fix, the whole suite passes, about 1,600 tests.
Checked against a throwaway project
Valid calls still add and delete on a live project
add(filters={"user_id": ...}) -> ValueError: filters is not supported in add()
add(AddMemoryOptions(user_id=..., infer=False)) -> 'status': 'SUCCEEDED'
memory visible for user via get_all(filters): True count: 1
delete_all(DeleteAllMemoryOptions(user_id=...)) -> 'Delete in progress.'
memories remaining for user after delete_all: 0
Then a live run, against a throwaway project with a one-off user ID. Filters on add now raise. Add with the typed options stores the memory, get all finds exactly one, and delete all with the typed options leaves zero. The TypeScript run matched. The empty ID delete was kept off the live project on purpose, since it could wipe it.
What changes for callers
Pass entity IDs at the top level, and use "*" to delete on purpose
For anyone calling the clients, the change is one line. Entity IDs go at the top level of add and delete all, never in filters. The server still treats an empty ID as every memory and needs its own fix, and the version bump lands in a separate pull request. Until then, the clients are the guard, and an empty user ID no longer deletes every memory in the project.











