{"record":{"id":"af348b339947f659","repo":"mem0ai/mem0","slug":"validation-003","errorCode":"VALIDATION_003","errorMessage":"messages must be str, dict, or list[dict]","messagePattern":"messages must be str, dict, or list\\[dict\\]","errorType":"validation","errorClass":"Mem0ValidationError","httpStatus":null,"severity":"error","filePath":"mem0/memory/main.py","lineNumber":846,"sourceCode":"        if normalized_expiration_date is not None:\n            processed_metadata[\"expiration_date\"] = normalized_expiration_date\n\n        if memory_type is not None and memory_type != MemoryType.PROCEDURAL.value:\n            raise Mem0ValidationError(\n                message=f\"Invalid 'memory_type'. Please pass {MemoryType.PROCEDURAL.value} to create procedural memories.\",\n                error_code=\"VALIDATION_002\",\n                details={\"provided_type\": memory_type, \"valid_type\": MemoryType.PROCEDURAL.value},\n                suggestion=f\"Use '{MemoryType.PROCEDURAL.value}' to create procedural memories.\"\n            )\n\n        if isinstance(messages, str):\n            messages = [{\"role\": \"user\", \"content\": messages}]\n\n        elif isinstance(messages, dict):\n            messages = [messages]\n\n        elif not isinstance(messages, list):\n            raise Mem0ValidationError(\n                message=\"messages must be str, dict, or list[dict]\",\n                error_code=\"VALIDATION_003\",\n                details={\"provided_type\": type(messages).__name__, \"valid_types\": [\"str\", \"dict\", \"list[dict]\"]},\n                suggestion=\"Convert your input to a string, dictionary, or list of dictionaries.\"\n            )\n\n        if agent_id is not None and memory_type == MemoryType.PROCEDURAL.value:\n            results = self._create_procedural_memory(messages, metadata=processed_metadata, prompt=prompt)\n            scale_threshold_notice = detect_scale_threshold_from_add_result(self, results)\n            if temporal_usage_notice:\n                display_temporal_usage_notice(self, \"sync\", \"add\", *temporal_usage_notice)\n            elif scale_threshold_notice:\n                display_scale_threshold_notice(self, \"sync\", \"add\", *scale_threshold_notice)\n            else:\n                display_first_run_notice(self, \"sync\", \"add\")\n            return results\n\n        if self.config.llm.config.get(\"enable_vision\"):","sourceCodeStart":828,"sourceCodeEnd":864,"githubUrl":"https://github.com/mem0ai/mem0/blob/001c235229be8795e3834520467bd0d661ed8f34/mem0/memory/main.py#L828-L864","documentation":"Raised as Mem0ValidationError (VALIDATION_003) by Memory.add() when the messages argument is not a str, dict, or list. The SDK is flexible — a bare string is wrapped as [{'role':'user','content': ...}] and a single dict is wrapped in a list — but tuples of dicts, generators, a JSON string containing a list, pandas rows, or None all fail this check. The details payload records the provided Python type name so you can see exactly what arrived.","triggerScenarios":"m.add(({'role':'user','content':'hi'},)) with a tuple instead of list; m.add(None); passing a generator or map object from a streaming pipeline; passing messages=json.dumps(list_of_dicts) (a str that will be treated as one user message, so ensure you pass the list itself); passing a pandas Series of dicts.","commonSituations":"Functions that accept *args and build a tuple; streaming chat UIs accumulating messages in a tuple; JSON transport layers that serialize too early; mocking tests with MagicMock payloads.","solutions":["Convert to list: messages = list(messages) before add().","Pass the Python list of dicts directly, not its JSON serialization.","Normalize with the documented coercion: str -> auto-wrapped, dict -> [dict], list[dict] -> used as-is.","Add a type guard in your wrapper: if not isinstance(messages, (str, dict, list)): raise your own error."],"exampleFix":"# before\nm.add(({\"role\": \"user\", \"content\": \"hi\"},), user_id=\"u1\")\n\n# after\nm.add([{\"role\": \"user\", \"content\": \"hi\"}], user_id=\"u1\")\n# or the shorthand\nm.add(\"hi\", user_id=\"u1\")","handlingStrategy":"type-guard","validationCode":"if isinstance(messages, tuple):\n    messages = list(messages)\nelif isinstance(messages, str):\n    messages = [{\"role\": \"user\", \"content\": messages}]\nif not isinstance(messages, (str, dict, list)):\n    raise TypeError(f\"messages must be str/dict/list, got {type(messages).__name__}\")","typeGuard":"def is_valid_messages(msgs) -> bool:\n    if isinstance(msgs, (str, dict)):\n        return True\n    return isinstance(msgs, list) and all(isinstance(x, dict) for x in msgs)","tryCatchPattern":"from mem0.memory.utils import Mem0ValidationError\ntry:\n    m.add(msgs, user_id=uid)\nexcept Mem0ValidationError as e:\n    if e.error_code == \"VALIDATION_003\":\n        msgs = [msgs] if isinstance(msgs, (str, dict)) else list(msgs)\n        m.add(msgs, user_id=uid)\n    else:\n        raise","preventionTips":["Normalize collections to list early: list(messages) fixes tuples/generators.","Pass Python objects, never json.dumps output, as messages.","Assert the shape in unit tests for wrapper functions."],"tags":["validation","messages","add","typeerror"],"backgroundTag":null,"analyzedSha":"001c235229be8795e3834520467bd0d661ed8f34","analyzedAt":"2026-08-15T01:55:42.685Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}