mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-06-11 09:55:59 +00:00
fix(agents): require config.yaml in resolve_agent_dir to skip memory-only directories (#3390) (#3481)
When memory is enabled, the first conversation with a legacy shared agent creates a per-user agent directory containing only memory.json (no config.yaml). On the second turn, resolve_agent_dir() returned this incomplete directory, causing load_agent_config() to fail with "Agent config not found". Require config.yaml to exist alongside the directory for both the per-user and legacy paths, so that memory-only directories fall through correctly. This aligns resolve_agent_dir with the existing config.yaml check in list_custom_agents. Refs: https://github.com/bytedance/deer-flow/issues/3390
This commit is contained in:
@@ -67,11 +67,13 @@ def resolve_agent_dir(name: str, *, user_id: str | None = None) -> Path:
|
|||||||
paths = get_paths()
|
paths = get_paths()
|
||||||
effective_user = user_id or get_effective_user_id()
|
effective_user = user_id or get_effective_user_id()
|
||||||
user_path = paths.user_agent_dir(effective_user, name)
|
user_path = paths.user_agent_dir(effective_user, name)
|
||||||
if user_path.exists():
|
# Require config.yaml to confirm this is a genuine agent directory,
|
||||||
|
# not a leftover from memory/storage writes (see #3390).
|
||||||
|
if user_path.exists() and (user_path / "config.yaml").exists():
|
||||||
return user_path
|
return user_path
|
||||||
|
|
||||||
legacy_path = paths.agent_dir(name)
|
legacy_path = paths.agent_dir(name)
|
||||||
if legacy_path.exists():
|
if legacy_path.exists() and (legacy_path / "config.yaml").exists():
|
||||||
return legacy_path
|
return legacy_path
|
||||||
|
|
||||||
return user_path
|
return user_path
|
||||||
|
|||||||
@@ -203,6 +203,79 @@ class TestLoadAgentConfig:
|
|||||||
assert cfg.name == "legacy-agent"
|
assert cfg.name == "legacy-agent"
|
||||||
|
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# 3b. resolve_agent_dir — memory-only directory fallback (#3390)
|
||||||
|
# ===========================================================================
|
||||||
|
|
||||||
|
|
||||||
|
class TestResolveAgentDirMemoryOnlyFallback:
|
||||||
|
"""Regression tests for #3390.
|
||||||
|
|
||||||
|
When memory is enabled, the first conversation creates a user-isolated
|
||||||
|
agent directory containing only ``memory.json`` (no ``config.yaml``).
|
||||||
|
On the next turn ``resolve_agent_dir`` must fall through to the legacy
|
||||||
|
shared layout instead of returning the incomplete user directory.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_user_dir_with_only_memory_falls_back_to_legacy(self, tmp_path):
|
||||||
|
"""User dir has memory.json but no config.yaml → use legacy dir."""
|
||||||
|
from deerflow.config.agents_config import resolve_agent_dir
|
||||||
|
|
||||||
|
# Legacy agent with full config
|
||||||
|
legacy_dir = tmp_path / "agents" / "my-agent"
|
||||||
|
legacy_dir.mkdir(parents=True)
|
||||||
|
(legacy_dir / "config.yaml").write_text("name: my-agent\n", encoding="utf-8")
|
||||||
|
(legacy_dir / "SOUL.md").write_text("legacy soul", encoding="utf-8")
|
||||||
|
|
||||||
|
# User dir created by memory write — no config.yaml
|
||||||
|
user_dir = tmp_path / "users" / "u1" / "agents" / "my-agent"
|
||||||
|
user_dir.mkdir(parents=True)
|
||||||
|
(user_dir / "memory.json").write_text("{}", encoding="utf-8")
|
||||||
|
|
||||||
|
with patch("deerflow.config.agents_config.get_paths", return_value=_make_paths(tmp_path)), patch("deerflow.config.agents_config.get_effective_user_id", return_value="u1"):
|
||||||
|
result = resolve_agent_dir("my-agent", user_id="u1")
|
||||||
|
|
||||||
|
assert result == legacy_dir
|
||||||
|
|
||||||
|
def test_user_dir_with_config_takes_priority(self, tmp_path):
|
||||||
|
"""User dir with config.yaml should still win over legacy."""
|
||||||
|
from deerflow.config.agents_config import resolve_agent_dir
|
||||||
|
|
||||||
|
# Legacy
|
||||||
|
legacy_dir = tmp_path / "agents" / "my-agent"
|
||||||
|
legacy_dir.mkdir(parents=True)
|
||||||
|
(legacy_dir / "config.yaml").write_text("name: my-agent\n", encoding="utf-8")
|
||||||
|
|
||||||
|
# User dir with full config (migrated)
|
||||||
|
user_dir = tmp_path / "users" / "u1" / "agents" / "my-agent"
|
||||||
|
user_dir.mkdir(parents=True)
|
||||||
|
(user_dir / "config.yaml").write_text("name: my-agent\nmodel: gpt-4\n", encoding="utf-8")
|
||||||
|
(user_dir / "memory.json").write_text("{}", encoding="utf-8")
|
||||||
|
|
||||||
|
with patch("deerflow.config.agents_config.get_paths", return_value=_make_paths(tmp_path)), patch("deerflow.config.agents_config.get_effective_user_id", return_value="u1"):
|
||||||
|
result = resolve_agent_dir("my-agent", user_id="u1")
|
||||||
|
|
||||||
|
assert result == user_dir
|
||||||
|
|
||||||
|
def test_load_config_falls_back_when_user_dir_is_memory_only(self, tmp_path):
|
||||||
|
"""End-to-end: load_agent_config works when user dir only has memory.json."""
|
||||||
|
config_dict = {"name": "my-agent", "description": "Legacy agent", "model": "deepseek-v3"}
|
||||||
|
_write_agent(tmp_path, "my-agent", config_dict)
|
||||||
|
|
||||||
|
# Simulate memory write creating user dir without config
|
||||||
|
user_dir = tmp_path / "users" / "u1" / "agents" / "my-agent"
|
||||||
|
user_dir.mkdir(parents=True)
|
||||||
|
(user_dir / "memory.json").write_text("{}", encoding="utf-8")
|
||||||
|
|
||||||
|
with patch("deerflow.config.agents_config.get_paths", return_value=_make_paths(tmp_path)), patch("deerflow.config.agents_config.get_effective_user_id", return_value="u1"):
|
||||||
|
from deerflow.config.agents_config import load_agent_config
|
||||||
|
|
||||||
|
cfg = load_agent_config("my-agent", user_id="u1")
|
||||||
|
|
||||||
|
assert cfg.name == "my-agent"
|
||||||
|
assert cfg.model == "deepseek-v3"
|
||||||
|
|
||||||
|
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
# 4. load_agent_soul
|
# 4. load_agent_soul
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user