Skip to content

功能规格说明:子代理支持 ​

创建日期:2024-12-19

用户场景与测试 (必填) ​

用户故事:加载和解析用户创建的子代理(优先级:P1) ​

作为开发者,我希望 Wave Agent SDK 自动发现和加载我手动创建的子代理配置文件,以便我可以使用具有领域专业知识和适当工具访问权限的专门 AI 助手。

为什么是这个优先级:这是子代理系统的基础 - SDK 必须能够在任何委托发生之前发现、加载和解析用户创建的子代理配置。SDK 专注于读取能力,而用户通过首选编辑器管理创建。

独立测试:可以通过在正确位置手动创建带有有效 YAML frontmatter 的子代理配置文件并验证它可以被 SDK 发现、加载和解析来完整测试。

验收场景:

  1. 假设 我在 .wave/agents/ 中手动创建了带有有效 YAML frontmatter 的 markdown 文件,当 SDK 扫描子代理,则 子代理被发现并可用于使用
  2. 假设 我在 ~/.wave/agents/ 中有一个用户级子代理,当 SDK 加载配置,则 该子代理在所有项目中可用
  3. 假设 我有同名的项目和用户子代理,当 SDK 加载配置,则 项目级子代理优先
  4. 假设 我手动配置了带有特定工具的子代理,当 子代理被调用,则 它只能访问这些指定的工具
  5. 假设 我手动配置了带有特定模型的子代理,当 子代理被调用,则 它使用指定模型进行响应
  6. 假设 子代理的 frontmatter 用 YAML 块标量写多行值(>/>-/|/|- 等),当 SDK 加载配置,则 description/model 等字段必须是折叠/保留后的真实文本(不含 >、|、- 这类指示符),续行不得被解析成独立的 frontmatter 键;子代理列表与自动委托判据看到的描述都是解析后的结果

用户故事:自动任务委托(优先级:P2) ​

作为用户,我希望 Wave Agent 自动识别任务何时匹配子代理的专业领域并适当委托,这样我无需手动指定使用哪个子代理就能获得专业帮助。

为什么是这个优先级:这提供了使子代理真正有用的智能自动化 - 用户不必为每个任务手动管理使用哪个子代理。

独立测试:可以通过为子代理配置清晰的专业描述并发出匹配这些描述的任务,然后验证自动委托发生来测试。

验收场景:

  1. 假设 我配置了 test-runner 子代理,当 我要求"修复失败的测试",则 任务自动委托给 test-runner 子代理
  2. 假设 我有多个具有不同专业领域的子代理,当 我描述一个任务,则 根据任务描述和子代理描述选择最合适的子代理
  3. 假设 我有一个描述中包含 "PROACTIVELY" 的子代理,当 提到任何相关任务,则 系统优先委托给该子代理
  4. 假设 没有子代理匹配我的任务描述,当 我请求帮助,则 主代理直接处理任务

用户故事:显式子代理调用(优先级:P3) ​

作为用户,我希望在输入框里用 @ 直接点名一个子代理,以便把这条任务明确交给它,而不是完全依赖主代理自己判断该用谁。

为什么是这个优先级:自动委托(P2)覆盖大多数情形;显式点名是"我知道该用谁"时的兜底,也是用户对子代理选择唯一的确定控制手段。低于自动委托,是因为它要求用户先知道有哪些子代理、且在一个已经很长的 @ 下拉里再分出一类条目。

独立测试:在输入框输入 @、选中子代理 Explore、发送,验证主代理这一轮以 subagent_type=Explore 调用了 Agent 工具(对话里出现该子代理的工具块)。

归属与形态:

  • 入口是现有 @ 下拉,不新增交互概念:子代理条目与文件、IDE 上下文条目同列,排在文件搜索结果之前、IDE 固定条目之后。选中后插入原子标签(不可在标签内编辑,退格整体删除),标签可见文本是 @<名称>。
  • 两条正文拼写,一套解析:webview 侧落地为 [@agent:<名称>],与 [@file:…]、[@ide:…] 同族——草稿还原与消息气泡复用同一套标记解析;CLI 没有标签机制,插入 @agent:<名称>。SDK 用一条正则同时认这两种拼写。
  • 判定在 SDK,不在 UI:宿主只负责下发候选、把用户选中的标记原样放进消息;"这条消息点名了哪个子代理"由 SDK 在每轮首回合判定并注入提醒。因此 CLI、桌面端、插件端、远控页四端语义一致,手打标记与从下拉里选中等价。
  • 软指定,不是硬绑定:注入的是 system-reminder(点名该子代理、要求把这条任务委派给它),最终怎么切分上下文、是否并行多次调用仍由主代理决定。与 Claude Code 的 agent_mention 同一语义(@agent-<name> 文本 + "用户希望调用该代理"提醒)。

