[Bug] MemoryManager.sync() crashes when state_dir.knowledge_dir() falls back outside the Agent workspace
Environment
- Version: v2.1.9 (
2.1.9-9-g3bf04290) - OS: Windows 11 (also reproducible on Linux/macOS — the bug is layout-dependent, not OS-dependent)
- Python: 3.12.13
- Install: source
- Model & channel: deepseek-flash, web
What happened?
MemoryManager.sync() crashes with ValueError whenever the Agent's workspace has no local knowledge/ directory. Because sync() is called from inside search() (manager.py:124-126), this takes the Agent's entire memory retrieval down.
Root cause
sync() collects files from state_dir.knowledge_dir(base=workspace_dir) (manager.py:314-322). That helper is designed to fall back to the shared root when the Agent has no local knowledge/:
# common/state_dir.py:118-133
def _shared_or_own(identity, base, *parts: str) -> Path:
own = _agent_base(identity, base).joinpath(*parts)
if own.exists():
return own
return shared_root().joinpath(*parts) # <-- falls back outside the workspace
But downstream, sync() unconditionally computes a path relative to the workspace root (manager.py:336):
for file_path, source, scope, user_id in files_to_scan:
try:
content = file_path.read_text(encoding='utf-8')
except Exception:
continue
file_hash = MemoryStorage.compute_hash(content)
rel_path = str(file_path.relative_to(workspace_dir_path)) # <-- raises ValueError
When the shared knowledge/ lives outside workspace_dir, relative_to() raises and aborts the whole sync.
The comment at manager.py:316-317 explicitly claims to support exactly this case:
# Resolve through state_dir so an Agent without its own knowledge/
# scans the shared base rather than an empty (or missing) local one.
…but the downstream code does not handle the resulting out-of-workspace paths.
Traceback
Traceback (most recent call last):
File "repro.py", line 17, in main
await mm.sync()
File "agent/memory/manager.py", line 336, in sync
rel_path = str(file_path.relative_to(workspace_dir_path))
File ".../pathlib.py", line 682, in relative_to
raise ValueError(f"{str(self)!r} is not in the subpath of {str(other)!r}")
ValueError: 'C:\Users\Administrator\cow\knowledge\index.md' is not in the subpath
of 'C:\Users\ADMINI~1\AppData\Local\Temp\repro_xxxx'
Note the exception itself is the evidence: the scanned file is under the shared root (C:\Users\Administrator\cow), while workspace_dir is a different tree.
Minimal reproduction
import asyncio, tempfile
from pathlib import Path
from agent.memory.config import MemoryConfig
from agent.memory.manager import MemoryManager
async def main():
tmp = Path(tempfile.mkdtemp(prefix="repro_"))
cfg = MemoryConfig(workspace_root=str(tmp))
mm = MemoryManager(config=cfg) # no local knowledge/ under tmp -> triggers fallback
mm._init_workspace()
(tmp / "memory").mkdir(exist_ok=True)
(tmp / "memory" / "x.md").write_text("# x\nhello\n", encoding="utf-8")
await mm.sync() # raises ValueError
asyncio.run(main())
Run from the repo root with the project's venv. Zero external dependencies.
Affected scenarios
- Any Agent using a non-default workspace without its own
knowledge/— i.e. non-default Agents in multi-Agent setups, isolated deployments, customworkspace_root, and test environments. - Default single-Agent installs are not affected, because there
workspace_dir == shared_root()and the two trees coincide — which is likely why this has gone unnoticed.
Suggested fix (minimal)
Guard the relative-path computation so out-of-workspace files get a stable key instead of crashing:
try:
rel_path = str(file_path.relative_to(workspace_dir_path))
except ValueError:
# Shared knowledge/ resolved by state_dir lives outside this workspace's
# subtree; key it by its own root so hashing/dedup still works.
from common import state_dir
rel_path = str(file_path.relative_to(Path(state_dir.knowledge_dir())))
rel_path is only used as a key for get_file_hash() / update_file_metadata(), so any stable unique key works. As a defensive measure, the try/except around read_text() could also cover relative_to() — but with a logger.warning, not a silent continue, so partial indexing is at least observable.
I'm happy to open a PR implementing the above (≈5 lines) plus a regression test that runs sync() against a workspace without a local knowledge/. Just let me know if you prefer a different approach.
中文简述
当 Agent 的工作区没有本地 knowledge/ 目录时,sync() 会崩溃:
state_dir.knowledge_dir(base=workspace)按设计回退到共享根目录(state_dir.py:118-133),扫出 workspace 之外的文件;- 但
manager.py:336无条件调用file_path.relative_to(workspace_dir),跨根路径直接抛ValueError,整个 sync 中断; - 由于
sync()在search()内被调用,等于该 Agent 的记忆检索整体不可用。
manager.py:316-317 的注释明确声称支持这种回退场景,但下游代码没接住。
默认单 Agent 安装下 workspace_dir == shared_root()、两棵树重合,因此不会暴露;非默认 workspace(多 Agent / 隔离部署 / 测试)必崩。
我已用零依赖脚本稳定复现,并给出了最小修复方案,可直接提 PR。
Source: zhayujie/CowAgent