* fix(agents): sync agent_name across context/configurable and reject empty soul (#3549)
Two independent issues caused custom agent creation to silently fail:
1. build_run_config only wrote agent_name into one container (configurable
or context), so setup_agent — which reads ToolRuntime.context exclusively
since LangGraph >=1.1.9 — saw agent_name=None and wrote SOUL.md to the
global base_dir instead of users/{user_id}/agents/{name}/. Mirror the
dual-write pattern already used by merge_run_context_overrides and
naming.py so both containers always carry the same value.
2. setup_agent persisted whatever soul string it received, including empty
or whitespace-only content, and still reported success. The frontend
then surfaced an unusable agent and the global default SOUL.md could be
silently overwritten with empty content. Reject empty soul before any
filesystem operation so the model can retry.
Tests:
- test_gateway_services.py: dual-write regressions for both configurable
and context entry paths, explicit-agent-name precedence on both sides,
and a shape-parity test against merge_run_context_overrides.
- test_setup_agent_tool.py: empty/whitespace soul rejection, plus
no-overwrite guarantees for existing global and per-agent SOUL.md.
* Update services.py
* feat(agent): add update_agent tool for in-chat custom-agent self-updates (#2616)
Custom agents had no built-in way to persist updates to their own SOUL.md /
config.yaml from a normal chat — `setup_agent` was only bound during the
bootstrap flow, so when the user asked the agent to refine its description
or personality, the agent would shell out via bash/write_file and the edits
landed in a temporary sandbox/tool workspace instead of
`{base_dir}/agents/{agent_name}/`.
Changes:
- New `update_agent` builtin tool with partial-update semantics (only the
fields you pass are written) and atomic temp-file + os.replace writes so
a failed update never corrupts existing SOUL.md / config.yaml.
- Lead agent now binds `update_agent` in the non-bootstrap path whenever
`agent_name` is set in the runtime context. Default agent (no
agent_name) and bootstrap flow are unchanged.
- New `<self_update>` system-prompt section is injected for custom agents,
instructing them to use `update_agent` — and explicitly NOT bash /
write_file — to persist self-updates.
- Tests: 11 new cases in `tests/test_update_agent_tool.py` covering
validation (missing/invalid agent_name, unknown agent, no fields),
partial updates (soul-only, description-only, skills=[] vs omitted),
no-op detection, atomic-write safety, and AgentConfig round-tripping;
plus 2 new cases in `tests/test_lead_agent_prompt.py` covering the
self-update prompt section.
- Docs: updated backend/CLAUDE.md builtin tools list and tools.mdx
(en/zh) with the new tool description.
* feat(agent): isolate custom agents per user
Store custom agent definitions under the effective user, keep legacy agents readable until migration, and cover API/tool/migration behavior with tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat: consistent write/delete targets & add --user-id to migration
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(setup-agent): prevent data loss when setup fails on existing agent directory
Record whether the agent directory pre-existed before mkdir, and only
run shutil.rmtree cleanup when the directory was newly created during
this call. Previously, any failure would delete the entire directory
including pre-existing SOUL.md and config.yaml.
* fix: address PR review — init variables before try, remove unused result
* style: fix ruff I001 import block formatting in test file
* style: add missing blank lines between top-level definitions in test file