验收场景:

  1. 假设 我已配置了多个子代理,当 我在输入框输入 @,则 下拉里除文件外还列出各子代理(名称为主文案、description 为说明),且排在文件搜索结果之前
  2. 假设 @ 下拉里已有子代理条目,当 我继续输入过滤文本,则 子代理条目按名称与描述过滤,文件搜索照常进行,两者互不影响
  3. 假设 我选中某个子代理,当 标签插入输入框,则 它以原子标签形式呈现(可见 @<名称>),发送后正文里含 [@agent:<名称>](CLI 为 @agent:<名称>)
  4. 假设 消息里点名了已注册的子代理,当 主代理开始这一轮,则 上下文中出现一条提醒,点名该子代理并要求以 subagent_type=<名称> 委派,主代理据此调用 Agent 工具
  5. 假设 消息里点名的名字不是任何已注册子代理(手打错名或子代理已被删除),当 主代理开始这一轮,则 提醒改为列出可用子代理名,不报错、不中断这一轮
  6. 假设 同一条消息里点名了两个不同子代理,当 主代理开始这一轮,则 两个都被点名到同一条提醒里
  7. 假设 带子代理标签的消息已发送,当 它在消息历史里重放,则 标签仍然可读(内容来自消息正文本身,不重新查询子代理列表)
  8. 假设 带子代理标签的内容留在输入框里未发送,当 切走会话再切回(草稿恢复),则 标签仍在,发送时照常按点名处理
  9. 假设 子代理在会话进行中被删除或被插件卸载,当 我再次输入 @,则 该子代理不再出现在候选里

用户故事:子代理上下文隔离(优先级:P2) ​

作为用户,我希望每个子代理维护与主对话分离的独立上下文窗口,以便专业代理可以专注于其特定任务而不受不相关对话历史的影响。

为什么是这个优先级:上下文隔离对子代理的有效性至关重要 - 它防止子代理被不相关的对话历史混淆,并允许它们保持对专业领域的专注。

独立测试:可以通过进行长对话,然后委托给子代理并验证它不引用不相关的先前对话元素来测试。

验收场景:

  1. 假设 我与主代理有长对话,当 我将任务委托给子代理,则 子代理仅使用其任务的相关上下文运行
  2. 假设 我按顺序使用多个子代理,当 每个子代理运行,则 它们不互相干扰上下文
  3. 假设 子代理完成任务,当 控制权返回主代理,则 主代理可以访问子代理的结果但维护自己的上下文

用户故事:子代理活动显示(优先级:P2) ​

作为用户,我希望在主对话流中看到子代理活动的反映,以便我无需单独的消息历史视图就能追踪它们的进度。

为什么是这个优先级:清晰的子代理活动反馈对用户理解和调试至关重要。用户需要实时了解子代理正在运行什么工具。

独立测试:可以通过触发子代理并验证 Agent 工具块的 short result 动态更新工具名称和 token 使用情况来测试。

验收场景:

  1. 假设 子代理被触发,当 它处理任务,则 Agent 工具块在其 shortResult 中显示实时更新
  2. 假设 子代理正在运行工具,当 我查看消息列表,则 我在 Agent 工具块的 shortResult 中看到最近执行的子代理工具
  3. 假设 子代理任务完成,当 任务结束,则 最终文本结果作为工具输出返回并显示在主对话中
  4. 假设 子代理任务完成,当 我查看工具块,则 我看到任务完成摘要,包括使用的总 token 和执行的工具

用户故事:子代理 worktree 隔离(优先级:P1) ​

作为开发者,我希望让子代理在它自己的 git worktree 中运行,以便多个子代理(或子代理与主会话)并行修改同一个仓库时不会互相踩文件、也不会污染主会话的工作区。

为什么是这个优先级:并行委派是子代理最主要的用法,但当前所有子代理共享父会话的工作目录——两个子代理并行改同一批文件会互相覆盖,一个子代理跑构建或改配置也会直接影响主会话。对齐 Claude Code 的 isolation: worktree。

独立测试:在 .wave/agents/ 创建带 isolation: worktree 的子代理并派它改文件,验证写入落在 .wave/worktrees/<name> 且父工作目录零改动;完成后有改动时 worktree 被保留、结果里给出路径/分支/变更统计;再派一个只读任务,验证无改动时 worktree 与临时分支被自动删除。

