bytedance/deer-flow · error · ValueError
fact.source.type must be a string
Error message
fact.source.type must be a string
What it means
When fact['source'] is a dict, its 'type' member (defaulting to 'unknown') must be a str; a non-string type (int, list, dict) raises ValueError. String sources are auto-converted to {'type': ..., 'threadId': ...}, so this error only fires for dict sources with a bad type value.
Source
Thrown at backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/storage.py:224
normalized["status"] = "active"
normalized["scope"] = copy.deepcopy(scope)
_require_string_list(normalized, "topics")
_require_string_list(normalized, "consolidatedFrom")
revision = normalized.get("revision", 1)
if isinstance(revision, bool) or not isinstance(revision, int) or revision < 1:
raise ValueError("fact.revision must be an integer >= 1")
source = normalized.get("source")
if isinstance(source, str):
if source in {"manual", "consolidation", "import", "unknown"}:
normalized["source"] = {"type": source, "threadId": None}
else:
normalized["source"] = {"type": "conversation", "threadId": source}
elif not isinstance(source, dict):
normalized["source"] = {"type": "unknown", "threadId": None}
else:
normalized["source"].setdefault("type", "unknown")
if not isinstance(normalized["source"].get("type"), str):
raise ValueError("fact.source.type must be a string")
if normalized["source"].get("threadId") is not None and not isinstance(normalized["source"].get("threadId"), str):
raise ValueError("fact.source.threadId must be a string or null")
normalized["title"] = _fact_title(normalized)
now = utc_now_iso_z()
if existing is None:
normalized.setdefault("createdAt", now)
normalized.setdefault("updatedAt", normalized["createdAt"])
normalized["revision"] = revision
else:
existing_revision = existing.get("revision")
if not isinstance(existing_revision, int) or existing_revision < 1:
raise MemoryStorageCorruption(f"Stored fact {normalized['id']!r} has an invalid revision")
if revision != existing_revision:
raise MemoryFactRevisionConflict(f"Expected fact {normalized['id']!r} revision {revision}, found {existing_revision}")
normalized["createdAt"] = existing.get("createdAt") or normalized.get("createdAt") or now
comparison_keys = {"revision", "updatedAt"}
incoming_material = {key: value for key, value in normalized.items() if key not in comparison_keys}
existing_material = {key: value for key, value in existing.items() if key not in comparison_keys}View on GitHub (pinned to 1dd6ba1acb)
Solutions
- Set source as a plain string ('manual' | 'consolidation' | 'import' | 'unknown' or a thread id) and let the backend normalize it.
- If using the dict form, ensure source['type'] is a str literal from the documented set.
- Validate imported source dicts with a type guard before saving.
Example fix
# before
memory.save_fact({"content": "...", "source": {"type": 3, "threadId": None}})
# after
memory.save_fact({"content": "...", "source": {"type": "manual", "threadId": None}}) Defensive patterns
Strategy: type-guard
Validate before calling
src = fact.get("source")
if isinstance(src, dict):
t = src.get("type", "unknown")
src["type"] = t if isinstance(t, str) else str(t)
elif src is not None and not isinstance(src, str):
fact["source"] = "unknown" Type guard
def has_valid_source_type(fact: dict) -> bool:
src = fact.get("source")
return not isinstance(src, dict) or isinstance(src.get("type", "unknown"), str) Prevention
- Prefer the string form of source and let the backend normalize it.
- Validate imported source dicts against the {'type': str, 'threadId': str|None} shape.
When it happens
Trigger: Saving {'source': {'type': 1}}, {'source': {'type': ['manual']}}, or a source dict built from unvalidated import JSON.
Common situations: Imports mapping a numeric source enum into type; producers nesting the wrong object; LLM tool output with structured source objects of mixed types.
Related errors
- fact.category must be a string
- fact.{field} must be a list of strings
- fact.content must be a string
- fact.source.threadId must be a string or null
- retrieval fact.id must be a non-empty string
AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14).
Data as JSON: /api/errors/935bfdda0bb168ac.
Report an issue: GitHub.