bytedance/deer-flow · error · ValueError
fact.revision must be an integer >= 1
Error message
fact.revision must be an integer >= 1
What it means
fact['revision'] (default 1) must be an int >= 1, with bool explicitly rejected. The revision implements optimistic concurrency per fact: callers echo the revision they read, and the backend verifies it against the stored copy. Fractional, zero, negative, or string revisions break that protocol.
Source
Thrown at backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/storage.py:212
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":
raise ValueError("fact.status must be 'active'; deletion is physical")
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)View on GitHub (pinned to 1dd6ba1acb)
Solutions
- Omit revision for new facts (defaults to 1); echo the exact integer you read from the stored fact for updates.
- Coerce before save: fact['revision'] = int(fact['revision']) and verify >= 1.
- Do not invent revisions client-side - they are compared for equality against stored state, so guessed values cause conflicts (see error 496).
Example fix
# before
memory.save_fact({"content": "...", "revision": "2"})
# after
memory.save_fact({"content": "...", "revision": 2}) Defensive patterns
Strategy: validation
Validate before calling
rev = fact.get("revision", 1)
if isinstance(rev, bool) or not isinstance(rev, int) or rev < 1:
fact["revision"] = max(1, int(rev)) if not isinstance(rev, bool) and isinstance(rev, (int, float)) else 1 Type guard
def is_valid_revision(v: object) -> bool:
return not isinstance(v, bool) and isinstance(v, int) and v >= 1 Prevention
- Omit revision for creates; echo the exact integer read from storage for updates.
- Never compute or guess revisions client-side.
When it happens
Trigger: Saving {'revision': 0} or {'revision': '3'}; computing revision as a float; passing True (bool) after arithmetic on flags.
Common situations: Clients that treat revision as optional metadata and send 0 for 'new'; spreadsheets/CSV exports turning ints into strings; producers copying updatedAt timestamps into revision.
Related errors
- change_set.deleteRevisions must be an object
- change_set.upsertRevisions must be an object
- change_set.upsertRevisions values must be null or integers >
- retrieval fact.id must be a non-empty string
- retrieval fact.content must be a non-empty string
AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14).
Data as JSON: /api/errors/65407a651d6f2280.
Report an issue: GitHub.