# Sub-Agent 子代理系统

PyClaw 的子代理系统让主 agent 把自包含的任务委派给专用人格（persona）：每个人格
拥有独立的上下文窗口、专属模型、工具白名单。每个人格就是一个带 YAML 前置元数据
的 Markdown 文件，无需安装插件、无需编写类、无需重新构建。

> **当前状态**：子代理基础设施于 2026-05-18 上线。PyClaw 自身**不打包**任何 persona
> 文件（保持 MIT 许可证清洁），用户自行编写或导入。

## TL;DR — 五分钟快速上手

1. 把清单文件放到五个发现目录之一：

   ```
   <workspace>/agents/<name>.md          ← 项目本地，最高优先级
   <workspace>/.agents/agents/<name>.md  ← 项目级（跨生态标准）
   <workspace>/.claude/agents/<name>.md  ← 项目级（Claude Code 兼容）
   ~/.openclaw/agents/<name>.md          ← managed（PyClaw 用户共享）
   ~/.agents/agents/<name>.md            ← personal 跨生态 fallback
   ```

2. 编写一个最小清单：

   ```markdown
   ---
   name: oracle
   description: 复杂架构与疑难调试的深度推理顾问。
   ---
   你是 Oracle。先复述问题、列出约束、逐步推理，最后把发现回传给主 agent。
   ```

3. 重启 PyClaw。主 agent 现在就有 `sub_agent` 工具了。在聊天里说：
   `请咨询 oracle 关于 X 的问题`。

4. 查看已安装的人格：

   ```
   /agents list
   /agents info oracle
   /agents reload   # 新放入 .md 后重新扫描
   ```

## 为什么需要子代理

单一的 770 行 agent 主循环在每次工具调用、每次记忆命中、每次用户来回时都会累积
上下文。复杂任务到第 20 轮以上时，prompt cache 污染和 context budget 压力会
明显降低推理质量。子代理通过**每次委派独立的全新上下文**解决这个问题：

- **架构 / 调试专家**（"oracle"）—— 在难题出现时启动，把发现回传给主 agent
- **代码库研究员**（"explore"）—— 并行启动做 grep / read / 阅读理解，
  返回简洁的 file:line 列表
- **外部文档研究员**（"librarian"）—— 启动后抓取 / 搜索 / 消化外部库文档

业界趋同的实现模式（Claude Code `Task`、OpenCode `task` + `agent`、Roo Code
custom modes、Gemini CLI `invoke_agent`）都是这一形态：用 YAML frontmatter
+ Markdown 文件定义人格。

## Manifest Schema 清单契约

PyClaw 的 `AgentManifest` 是跨 agent 生态 SKILL.md / agent-frontmatter 标准的
**严格子集**。必填字段：

- `name: str` —— 小写字母 / 数字 / 连字符，1–64 字符，**必须等于**文件名
  （去掉 `.md`）。例：`oracle.md` → `name: oracle`。
- `description: str` —— 1–1024 字符。主 agent 用它判断是否调用。要具体：
  不要写"代码评审员"，而要写"评审 PR diff 的 Python 类型安全、安全漏洞、
  SOP 合规性；为每个问题给出 file:line"。

可选字段：

- `model: str | null` —— provider/model id（如 `anthropic/claude-opus-4-7`）。
  覆盖 `pyclaw.json` 中 `subagent.model_routing` 的设置。
- `allowed_tools: list[str]` —— 工具名白名单；不设置时，子代理继承父代理
  能访问的所有工具（除了**始终禁用的**记忆工具——见下文）。
- `max_turns: int` —— 子代理循环迭代上限。默认 30。
- `timeout_seconds: float` —— wall-clock 超时（硬上限 1800s）。默认 300。
- `tools: list[str]` —— `allowed_tools` 的别名（Anthropic 风格 frontmatter）。

Pydantic `extra = "ignore"` 配置意味着跨生态字段如 `mode`、
`disable-model-invocation`、`paths`、`hooks` 都会静默通过。PyClaw 实际识别的
字段就是上面列出的；其他字段有文档但不强制执行。

### 示例：只读代码评审员

```markdown
---
name: code-reviewer
description: 评审 PR diff 的 Python 类型安全、安全问题、SOP 违规。每个问题给出 file:line。返回结构化清单。
model: anthropic/claude-opus-4-7
allowed_tools: [read, grep, glob]
max_turns: 8
timeout_seconds: 180
---
你是资深 Python 代码评审员，深谙：
- 类型安全（无 `Any`、无未注明理由的 `# type: ignore`）
- 安全（无硬编码密钥、无 SSRF、无 SQL 注入）
- SOP 合规（符合 CLAUDE.md 的团队约定）

