# 子智能体

Yuxi 的子智能体是 Agent-backed 形态：它仍然是 `agents` 表中的一级 Agent，只是额外带有 `is_subagent=true` 标记，并使用专用后端 `SubAgentBackend`。子智能体不再有独立的创建入口、独立表或独立管理接口。

## 用户视角

### 子智能体能解决什么问题

当任务复杂、需要分工处理时，主 Agent 可以通过 `task` 工具把一个子任务交给子智能体。例如：

- 研究型子任务：聚焦检索和资料整理。
- 评审型子任务：对草稿进行结构和质量审查。
- 领域型子任务：使用指定模型、工具、知识库或 Skills 处理特定领域问题。

### 在哪里创建和编辑

子智能体与普通 Agent 使用同一个管理入口：进入模型配置中的“智能体”管理页，点击新增智能体，并在后端类型中选择 `SubAgentBackend`。

创建和编辑流程与普通 Agent 保持一致：

- 展示信息、共享权限、系统提示词和运行配置都保存在同一份 Agent 配置中。
- 模型、工具、知识库、MCP 和 Skills 仍通过 Agent runtime config 表单配置。
- 子智能体不会出现在聊天页的 Agent 快速切换列表中。
- 子智能体不能再配置或调用其他子智能体。

### 如何让主 Agent 调用子智能体

仅创建子智能体不会让它自动参与运行。需要编辑主 Agent，在 runtime config 的“子智能体”字段中选择允许调用的子智能体。

`subagents` 字段表示当前主 Agent 的允许列表：

- 未选择时表示不启用子智能体。
- 只会调用当前用户可访问且 `is_subagent=true` 的 Agent。
- 每个子智能体使用自己的 `config_json.context`，包括模型、工具、知识库、MCP、Skills 和系统提示词。

这和工具、知识库、MCP、Skills 的默认全量语义不同：这些资源未显式配置时会默认启用当前用户可访问的全部资源，但子智能体必须显式选择。

## 开发者视角

### 数据模型

子智能体复用 `agents` 表，核心字段包括：

| 字段 | 说明 |
|------|------|
| `backend_id` | 子智能体固定使用 `SubAgentBackend` |
| `is_subagent` | 子智能体标记，`SubAgentBackend` 必须对应 `true` |
| `config_json.context` | 子智能体自己的运行配置 |
| `share_config` | 可见性与管理权限，沿用 Agent 共享模型 |

后端会校验 `backend_id` 与 `is_subagent` 一致：普通 Agent 不能伪装成子智能体，`SubAgentBackend` 也不能以普通 Agent 形态保存。子智能体不能被设置为默认 Agent。

### API 与列表语义

子智能体沿用 `/api/agent` CRUD：

- `GET /api/agent` 默认只返回聊天可用的普通 Agent。
- `GET /api/agent?include_subagents=true` 返回管理页需要的完整 Agent 列表。
- 创建或更新 `SubAgentBackend` 时，payload 会携带或推导 `is_subagent=true`。
- 详情、更新和删除仍走同一套 Agent 管理接口，并复用现有权限过滤。

旧的独立 SubAgent 管理链路已经移除，不再维护单独的启停状态、内置初始化或 spec 缓存。

### 运行时调用链

主 Agent 构图时，如果 `context.subagents` 非空，会挂载 Yuxi 的 task middleware。middleware 会把允许的子智能体列表注入模型提示，并暴露一个 `task` 工具。

工具参数为：

```python
class TaskToolSchema(BaseModel):
    description: str
    subagent_type: str
    thread_id: str | None = None
```

`thread_id` 是可选的子智能体线程 ID。新任务不需要填写；如果要继续之前同一个子智能体任务，应使用上一次 `task` 工具结果中的 `子智能体线程 ID`。

执行时的关键流程：

1. 从父 Agent 的 `context.subagents` 读取允许的子智能体 slug。
2. 使用 `AgentRepository` 加载当前用户可见且 `is_subagent=true` 的 Agent。
3. 新任务会为本次调用生成 child checkpoint thread id，例如 `<parent_thread_id>_sub_<slug>_<uuid8>`；续跑任务会校验并复用传入的 `thread_id`。
4. 使用子智能体自己的 `SubAgentContext` 和 `config_json.context` 构建真实 Agent graph。
5. 调用结束后，把子智能体线程 ID 和最终 assistant 文本作为 `task` 工具结果返回给主 Agent。

`SubAgentBackend` 复用普通 Agent 的运行时资源归一化流程，但不会挂载 task middleware；它的 `subagents` 字段隐藏且默认为空，因此不会形成嵌套子智能体调用。

### 文件系统与沙盒作用域

子智能体与主 Agent 共享文件系统时使用拆分作用域：

| 路径/作用域 | 普通 Agent | 子智能体 |
|------|------|------|
| LangGraph checkpoint | 当前 `thread_id` | child `thread_id` |
| `/home/gem/user-data/workspace` | 当前 `uid` 的共享工作区 | 同一 `uid` 的共享工作区 |
| `/home/gem/user-data/uploads` | 当前会话文件作用域 | 父会话 `file_thread_id` |
| `/home/gem/user-data/outputs` | 当前会话文件作用域 | 父会话 `file_thread_id` |
| `/home/gem/skills` | 当前 Agent 的 Skills 作用域 | 子智能体自己的 `skills_thread_id` |

这保证子智能体可以读取父会话上传、产物也会回到父会话 artifacts 中，同时子智能体的 Skills 不会污染主 Agent。

## 常见问题

### 为什么创建了子智能体，主 Agent 仍不会调用？

需要在主 Agent 的 runtime config 中显式选择该子智能体。`subagents` 为空时不会启用 task 工具。

### 为什么聊天 Agent 列表里看不到子智能体？

这是预期行为。子智能体是被主 Agent 调用的后端配置，不是直接进入聊天的 Agent；管理页会使用包含子智能体的列表。

### 子智能体能否继承主 Agent 的模型或工具？

子智能体运行时使用自己的 Agent 配置。确实需要一致时，应在子智能体配置中显式选择相同模型、工具或 Skills；运行时只继承必要的父会话作用域，例如 uploads/outputs。
