mem0ai/mem0 · error · Mem0ValidationError

VALIDATION_003

VALIDATION_003

Error message

messages must be str, dict, or list[dict]

What it means

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.

Source

Thrown at mem0/memory/main.py:846

        if normalized_expiration_date is not None:
            processed_metadata["expiration_date"] = normalized_expiration_date

        if memory_type is not None and memory_type != MemoryType.PROCEDURAL.value:
            raise Mem0ValidationError(
                message=f"Invalid 'memory_type'. Please pass {MemoryType.PROCEDURAL.value} to create procedural memories.",
                error_code="VALIDATION_002",
                details={"provided_type": memory_type, "valid_type": MemoryType.PROCEDURAL.value},
                suggestion=f"Use '{MemoryType.PROCEDURAL.value}' to create procedural memories."
            )

        if isinstance(messages, str):
            messages = [{"role": "user", "content": messages}]

        elif isinstance(messages, dict):
            messages = [messages]

        elif not isinstance(messages, list):
            raise Mem0ValidationError(
                message="messages must be str, dict, or list[dict]",
                error_code="VALIDATION_003",
                details={"provided_type": type(messages).__name__, "valid_types": ["str", "dict", "list[dict]"]},
                suggestion="Convert your input to a string, dictionary, or list of dictionaries."
            )

        if agent_id is not None and memory_type == MemoryType.PROCEDURAL.value:
            results = self._create_procedural_memory(messages, metadata=processed_metadata, prompt=prompt)
            scale_threshold_notice = detect_scale_threshold_from_add_result(self, results)
            if temporal_usage_notice:
                display_temporal_usage_notice(self, "sync", "add", *temporal_usage_notice)
            elif scale_threshold_notice:
                display_scale_threshold_notice(self, "sync", "add", *scale_threshold_notice)
            else:
                display_first_run_notice(self, "sync", "add")
            return results

        if self.config.llm.config.get("enable_vision"):

View on GitHub (pinned to 001c235229)

Solutions

  1. Convert to list: messages = list(messages) before add().
  2. Pass the Python list of dicts directly, not its JSON serialization.
  3. Normalize with the documented coercion: str -> auto-wrapped, dict -> [dict], list[dict] -> used as-is.
  4. Add a type guard in your wrapper: if not isinstance(messages, (str, dict, list)): raise your own error.

Example fix

# before
m.add(({"role": "user", "content": "hi"},), user_id="u1")

# after
m.add([{"role": "user", "content": "hi"}], user_id="u1")
# or the shorthand
m.add("hi", user_id="u1")
Defensive patterns

Strategy: type-guard

Validate before calling

if isinstance(messages, tuple):
    messages = list(messages)
elif isinstance(messages, str):
    messages = [{"role": "user", "content": messages}]
if not isinstance(messages, (str, dict, list)):
    raise TypeError(f"messages must be str/dict/list, got {type(messages).__name__}")

Type guard

def is_valid_messages(msgs) -> bool:
    if isinstance(msgs, (str, dict)):
        return True
    return isinstance(msgs, list) and all(isinstance(x, dict) for x in msgs)

Try / catch

from mem0.memory.utils import Mem0ValidationError
try:
    m.add(msgs, user_id=uid)
except Mem0ValidationError as e:
    if e.error_code == "VALIDATION_003":
        msgs = [msgs] if isinstance(msgs, (str, dict)) else list(msgs)
        m.add(msgs, user_id=uid)
    else:
        raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of mem0ai/mem0@001c235229 (2026-08-15). Data as JSON: /api/errors/af348b339947f659. Report an issue: GitHub.