验收场景:

  1. 假设 子代理定义文件的 frontmatter 含 isolation: worktree,当 主代理通过 Agent 工具委派它,则 该子代理的文件与命令工具(Read/Write/Edit/Bash/Grep/Glob、后台任务)都以新 worktree 目录为工作目录。
  2. 假设 隔离子代理启动,当 worktree 被创建,则 目录为 <主仓库>/.wave/worktrees/<name>、分支为 worktree-<name>、基线按 worktree.baseRef(fresh 默认基于 origin/<默认分支>,head 基于父会话当前 HEAD),与 CLI -w / EnterWorktree 同一套规则。
  3. 假设 配置了 WorktreeCreate hook,当 隔离子代理启动,则 由 hook 创建(路径取第一个成功 hook 的 stdout);未配置则走 git;新建 worktree 时复制 .wave/settings.local.json 与 .worktreeinclude 列出的文件。
  4. 假设 隔离子代理在 worktree 内运行,当 它用 Write/Edit 修改父仓库(worktree 之外)的文件,则 被拒绝并返回 worktree 安全拦截消息(与父会话 worktree 同一条规则,bypassPermissions 下同样生效)。
  5. 假设 子代理结束且本次新建的 worktree 内没有任何改动(0 个变更文件、0 个新提交),当 收尾执行,则 worktree 目录与 worktree-<name> 分支被自动删除,结果中说明已清理。
  6. 假设 子代理结束且 worktree 内有改动(未提交文件或新提交),当 收尾执行,则 worktree 与分支被保留,结果中给出 worktree 路径、分支名、变更文件数与提交数,并说明这些改动没有应用到父工作目录。
  7. 假设 子代理失败、被停止(TaskStop / 后台任务停止)或被清理,当 收尾执行,则 同样按「有改动保留、无改动删除」处理,且收尾失败不改变原有的成功/失败结果(只记日志)。
  8. 假设 变更无法判定(git status / rev-list 执行失败),当 收尾执行,则 保留 worktree(fail-safe,不删),结果提示路径与无法判定。
  9. 假设 隔离子代理运行中,当 它调用 EnterWorktree 或 ExitWorktree,则 工具被拒绝——worktree 生命周期由父级管理,子代理不得自行进出或删除。
  10. 假设 父会话本身已在一个 worktree 中,当 派发隔离子代理,则 子代理 worktree 仍创建在主仓库的 .wave/worktrees/ 下,默认基线为 origin/<默认分支>(与父会话 worktree 无嵌套关系)。
  11. 假设 工作目录不是 git 仓库且未配置 WorktreeCreate hook,当 派发隔离子代理,则 委派失败并返回明确错误,不得静默退回父工作目录执行。

用户故事:按次指定 worktree 隔离(优先级:P2) ​

作为用户,我希望在不改子代理定义文件的前提下,把某一次委派放进独立 worktree,以便临时并行跑几单改代码的任务。

为什么是这个优先级:定义文件只能给「某个子代理」永久开启隔离;临时并行(例如一次派三个 general-purpose 改不同模块)如果每次都要先改定义文件就不实用。对齐 Claude Code「让 Claude 用 worktree 跑子代理」的动态路径。

独立测试:对不带 isolation 的 general-purpose 子代理,主代理调用 Agent 时传 isolation: "worktree",验证该次运行在独立 worktree 中、收尾规则与上一条故事一致;再不带该参数调用一次,验证仍跑在父工作目录。

验收场景:

  1. 假设 子代理定义没有 isolation,当 Agent 调用带 isolation: "worktree",则 该次运行使用独立 worktree,其余行为不变。
  2. 假设 子代理定义含 isolation: worktree,当 Agent 调用不带该参数,则 仍然隔离(定义是下限,调用参数不能关闭)。
  3. 假设 Agent 调用传入除 "worktree" 以外的 isolation 值,则 入参校验失败并返回错误,不启动子代理。
  4. 假设 子代理以后台方式运行(run_in_background),当 它完成或被停止,则 隔离与收尾规则与前台一致,完成通知里包含 worktree 摘要。

用户故事:定义文件的上下文开关与工具黑名单(优先级:P2) ​

作为子代理作者,我希望像 Claude Code 那样在 frontmatter 里声明「这个子代理不加载项目记忆」和「排除掉某几个工具」,这样一次性搜索类子代理不必携带主会话的 AGENTS.md,工具面也不必逐个列白名单。