输出格式：
- 一行一个发现：`<file>:<line> — <严重度> — <描述>`
- 末尾给出 `OK to merge: yes/no`
```

## 继承契约

主 agent 调用 `sub_agent(agent_name="oracle", task="...")` 时，executor 会
基于父 ToolContext 构造新的 ToolContext，**按引用共享** 7 个字段，**零覆盖
能力**：

| 字段 | 是否继承 | 原因 |
|---|---|---|
| `permission_tier` | 是（严格相等） | 子代理无法升级（read-only 父代理永远不会派生 yolo 子代理） |
| `sandbox_policy` | 是（同实例） | Sprint 3 的 srt / Seatbelt / bwrap+seccomp 同样包裹子代理的每个 `bash` 调用 |
| `workspace_path` | 是 | 子代理在同一 workspace 边界内读写 |
| `abort` (asyncio.Event) | 是（同实例） | 父 abort 在 ~2s 内传递到子代理循环 |
| `user_id`, `role`, `user_profile` | 是 | 审计链路不中断 |
| `subagent_depth` | 递增 | 用于 depth-1 强制限制 |
| `session_id` | 替换 | `<父>#sub:<uuid7>`，便于审计日志关联 |

**记忆继承是只读的**：4 层记忆搜索结果会作为 `<memory>` XML 注入到子代理的
*system prompt*，但三个记忆写工具（`memorize`、`forget`、`update_working_memory`）
在 executor 层被屏蔽，**与 `manifest.allowed_tools` 无关**。即使清单声明
`allowed_tools: [memorize, ...]` 也无法绕过 —— 子代理把发现回传给主 agent，
由主 agent 决定记忆什么。

## 权限层级 —— 严格继承，禁止升级

按 `tool-approval-tiers` 能力规范，子代理严格继承父代理的有效权限层：

- `read-only` 父代理 → 子代理运行在 `read-only`。子代理循环内的 write-class
  工具调用按 Sprint 1 规则自动拒绝。
- `approval` 父代理 → 子代理运行在 `approval`。子代理循环内的工具调用走同一个
  `ToolApprovalHook` 实例，作用域为子代理的 `session_id`。
- `yolo` 父代理 → 子代理运行在 `yolo`。

`SubAgentTool` 自身的 `tool_class="write"`，因为父代理无法分类子代理会传递
调用什么。这意味着：

- `read-only` 层下，`sub_agent` 调用**自动拒绝**且不弹窗。Read-only session
  无法 fan out 到可能开销巨大的子代理推理。
- `approval` 层下，`sub_agent` 评估通道的 `tools_requiring_approval` 列表。
  运维可把 `"sub_agent"` 加到该列表，让派生需要用户批准。

详见 [permissions guide](./permissions.md)，了解完整的层级模型以及子代理
调用如何嵌入 Sprint 3 的 per-call effective-tier 解析路径。

## Sandbox 共享

Sprint 3 的 `SrtPolicy`（macOS Seatbelt / Linux bwrap+seccomp）以**零新代码**
包裹子代理循环内的每次 `bash` 调用，因为 `sandbox_policy` 是按引用共享的。
父代理若运行在 `sandbox.policy = "srt"`，每个子代理的 `bash` 调用都走同一个
`srt` wrapper。详见 [sandbox guide](./sandbox.md)。

`pyclaw.json` 中 `production_require_sandbox = true` 时，`NoSandboxPolicy`
拒绝加载 —— 子代理继承与主代理同样严格的 floor。

## 配置

`pyclaw.json` 顶层 `subagent` 块（所有字段均可选）：

```json
{
  "subagent": {
    "enabled": true,
    "workspaceSubagentsDir": "agents",
    "projectSubagentsDir": ".agents/agents",
    "projectClaudeSubagentsDir": ".claude/agents",
    "personalSubagentsDir": "~/.agents/agents",
    "managedSubagentsDir": "~/.openclaw/agents",
    "modelRouting": {
      "oracle": "anthropic/claude-opus-4-7",
      "explore": "anthropic/claude-haiku-4-5"
    },
    "defaultMaxTurns": 30,
    "defaultTimeoutSeconds": 300,
    "maxDepth": 1
  }
}
```

`model_routing` 让你按 persona 指定模型。解析顺序：
`manifest.model > settings.modelRouting[name] > settings.agent.defaultModel`。
高频简单 persona 用便宜模型（Haiku、Llama），把 Opus 留给 oracle 这类深度
推理。

`maxDepth = 1` 意味着子代理本身不能再派生子代理。V1 默认严格；未来如观察到
合理递归用例可升高。

## 运维：`/agents` 命令

```
/agents list           # 按 source 标签分组的 persona 表 + 实际生效模型
/agents info oracle    # 完整清单 dump + 实际生效模型 + 文件路径
/agents reload         # 重新扫描全部 5 个发现目录（idle 防护）
```

`/agents reload` 在子代理调用进行中时拒绝执行，避免运行时 registry 变更。

