bytedance/deer-flow · error · ValueError
agent_name is required to persist facts
Error message
agent_name is required to persist facts
What it means
Raised by FileMemoryStorage.save() when the payload contains a non-empty facts list but agent_name is None. Facts in this backend are always scoped to an agent (per-agent memory files keyed by agent_name and user_id); there is no global fact repository. The guard runs before any locking or I/O, so nothing is written when it fires.
Source
Thrown at backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/storage.py:1087
Repository callers should prefer ``apply_changes`` to avoid even that
full comparison scan.
"""
path = self._get_memory_file_path(agent_name, user_id=user_id)
key = self._cache_key(agent_name, user_id=user_id)
lock_path = path.parent / ".memory.lock"
notifications: list[RetrievalNotification] = []
try:
if not isinstance(memory_data, dict):
raise ValueError("memory_data must be an object")
if agent_name is not None and "facts" not in memory_data:
raise ValueError("memory_data.facts is required for an agent full save")
facts_raw = memory_data.get("facts", [])
if not isinstance(facts_raw, list):
raise ValueError("memory_data.facts must be a list")
if any(not isinstance(fact, dict) for fact in facts_raw):
raise ValueError("memory_data.facts must contain only fact objects")
if agent_name is None and facts_raw:
raise ValueError("agent_name is required to persist facts")
with self._scope_lock(key), _process_file_lock(lock_path, float(getattr(self._config, "file_lock_timeout_seconds", 10))):
self._recover_if_needed(path)
ids = [str(fact.get("id") or "") for fact in facts_raw]
if len(ids) != len(set(ids)):
raise ValueError("Duplicate fact ids are not allowed")
old_ids = set(self._agent_entries(path, agent_name, user_id=user_id)) if agent_name is not None else set()
summaries = None
if agent_name is None:
summaries = {"user": memory_data.get("user", {}), "history": memory_data.get("history", {})}
_, notifications = self._commit_changes_locked(
path,
user_id=user_id,
agent_name=agent_name,
upserts=copy.deepcopy(facts_raw),
deletes=sorted(old_ids - set(ids)),
summaries=summaries,
expected_revision=expected_revision,
)View on GitHub (pinned to 1dd6ba1acb)
Solutions
- Pass the agent_name you originally loaded the facts with: storage.save(memory_data, agent_name='my-agent', user_id=user_id).
- If the save is intentionally user-global (summaries only), strip facts: save({'user': ..., 'history': ...}, user_id=...) with no 'facts' key.
- Add a call-site assertion that agent_name is not None whenever facts are present.
Example fix
# before storage.save(memory_data, user_id=user_id) # memory_data contains facts # after storage.save(memory_data, agent_name=agent_name, user_id=user_id)
Defensive patterns
Strategy: validation
Validate before calling
facts = memory_data.get("facts") or []
if facts and agent_name is None:
raise HTTPException(400, "agent_name required when facts are present")
storage.save(memory_data, agent_name=agent_name, user_id=user_id) Prevention
- Make agent_name a required keyword in wrappers that save fact-bearing documents.
- Load and save with the same scope parameters in the same function.
- Strip 'facts' from payloads destined for user-global summary saves.
When it happens
Trigger: storage.save({'facts': [...fact dicts...]}) with agent_name omitted/None (a user-global summary-only save), while still including facts in the payload.
Common situations: Copy-pasting an agent-scoped save call into a user-summary sync path; a caller that loads a full document (which includes facts) and re-saves it without the agent_name it loaded it with; refactoring that drops the agent_name parameter.
Related errors
- agent_name is required to get a fact
- agent_name is required to upsert a fact
- agent_name is required to delete a fact
- 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/ebc1514d7d9d1da5.
Report an issue: GitHub.