为什么是这个优先级:白名单只能加、不能减——将来新增任何一个工具,白名单式子代理都看不到它(内置 Explore 因此至今抓不了网页);而项目记忆对一次性的搜索子代理是纯粹的成本。字段名取 Wave 语义(omitAgentsMd:Wave 有 AGENTS.md,「CLAUDE.md」只是回退),同时把 Claude Code 的 omitClaudeMd 认作别名——用户从 Claude Code 抄过来的子代理文件不会静默变成「照样加载项目记忆」;disallowedTools 沿用 Claude Code 原名。

独立测试:写一个带 omitAgentsMd: true 与 disallowedTools: [Edit, Write] 的子代理,派发后验证首条消息与 system prompt 里没有 AGENTS.md 内容、Edit/Write 不在工具列表且调用返回 not found,其余工具(含 Bash、Read)正常。

验收场景:

  1. 假设 子代理 frontmatter 含 omitAgentsMd: true(或 CC 别名 omitClaudeMd: true),当 子代理启动并发出第一次请求,则 不注入项目 AGENTS.md(含 CLAUDE.md 回退)、用户记忆以及 .wave/rules 的无条件规则;不带该字段(或值不是 true)时行为不变,照常注入。
  2. 假设 子代理 frontmatter 含 disallowedTools([Edit, Write]、Edit, Write、块列表三种写法均可),当 子代理初始化自己的 ToolManager,则 这些工具不注册、调用返回 Tool '<name>' not found,未列出的工具照常可用。
  3. 假设 同一个子代理既声明 tools 又声明 disallowedTools,当 它初始化,则 先按白名单取交集再减黑名单;两边都出现的工具一律不可用。
  4. 假设 子代理只声明 disallowedTools、没有 tools,当 主模型读 Agent 工具描述,则 该子代理的工具摘要写成 All except <名单>,不得写成误导性的 *。
  5. 假设 disallowedTools 里写了平台不存在的工具名,当 子代理初始化,则 该项被忽略,解析与启动都不失败。

边界情况 ​

  • 子代理配置文件的 YAML frontmatter 无效时会怎样?
  • 系统如何处理循环委托(子代理尝试委托回主代理或另一个子代理)?
  • 子代理配置了不存在的工具时会怎样?
  • 当项目和用户目录都包含同名代理时系统如何表现?
  • 子代理指定的模型不可用或无效时会怎样?
  • 系统如何处理存在但没有内容或缺少必需字段的子代理文件?
  • 用户请求的子代理存在但缺少访问所需工具的权限时会怎样?
  • 子代理尝试访问 Task 管理工具(TaskCreate/TaskGet/TaskUpdate/TaskList)时会怎样?→ 系统必须拒绝:这些工具不注册到子代理的 ToolManager,子代理无法调用,防止其修改主代理的共享任务列表。
  • 隔离子代理撞上已存在的同名 worktree 时会怎样?→ 复用该目录(isNew: false)、不执行 post-creation 文件复制,避免覆盖既有本地状态;复用的 worktree 不是本次创建的,收尾一律保留并在结果里报告变更统计。
  • 隔离子代理保留的 worktree 由谁清理?→ 本期不做定时清扫(Claude Code 的 cleanupPeriodDays 清扫不在范围内)。结果里给出路径与分支,由用户或后续会话用 git worktree remove(必要时加 --force)与 git branch -D worktree-<name> 手工清理。
  • hook-based worktree 收尾时没有配 WorktreeRemove hook 会怎样?→ 保留 worktree 并记 warning(沿用现有语义),结果里给出路径。
  • 子代理在 worktree 内 checkout 了别的分支会怎样?→ 收尾只删 worktree-<name>,其它分支与其提交保留(沿用现有语义)。
  • 子代理用 Bash cd 到主仓库改文件会怎样?→ 不在拦截范围(现有 worktree 安全拦截只覆盖 Write/Edit),与父会话 worktree 会话一致,属已知边界而非本次新增缺口。
  • 隔离子代理的指令文件(AGENTS.md、记忆规则)从哪来?→ 来自父会话(子容器解析到父级 MemoryRuleManager),不读取 worktree 内的版本,与 Claude Code「指令文件取自主对话」一致。
  • 点名提醒会不会进子代理的上下文?→ 不会。子代理不能再派子代理(层级限制 1 层),点名只对主代理这一轮生效。
  • 子代理名字里出现冒号(插件子代理注册为 <插件名>:<名称>)会破坏解析吗?→ 不会:标记解析按「首字符是字母、其后是字母/数字/下划线/连字符/冒号」取名字,冒号本就在字符集内。
  • @agent:<名称> 会被当成文件提及去触发条件规则吗?→ 不会:@ 文件提及的扫描必须排除 agent: 前缀,否则会拿 agent 这个词去查文件。
  • 用户手打或从别处粘贴 @agent:<名称>、不经 @ 下拉,还生效吗?→ 生效,与从下拉里选中完全等价(判定在 SDK 侧,只看正文标记)。