## License 注意事项（关键）

PyClaw 是 MIT 许可证。PyClaw 任何分发包都**不包含 persona 文件**。persona
从哪里来？

1. **自己写** —— 最常见，最快。
2. **从 MIT 来源导入** —— `obra/superpowers` 是 MIT 许可的精选 agent
   skills/personas 集合；把相关 `.md` 文件拖入 `~/.openclaw/agents/`。
3. **依据个人非商业例外条款手写** —— 某些 source-available 集合（如
   OhMyOpenAgent 在 Sustainable Use License v1.0 下）允许个人非商业使用；
   你可以受其结构启发手写 persona，但不能再分发它们的文件。

驱动约束：OhMyOpenAgent 的 Sustainable Use License v1.0 禁止
"derivative-to-compete distribution"。PyClaw 选择"只发基础设施，零打包
persona"，保持 PyClaw 干净的 MIT 可发布性。

## 向前兼容（A → B）

本次发布是 option A'（基于文件的发现 + `register(source=...)` 的向前兼容
税）。未来的 `add-plugin-protocol` 变更（option B）通过以下方式扩展为统一
插件系统：

1. 增加第 6 个发现 source —— `("plugin:<name>", iter)`。
2. 插件 loader 调用 `registry.register(name, manifest, source=f"plugin:{name}")`。

本次变更付出的 5 LOC 税（公开的 `register()` API + `source` 参数 +
`subagent_depth` 字段）让插件触发条件（≥3 个第三方插件请求或 PyClaw 用户
≥ 10k）成熟时，option B 是零返工增量。详见
[ROADMAP](../../DailyWork/planning/ROADMAP.md)（私有）。

## 排错

**清单未出现在 `/agents list`**：

- 检查文件名是否与 frontmatter `name` 匹配（区分大小写）。`oracle.md`
  必须 `name: oracle`，不能是 `Oracle` 或 `oracles`。
- 检查文件以 `.md` 结尾。
- 检查它在目录顶层，不是嵌套在子目录里。
- 运行 `/agents reload`（或重启 PyClaw）。
- 检查日志中 `subagent.discovery.parse_error` 警告。

**子代理超时返回截断输出**：

返回内容会以 `[Sub-agent timed out at <duration>s with <turns> turns]\n`
开头，让主 agent 看到截断信号。审计日志记 `outcome: "timeout"`。要么
提高 `manifest.timeout_seconds`，要么简化任务。

**子代理调用 `memorize` 被拒**：

这是设计 —— 见上文"记忆继承是只读的"。让子代理返回发现，主 agent 来
调用 `memorize`。

**`read-only` 父代理下子代理无法 `bash`**：

这是设计。Read-only 层对所有 write-class 工具调用自动拒绝，与谁调用无关。
要允许写，父代理必须运行在 `approval` 或 `yolo`。

**跨生态字段被静默忽略**（`disable-model-invocation`、`mode`、`paths`、
`hooks`）：

PyClaw 的 `extra = "ignore"` Pydantic 配置丢弃未知字段。识别清单见上文。
如果需要 PyClaw 识别新字段，请提 issue —— 通常是 `AgentManifest` 一行加。

## 审计日志 Schema

每次子代理派生在 `pyclaw.audit.subagent` logger 上发出一行 JSON：

```json
{
  "event": "subagent_invocation",
  "event_type": "subagent_spawn",
  "ts": "2026-05-18T19:30:45Z",
  "subagent_name": "oracle",
  "subagent_source": "managed",
  "parent_session_id": "ses_abc",
  "subagent_depth": 1,
  "outcome": "success",
  "turns": 12,
  "tokens_input": 5432,
  "tokens_output": 1234,
  "duration_seconds": 23.4,
  "model_used": "anthropic/claude-opus-4-7",
  "tier_inherited": "approval"
}
```

子代理循环内的工具调用在原有 `pyclaw.audit.tool_approval` channel 上携带
4 个增强字段（`subagent_name`、`subagent_source`、`parent_session_id`、
`subagent_depth`），与 Sprint 1/3 字段（`tier`、`decision`、`tier_source`、
`forced_server`、`user_id`、`role`、`sandbox_backend`）并存。

`outcome` 枚举：`success`、`timeout`、`error`、`depth_exceeded`、
`tool_whitelist_violation`、`aborted`、`max_turns_exceeded`。

## 参见

- [permissions guide](./permissions.md) —— 层级继承如何与子代理调用交互
- [sandbox guide](./sandbox.md) —— `srt` floor 如何作用于子代理 `bash` 调用
- [skill-hub 兼容性](./skill-hub-compatibility.md) —— 子代理 persona 与
  skill 的关系（不同关注点：skills 打包知识 + 工具依赖；sub-agents 打包
  人格 + 隔离）
- [English version](../en/subagents.md)
