bytedance/deer-flow · error · HTTPException

Agent '{name}' only exists in the legacy shared layout and i

Error message

Agent '{name}' only exists in the legacy shared layout and is not scoped to a user. Run scripts/migrate_user_isolation.py to move legacy agents into the per-user layout before updating.

What it means

409 from PUT `/agents/{name}`: the agent exists only in the legacy shared layout — `user_agent_dir/user/config.yaml` is missing while the shared `agent_dir/config.yaml` exists. Note the guard deliberately checks the config file, not directory existence, because per-user dirs can hold only `memory.json` from chatting with a legacy agent; a bare `.exists()` would silently fork a fresh config into a memory-only dir (issue #3390). Updates are blocked until migration; the db backend has no legacy layout so this is file-backend-only.

Source

Thrown at backend/app/gateway/routers/agents.py:395

        raise HTTPException(status_code=404, detail=f"Agent '{name}' not found")

    def _is_legacy_only_layout() -> bool:
        # Require config.yaml, not bare directory existence — a per-user agent
        # directory can exist containing only memory.json (written the first
        # time this user chats with a legacy shared agent, before this route
        # is ever called). Bare .exists() would miss that case and let this
        # fall through to a silent fork of a brand-new config.yaml/SOUL.md
        # into the memory-only directory instead of blocking (mirrors
        # resolve_agent_dir's guard, see #3390). The db backend has no legacy
        # shared layout, so this file-only guard is a no-op there. The .exists()
        # probes are filesystem IO, so they run off the event loop.
        paths = get_paths()
        agent_dir = paths.user_agent_dir(user_id, name)
        legacy_dir = paths.agent_dir(name)
        return not (agent_dir / "config.yaml").exists() and (legacy_dir / "config.yaml").exists()

    if await asyncio.to_thread(_is_legacy_only_layout):
        raise HTTPException(
            status_code=409,
            detail=(f"Agent '{name}' only exists in the legacy shared layout and is not scoped to a user. Run scripts/migrate_user_isolation.py to move legacy agents into the per-user layout before updating."),
        )

    if "model" in request.model_fields_set:
        _validate_model_exists(request.model)

    try:
        # Update config if any config fields changed
        # Use model_fields_set to distinguish "field omitted" from "explicitly set to null".
        # This is critical for skills where None means "inherit all" (not "don't change").
        fields_set = request.model_fields_set
        config_changed = bool(fields_set & ({"description", "tool_groups", "skills"} | set(_MODEL_BEHAVIOR_FIELDS)))

        updated: dict | None = None
        if config_changed:
            updated = {
                "name": agent_cfg.name,

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Run `scripts/migrate_user_isolation.py` to move legacy agents into the per-user layout, then retry the PUT
  2. Verify migration succeeded by confirming `user_agent_dir/<user>/<name>/config.yaml` now exists
  3. Do not hand-copy config.yaml into the memory-only dir — that recreates the silent-fork bug the guard prevents

Example fix

# before
PUT /agents/old-shared-agent  # 409 legacy layout
# after
python scripts/migrate_user_isolation.py
PUT /agents/old-shared-agent  # 200
Defensive patterns

Strategy: try-catch

Try / catch

try { return await api.updateAgent(name, body); }
catch (e) {
  if (e.status === 409 && /legacy shared layout/.test(e.detail)) {
    showAdminNotice('Run scripts/migrate_user_isolation.py, then retry');
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Upgrading a deployment that predates per-user agent isolation, then editing one of the old shared agents; a user who has chatted with a legacy agent (memory.json written) attempting to update it.

Common situations: Post-upgrade administrations hitting 409 on all pre-existing agents; mixed fleets where only some nodes ran the migration script.

Related errors


AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14). Data as JSON: /api/errors/a8b1a97a8166fbe9. Report an issue: GitHub.