bytedance/deer-flow · error · ValueError
agent_name is required to get a fact
Error message
agent_name is required to get a fact
What it means
MemoryStorage.get_fact() requires agent_name because facts live in per-agent memory files (path is derived from agent_name and user_id); there is no global fact lookup. The ValueError fires immediately, before path resolution, locking, or legacy migration, so a failed call has no side effects.
Source
Thrown at backend/packages/harness/deerflow/agents/memory/backends/deermem/deermem/core/storage.py:1197
@staticmethod
def _scope_kwargs(scope: dict[str, str | None]) -> dict[str, str]:
kwargs: dict[str, str] = {}
if scope.get("userId") is not None:
kwargs["user_id"] = str(scope["userId"])
if scope.get("agentName") is not None:
kwargs["agent_name"] = str(scope["agentName"])
return kwargs
def get_fact(
self,
fact_id: str,
*,
user_id: str | None = None,
agent_name: str | None = None,
) -> dict[str, Any] | None:
if agent_name is None:
raise ValueError("agent_name is required to get a fact")
path = self._get_memory_file_path(agent_name, user_id=user_id)
key = self._cache_key(agent_name, user_id=user_id)
legacy_path = self._legacy_agent_memory_path(path, agent_name)
notifications: list[RetrievalNotification] = []
with self._scope_lock(key), _process_file_lock(path.parent / ".memory.lock", float(getattr(self._config, "file_lock_timeout_seconds", 10))):
self._recover_if_needed(path)
if legacy_path.exists():
_, _, notifications = self._migrate_locked(path, agent_name, user_id=user_id, include_global=False)
fact, _ = self._read_fact(path, fact_id, user_id=user_id, agent_name=agent_name)
self._dispatch_retrieval_notifications(notifications, user_id=user_id, agent_name=agent_name)
return copy.deepcopy(fact)
def list_facts(
self,
*,
user_id: str | None = None,
agent_name: str | None = None,
filters: dict[str, Any] | None = None,View on GitHub (pinned to 1dd6ba1acb)
Solutions
- Supply the agent scope: storage.get_fact(fact_id, agent_name=agent_name, user_id=user_id).
- If you only know the fact id, first locate the owning agent with list_facts(agent_name=..., filters={'id': fact_id}) per known agent, or maintain an id->agent index.
- Fix API contracts so fact ids are always handled together with their agent scope.
Example fix
# before fact = storage.get_fact(fact_id, user_id=user_id) # after fact = storage.get_fact(fact_id, agent_name=agent_name, user_id=user_id)
Defensive patterns
Strategy: validation
Validate before calling
if agent_name is None:
raise HTTPException(400, "fact lookup requires agent_name")
fact = storage.get_fact(fact_id, agent_name=agent_name, user_id=user_id) Prevention
- Treat (agent_name, fact_id) as the composite key everywhere facts are referenced.
- Carry agent_name alongside fact_id in search results and UI state.
- Expose fact ids only through APIs that also expose their agent scope.
When it happens
Trigger: storage.get_fact('fact_123') or storage.get_fact('fact_123', user_id=u) with agent_name omitted while routing a generic 'read fact by id' API that assumed global ids.
Common situations: Building a generic fact-inspection endpoint that only receives fact_id; refactoring a caller that previously loaded facts via list_facts(user-only) and now tries get_fact without the agent scope; passing agent_name=None as a sentinel for 'any agent'.
Related errors
- agent_name is required to persist facts
- 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/3355da8392b3580b.
Report an issue: GitHub.