bytedance/deer-flow · error · ValueError
fact must be an object
Error message
fact must be an object
What it means
The fact normalization entrypoint (_normalize_fact) requires its fact argument to be a dict (JSON object). Passing a list, string, number, or None raises ValueError('fact must be an object') before any field is read. It is the outermost type gate for every fact written to memory.
Source
Thrown at backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/storage.py:186
if not isinstance(value, list) or any(not isinstance(item, str) for item in value):
raise ValueError(f"fact.{field} must be a list of strings")
fact[field] = value
def _normalize_fact(
fact: dict[str, Any],
*,
scope: dict[str, str | None],
existing: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""Validate one fact and derive its per-item revision.
The shared JSON revision protects the multi-file transaction. The fact's
own revision protects one Markdown object when a disjoint transaction is
safely rebased after that shared revision changed.
"""
if not isinstance(fact, dict):
raise ValueError("fact must be an object")
normalized = copy.deepcopy(fact)
normalized["id"] = str(normalized.get("id") or f"fact_{uuid.uuid4().hex}")
# Validate the id through the canonical path builder's public contract.
if not normalized["id"] or any(character not in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-" for character in normalized["id"]):
raise ValueError("fact.id may contain only letters, numbers, '_' and '-'")
normalized["schemaVersion"] = 2
if not isinstance(normalized.get("content"), str):
raise ValueError("fact.content must be a string")
normalized["content"] = normalized["content"].strip()
if not normalized["content"]:
raise ValueError("fact.content must not be empty")
_normalize_category(normalized)
confidence = normalized.get("confidence", 0.5)
if isinstance(confidence, bool) or not isinstance(confidence, (int, float)) or not 0 <= confidence <= 1:
raise ValueError("fact.confidence must be a number between 0 and 1")
normalized["confidence"] = float(confidence)
status = normalized.get("status", "active")
if status != "active":View on GitHub (pinned to 1dd6ba1acb)
Solutions
- Parse before saving: fact = json.loads(raw) if isinstance(raw, str) else raw, and check isinstance(fact, dict).
- Skip or log-and-continue when the extraction step yields None or a non-object, instead of forwarding it to storage.
- If you meant to save many facts, loop over the list and save each dict element individually.
Example fix
# before memory.save_fact(raw_fact_json) # raw_fact_json is a str # after memory.save_fact(json.loads(raw_fact_json))
Defensive patterns
Strategy: type-guard
Validate before calling
import json
if isinstance(fact, (bytes, str)):
fact = json.loads(fact)
if not isinstance(fact, dict):
return # nothing to save Type guard
from typing import Any
def is_fact_object(value: Any) -> bool:
return isinstance(value, dict) Try / catch
try:
store.save(fact)
except ValueError as exc:
if "must be an object" in str(exc):
return # extraction produced nothing; not an error condition
raise Prevention
- Parse JSON payloads once, at the boundary, and assert the expected envelope shape.
- Treat a null extraction result as 'skip', never forward it to storage.
When it happens
Trigger: Calling save/upsert with a JSON string instead of a parsed dict (e.g. json.dumps applied twice), a list of facts where one is expected, or None from an upstream extraction step that found nothing.
Common situations: LLM extraction pipelines returning null on 'no memories found' and the caller forwarding it; double serialization bugs; passing the whole response envelope instead of response['fact'].
Related errors
- retrieval fact.id must be a non-empty string
- retrieval fact.content must be a non-empty string
- unsupported FTS5 retrieval mode: {mode}
- retrieval category filter must be a string
- fact.category must be a string
AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14).
Data as JSON: /api/errors/e86bf0eeb4cfbda0.
Report an issue: GitHub.