Appearance
功能规格说明:子代理支持
创建日期:2024-12-19
用户场景与测试 (必填)
用户故事:加载和解析用户创建的子代理(优先级:P1)
作为开发者,我希望 Wave Agent SDK 自动发现和加载我手动创建的子代理配置文件,以便我可以使用具有领域专业知识和适当工具访问权限的专门 AI 助手。
为什么是这个优先级:这是子代理系统的基础 - SDK 必须能够在任何委托发生之前发现、加载和解析用户创建的子代理配置。SDK 专注于读取能力,而用户通过首选编辑器管理创建。
独立测试:可以通过在正确位置手动创建带有有效 YAML frontmatter 的子代理配置文件并验证它可以被 SDK 发现、加载和解析来完整测试。
验收场景:
- 假设 我在
.wave/agents/中手动创建了带有有效 YAML frontmatter 的 markdown 文件,当 SDK 扫描子代理,则 子代理被发现并可用于使用 - 假设 我在
~/.wave/agents/中有一个用户级子代理,当 SDK 加载配置,则 该子代理在所有项目中可用 - 假设 我有同名的项目和用户子代理,当 SDK 加载配置,则 项目级子代理优先
- 假设 我手动配置了带有特定工具的子代理,当 子代理被调用,则 它只能访问这些指定的工具
- 假设 我手动配置了带有特定模型的子代理,当 子代理被调用,则 它使用指定模型进行响应
- 假设 子代理的 frontmatter 用 YAML 块标量写多行值(
>/>-/|/|-等),当 SDK 加载配置,则description/model等字段必须是折叠/保留后的真实文本(不含>、|、-这类指示符),续行不得被解析成独立的 frontmatter 键;子代理列表与自动委托判据看到的描述都是解析后的结果
用户故事:自动任务委托(优先级:P2)
作为用户,我希望 Wave Agent 自动识别任务何时匹配子代理的专业领域并适当委托,这样我无需手动指定使用哪个子代理就能获得专业帮助。
为什么是这个优先级:这提供了使子代理真正有用的智能自动化 - 用户不必为每个任务手动管理使用哪个子代理。
独立测试:可以通过为子代理配置清晰的专业描述并发出匹配这些描述的任务,然后验证自动委托发生来测试。
验收场景:
- 假设 我配置了 test-runner 子代理,当 我要求"修复失败的测试",则 任务自动委托给 test-runner 子代理
- 假设 我有多个具有不同专业领域的子代理,当 我描述一个任务,则 根据任务描述和子代理描述选择最合适的子代理
- 假设 我有一个描述中包含 "PROACTIVELY" 的子代理,当 提到任何相关任务,则 系统优先委托给该子代理
- 假设 没有子代理匹配我的任务描述,当 我请求帮助,则 主代理直接处理任务
用户故事:显式子代理调用(优先级: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>文本 + "用户希望调用该代理"提醒)。
验收场景:
- 假设 我已配置了多个子代理,当 我在输入框输入
@,则 下拉里除文件外还列出各子代理(名称为主文案、description为说明),且排在文件搜索结果之前 - 假设
@下拉里已有子代理条目,当 我继续输入过滤文本,则 子代理条目按名称与描述过滤,文件搜索照常进行,两者互不影响 - 假设 我选中某个子代理,当 标签插入输入框,则 它以原子标签形式呈现(可见
@<名称>),发送后正文里含[@agent:<名称>](CLI 为@agent:<名称>) - 假设 消息里点名了已注册的子代理,当 主代理开始这一轮,则 上下文中出现一条提醒,点名该子代理并要求以
subagent_type=<名称>委派,主代理据此调用Agent工具 - 假设 消息里点名的名字不是任何已注册子代理(手打错名或子代理已被删除),当 主代理开始这一轮,则 提醒改为列出可用子代理名,不报错、不中断这一轮
- 假设 同一条消息里点名了两个不同子代理,当 主代理开始这一轮,则 两个都被点名到同一条提醒里
- 假设 带子代理标签的消息已发送,当 它在消息历史里重放,则 标签仍然可读(内容来自消息正文本身,不重新查询子代理列表)
- 假设 带子代理标签的内容留在输入框里未发送,当 切走会话再切回(草稿恢复),则 标签仍在,发送时照常按点名处理
- 假设 子代理在会话进行中被删除或被插件卸载,当 我再次输入
@,则 该子代理不再出现在候选里
用户故事:子代理上下文隔离(优先级:P2)
作为用户,我希望每个子代理维护与主对话分离的独立上下文窗口,以便专业代理可以专注于其特定任务而不受不相关对话历史的影响。
为什么是这个优先级:上下文隔离对子代理的有效性至关重要 - 它防止子代理被不相关的对话历史混淆,并允许它们保持对专业领域的专注。
独立测试:可以通过进行长对话,然后委托给子代理并验证它不引用不相关的先前对话元素来测试。
验收场景:
- 假设 我与主代理有长对话,当 我将任务委托给子代理,则 子代理仅使用其任务的相关上下文运行
- 假设 我按顺序使用多个子代理,当 每个子代理运行,则 它们不互相干扰上下文
- 假设 子代理完成任务,当 控制权返回主代理,则 主代理可以访问子代理的结果但维护自己的上下文
用户故事:子代理活动显示(优先级:P2)
作为用户,我希望在主对话流中看到子代理活动的反映,以便我无需单独的消息历史视图就能追踪它们的进度。
为什么是这个优先级:清晰的子代理活动反馈对用户理解和调试至关重要。用户需要实时了解子代理正在运行什么工具。
独立测试:可以通过触发子代理并验证 Agent 工具块的 short result 动态更新工具名称和 token 使用情况来测试。
验收场景:
- 假设 子代理被触发,当 它处理任务,则
Agent工具块在其shortResult中显示实时更新 - 假设 子代理正在运行工具,当 我查看消息列表,则 我在
Agent工具块的shortResult中看到最近执行的子代理工具 - 假设 子代理任务完成,当 任务结束,则 最终文本结果作为工具输出返回并显示在主对话中
- 假设 子代理任务完成,当 我查看工具块,则 我看到任务完成摘要,包括使用的总 token 和执行的工具
用户故事:子代理 worktree 隔离(优先级:P1)
作为开发者,我希望让子代理在它自己的 git worktree 中运行,以便多个子代理(或子代理与主会话)并行修改同一个仓库时不会互相踩文件、也不会污染主会话的工作区。
为什么是这个优先级:并行委派是子代理最主要的用法,但当前所有子代理共享父会话的工作目录——两个子代理并行改同一批文件会互相覆盖,一个子代理跑构建或改配置也会直接影响主会话。对齐 Claude Code 的 isolation: worktree。
独立测试:在 .wave/agents/ 创建带 isolation: worktree 的子代理并派它改文件,验证写入落在 .wave/worktrees/<name> 且父工作目录零改动;完成后有改动时 worktree 被保留、结果里给出路径/分支/变更统计;再派一个只读任务,验证无改动时 worktree 与临时分支被自动删除。
验收场景:
- 假设 子代理定义文件的 frontmatter 含
isolation: worktree,当 主代理通过Agent工具委派它,则 该子代理的文件与命令工具(Read/Write/Edit/Bash/Grep/Glob、后台任务)都以新 worktree 目录为工作目录。 - 假设 隔离子代理启动,当 worktree 被创建,则 目录为
<主仓库>/.wave/worktrees/<name>、分支为worktree-<name>、基线按worktree.baseRef(fresh默认基于origin/<默认分支>,head基于父会话当前 HEAD),与 CLI-w/EnterWorktree同一套规则。 - 假设 配置了
WorktreeCreatehook,当 隔离子代理启动,则 由 hook 创建(路径取第一个成功 hook 的 stdout);未配置则走 git;新建 worktree 时复制.wave/settings.local.json与.worktreeinclude列出的文件。 - 假设 隔离子代理在 worktree 内运行,当 它用 Write/Edit 修改父仓库(worktree 之外)的文件,则 被拒绝并返回 worktree 安全拦截消息(与父会话 worktree 同一条规则,
bypassPermissions下同样生效)。 - 假设 子代理结束且本次新建的 worktree 内没有任何改动(0 个变更文件、0 个新提交),当 收尾执行,则 worktree 目录与
worktree-<name>分支被自动删除,结果中说明已清理。 - 假设 子代理结束且 worktree 内有改动(未提交文件或新提交),当 收尾执行,则 worktree 与分支被保留,结果中给出 worktree 路径、分支名、变更文件数与提交数,并说明这些改动没有应用到父工作目录。
- 假设 子代理失败、被停止(
TaskStop/ 后台任务停止)或被清理,当 收尾执行,则 同样按「有改动保留、无改动删除」处理,且收尾失败不改变原有的成功/失败结果(只记日志)。 - 假设 变更无法判定(
git status/rev-list执行失败),当 收尾执行,则 保留 worktree(fail-safe,不删),结果提示路径与无法判定。 - 假设 隔离子代理运行中,当 它调用
EnterWorktree或ExitWorktree,则 工具被拒绝——worktree 生命周期由父级管理,子代理不得自行进出或删除。 - 假设 父会话本身已在一个 worktree 中,当 派发隔离子代理,则 子代理 worktree 仍创建在主仓库的
.wave/worktrees/下,默认基线为origin/<默认分支>(与父会话 worktree 无嵌套关系)。 - 假设 工作目录不是 git 仓库且未配置
WorktreeCreatehook,当 派发隔离子代理,则 委派失败并返回明确错误,不得静默退回父工作目录执行。
用户故事:按次指定 worktree 隔离(优先级:P2)
作为用户,我希望在不改子代理定义文件的前提下,把某一次委派放进独立 worktree,以便临时并行跑几单改代码的任务。
为什么是这个优先级:定义文件只能给「某个子代理」永久开启隔离;临时并行(例如一次派三个 general-purpose 改不同模块)如果每次都要先改定义文件就不实用。对齐 Claude Code「让 Claude 用 worktree 跑子代理」的动态路径。
独立测试:对不带 isolation 的 general-purpose 子代理,主代理调用 Agent 时传 isolation: "worktree",验证该次运行在独立 worktree 中、收尾规则与上一条故事一致;再不带该参数调用一次,验证仍跑在父工作目录。
验收场景:
- 假设 子代理定义没有
isolation,当Agent调用带isolation: "worktree",则 该次运行使用独立 worktree,其余行为不变。 - 假设 子代理定义含
isolation: worktree,当Agent调用不带该参数,则 仍然隔离(定义是下限,调用参数不能关闭)。 - 假设
Agent调用传入除"worktree"以外的isolation值,则 入参校验失败并返回错误,不启动子代理。 - 假设 子代理以后台方式运行(
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)正常。
验收场景:
- 假设 子代理 frontmatter 含
omitAgentsMd: true(或 CC 别名omitClaudeMd: true),当 子代理启动并发出第一次请求,则 不注入项目AGENTS.md(含CLAUDE.md回退)、用户记忆以及.wave/rules的无条件规则;不带该字段(或值不是true)时行为不变,照常注入。 - 假设 子代理 frontmatter 含
disallowedTools([Edit, Write]、Edit, Write、块列表三种写法均可),当 子代理初始化自己的ToolManager,则 这些工具不注册、调用返回Tool '<name>' not found,未列出的工具照常可用。 - 假设 同一个子代理既声明
tools又声明disallowedTools,当 它初始化,则 先按白名单取交集再减黑名单;两边都出现的工具一律不可用。 - 假设 子代理只声明
disallowedTools、没有tools,当 主模型读Agent工具描述,则 该子代理的工具摘要写成All except <名单>,不得写成误导性的*。 - 假设
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 收尾时没有配
WorktreeRemovehook 会怎样?→ 保留 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 添加)
此功能为子代理提供细粒度消息回调并确保严格的生命周期管理。
关键变更
SubagentManagerCallbacks 接口:子代理事件的新接口:
onSubagentUserMessageAddedonSubagentAssistantMessageAddedonSubagentAssistantContentUpdatedonSubagentToolBlockUpdatedonSubagentMessagesChange
重构的 SubagentManager:
- 使用
callbacks: SubagentManagerCallbacks转发事件 - 通过
cleanupInstance(subagentId)确保子代理实例清理
- 使用
移除持久子代理 UI 状态:
SubagentBlock组件及其关联的subagentMessages上下文状态从 CLI 中移除- 活动现在通过
Agent工具块的shortResult报告
澄清
2024-12-19 会议
- 问:子代理触发时,子代理消息块是否应在主对话流中内联显示? → 答:否,它在主对话流中表示为
Agent工具块 - 问:当子代理产生错误或未能完成任务时系统应如何处理任务委托? → 答:返回错误消息给主代理。子代理是工具调用,因此可以返回成功内容或错误消息(2026-11-02 修订:只有「一条助手消息都没有」时才返回错误;已产出内容的按完成返回,失败只记日志与面板,见上一节)
- 问:区分子代理活动的视觉指示器应该是什么? → 答:
Agent工具块的shortResult动态更新工具执行和 token 使用信息 - 问:Wave Agent SDK 是否应提供创建子代理配置文件的功能? → 答:否,SDK 仅读取/加载/解析用户创建的子代理文件