子代理 Worktree 隔离(2026-02-27 添加) ​

  • 配置面:定义文件 frontmatter 新增 isolation,唯一有效值 "worktree"(其它值忽略、不报错,与 Claude Code 一致);Agent 工具新增可选 isolation 参数,仅能在定义未开启时「加开」,不能关闭定义里已开启的隔离。
  • 生效范围:本轮只覆盖 Agent 工具派发的子代理(前台与后台)。Skill 的 context: fork 与 Workflow 的 agent() 暂不启用(Workflow 的结构化结果契约需要单独设计 worktree 摘要的承载位)。
  • 工作目录:隔离子代理的 MessageManager / AIManager / BackgroundTaskManager 以及子容器的 Workdir 槽都指向 worktree 路径;子容器注册自己的 WorktreeSession(含 originalCwd = 父工作目录),使 worktree 安全拦截与 EnterWorktree 的「已在 worktree 中」判断都基于子代理自己的状态。
  • 变更判定基线:以 worktree 创建后自身的 HEAD 为基线统计新提交,而不是父会话 HEAD——fresh 基线从 origin/<默认分支> 起,可能与父 HEAD 有前后差异,用父 HEAD 会把基线自带的历史误算成子代理的提交。
  • 收尾时机:正常完成、失败、被 TaskStop / 后台任务停止、实例清理四条路径统一走同一个收尾函数;收尾是 best-effort,失败只记日志,不覆盖原有结果。
  • 不做的事:不自动把 worktree 改动合并/应用到父工作目录(与 Claude Code 一致,只报告路径与统计);不做定时清扫。

子代理失败如何返回主代理(2026-11-02 添加) ​

主代理从子代理那里只拿到两样东西:子代理最后一条助手消息的正文,以及「连一条助手消息都没有」时的报错。

  • 已产出内容后失败(模型报错、hook 拦截、中途异常,但子代理已经说过话):按正常完成返回,内容只含 text 块。失败本身不进主代理的上下文——它记在子代理日志与子代理面板里,由人看。
  • 一条助手消息都没有:工具调用失败,主代理拿到 Agent delegation failed: <原因>;隔离子代理还会附上 worktree 收尾摘要,说明有没有留下文件。
  • 提示(notice)永不跨边界:轮次计数、错误提醒这类旁注是给人看的,既不当作子代理的产出,也不当作失败原因。
  • 后台子代理不同:结束时一条 <task-notification> 明确带 status 为 failed 和原因原文,主代理下一轮就会读到。
  • 对齐 Claude Code 的同步分支(AgentTool.tsx):有助手消息就按完成返回,没有才把错误抛出去。

子代理生命周期(2025-01-10 添加) ​

此功能为子代理提供细粒度消息回调并确保严格的生命周期管理。

关键变更 ​

  1. SubagentManagerCallbacks 接口:子代理事件的新接口:

    • onSubagentUserMessageAdded
    • onSubagentAssistantMessageAdded
    • onSubagentAssistantContentUpdated
    • onSubagentToolBlockUpdated
    • onSubagentMessagesChange
  2. 重构的 SubagentManager:

    • 使用 callbacks: SubagentManagerCallbacks 转发事件
    • 通过 cleanupInstance(subagentId) 确保子代理实例清理
  3. 移除持久子代理 UI 状态:

    • SubagentBlock 组件及其关联的 subagentMessages 上下文状态从 CLI 中移除
    • 活动现在通过 Agent 工具块的 shortResult 报告

澄清 ​

2024-12-19 会议 ​

  • 问:子代理触发时,子代理消息块是否应在主对话流中内联显示? → 答:否,它在主对话流中表示为 Agent 工具块
  • 问:当子代理产生错误或未能完成任务时系统应如何处理任务委托? → 答:返回错误消息给主代理。子代理是工具调用,因此可以返回成功内容或错误消息(2026-11-02 修订:只有「一条助手消息都没有」时才返回错误;已产出内容的按完成返回,失败只记日志与面板,见上一节)
  • 问:区分子代理活动的视觉指示器应该是什么? → 答:Agent 工具块的 shortResult 动态更新工具执行和 token 使用信息
  • 问:Wave Agent SDK 是否应提供创建子代理配置文件的功能? → 答:否,SDK 仅读取/加载/解析用户创建的子代理文件