bytedance/deer-flow · error · ValueError

memory_data.facts

Error message

memory_data.facts

What it means

Thrown by DeerMem's memory-import path when the incoming 'facts' section of memory_data is not a JSON list of objects. Before diffing against current facts, the updater validates that memory_data['facts'] is a list whose entries are all dicts, and refuses anything else. This is a caller-input contract: imported memory must round-trip the shape that get_memory_data() produces.

Source

Thrown at backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/updater.py:833

            raise ValueError("memory_data")
        memory_data = copy.deepcopy(memory_data)
        empty = create_empty_memory()
        for section in ("user", "history"):
            incoming_section = memory_data.get(section, {})
            if not isinstance(incoming_section, dict):
                raise ValueError(f"memory_data.{section}")
            complete_section = copy.deepcopy(empty[section])
            for key, value in incoming_section.items():
                if key in complete_section and isinstance(complete_section[key], dict) and isinstance(value, dict):
                    complete_section[key].update(copy.deepcopy(value))
                else:
                    complete_section[key] = copy.deepcopy(value)
            memory_data[section] = complete_section
        if agent_name is not None and getattr(type(self._storage), "apply_changes", None) is not MemoryStorage.apply_changes:
            current = self.get_memory_data(agent_name, user_id=user_id)
            incoming_facts = copy.deepcopy(memory_data.get("facts", []))
            if not isinstance(incoming_facts, list) or any(not isinstance(fact, dict) for fact in incoming_facts):
                raise ValueError("memory_data.facts")
            for fact in incoming_facts:
                fact["id"] = str(fact.get("id") or f"fact_{uuid.uuid4().hex}")
                fact["confidence"] = _coerce_source_confidence(fact)
            current_by_id = {str(fact.get("id")): fact for fact in current.get("facts", []) if isinstance(fact, dict)}
            incoming_ids = {str(fact.get("id")) for fact in incoming_facts}
            self._storage.apply_changes(
                {
                    "upserts": incoming_facts,
                    "upsertRevisions": {str(fact.get("id")): (int(current_by_id[str(fact.get("id"))].get("revision") or 1) if str(fact.get("id")) in current_by_id else None) for fact in incoming_facts},
                    "deletes": [fact_id for fact_id in current_by_id if fact_id not in incoming_ids],
                    "deleteRevisions": {fact_id: int(fact.get("revision") or 1) for fact_id, fact in current_by_id.items() if fact_id not in incoming_ids},
                    "summaries": {"user": copy.deepcopy(memory_data.get("user", {})), "history": copy.deepcopy(memory_data.get("history", {}))},
                },
                agent_name=agent_name,
                user_id=user_id,
                expected_manifest_revision=int(current.get("revision") or 0),
            )
            return self._storage.load(agent_name, user_id=user_id)

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Pass facts as a list of dicts: memory_data['facts'] = [{'id': ..., 'content': ..., 'category': ..., 'confidence': ...}, ...]
  2. If importing from an export, verify the export was produced by get_memory_data() for the same backend version
  3. Validate the payload shape before calling import (see validationCode)

Example fix

// before
await memory.import_memory_data({
  agent_name: "researcher",
  memory_data: { facts: { content: "likes tea" } },
});
// after
await memory.import_memory_data({
  agent_name: "researcher",
  memory_data: { facts: [{ content: "likes tea" }] },
});
Defensive patterns

Strategy: validation

Validate before calling

def is_valid_import_payload(memory_data: dict) -> bool:
    facts = memory_data.get("facts", [])
    return isinstance(facts, list) and all(isinstance(f, dict) for f in facts)

Try / catch

try:
    memory.import_memory_data(memory_data, agent_name=agent)
except ValueError as e:
    if str(e) == "memory_data.facts":
        raise HTTPException(400, "facts must be a list of objects")
    raise

Prevention

When it happens

Trigger: Calling the memory import API (import_memory_data / the tool that feeds it) with memory_data={'facts': {'id': 'x'}} (dict instead of list), facts being a list of strings/numbers, facts being null, or facts entries like "some fact" instead of {'content': ...} dicts.

Common situations: Hand-written import payloads, exporting from another memory system and mapping 'facts' to a single object, JSON schema drift after upgrading the backend, or a frontend form posting facts as a plain string array.

Related errors


AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14). Data as JSON: /api/errors/30a1e6e3909fa887. Report an issue: GitHub.