#3175·CowAgent

[Bug] MemoryManager.sync() crashes when state_dir.knowledge_dir() falls back outside the Agent workspace

Author: NBSW002Created Sep 17, 2026Updated Sep 17, 2026

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, custom workspace_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。