plugin-subtask — 子任务
包名: @aalis/plugin-subtask
源码: packages/plugin-subtask/src/index.ts
概述
为 AI Agent 提供子任务创建与并行协调能力。注册 subtask 工具组,并通过 agent:llm:before / agent:turn:after 中间件实现父/子会话的上下文注入与子任务自动完成。
插件声明
export default definePlugin({
name: '@aalis/plugin-subtask',
subsystem: 'session',
uses: {
tools: optional(tools),
events,
hooks,
logger,
config,
sessionManager: optional(sessionManager),
messageArchive: optional(messageArchive),
agent: optional(agent), // 给子会话写子任务模型(assignModel);工具参数的模型按它的实例认「模型」这一项
sessionSettings: optional(sessionSettings), // 会话设置的管理面:建子会话写下设置、按调用者的身份写子会话的设置(setAs)
document: optional(ownHostConfig), // 「子任务模型」的全局层
backgroundTasks: optional(backgroundTasks), // 子会话在跑的后台任务:有的不到期销毁
},
apply(caps) { /* 见源码 */ },
});注册工具组
subtask
| 工具 | 说明 |
|---|---|
create_subtask | 创建子会话并异步派发任务(不阻塞当前会话);model 参数写条目 id <提供者实例 id>/<模型 id> |
check_subtask | 查询一个或多个子任务的当前状态和结果;状态为 active、completed、error 或 stopped(已停止),汇总里另数已停止的个数。出错的另带 reason(出错原因)。到期销毁了的回它最后的状态与结果一次(destroyed 为真) |
send_to_subtask | 向子任务追加消息(追问/补充指令);已完成或出错的重新进入进行中 |
set_subtask_setting | 改一个直接子任务本会话层的一项设置(只写值,不能恢复继承) |
delete_subtask | 递归删除子任务会话 |
wait_subtasks | 阻塞直到指定子任务全部结束(完成、出错、被停止或会话已不存在)或超时;超时取 timeout_seconds 与 maxWaitMs 中的较小者(事件驱动,非轮询)。被停止的报 stopped,不算完成;出错的另带 reason(出错原因);到期销毁了的同 check_subtask;timedOut 只在等满时还有没结束的才为真。调用被中止(所在的一轮被停止键、禁言、/clear 等停下)时不再等,回 error「回合已中止,不再等」 |
create_subtask、send_to_subtask、set_subtask_setting、delete_subtask 声明 risk: 'dangerous'(2 级加确认):子任务替发起的人在后台接着做,与编排、定时任务、停后台任务同一规则。别人单独发起的一轮没人能批,拒绝;有 owner 在的一轮出审批卡给她批,批过的调用按她执行;她一个人的回合在免确认白名单(restrictedPolicy)里不问,不在就出卡,常要连续派几个并行子任务时把 tool:create_subtask 等加进白名单。check_subtask 与 wait_subtasks 只看不改,2 级、不确认(visibility: 'restricted'),低于 2 级的看不到别人派出的子任务的任务与结果。
除 create_subtask 外的五个工具只认调用所在会话的直接子任务(会话记录的 parentId 是调用所在会话)。别的会话的子任务、根会话与不存在的 id 一律按「子任务不存在」回,不确认存在与否:check_subtask 与 wait_subtasks 对这一项不交出状态、任务与结果,也不等它;send_to_subtask、set_subtask_setting 与 delete_subtask 不执行。
工作方式
create_subtask在session-manager中创建子会话,建之前调会话设置管理面的childMetadata,把父会话此刻每一项适用于子会话的生效值写进子会话自己那一层(见 plugin-session-settings 的「子会话」),之后父会话与上面各层再改都不影响它。子会话的模型按「工具参数model› 父会话此刻生效的「子任务模型」 › 父会话此刻实际在用的模型」取:前两者有值时建好后写进子会话那一层:工具参数按这次调用的授权身份经会话设置管理面的setAs写,与set_subtask_setting同一条路(判 agent「模型」这一项setting:<agent 实例 id>/llm与本会话层的门槛,owner 在操作门槛里调高的照算,权限服务不在时写不进);子任务模型是能改它的人设下的,经 agent 的assignModel写。门槛没过或写失败就删掉刚建的子会话、回错误。都没有时就是随设置写下的那一个。然后发送inbound:message事件触发子任务 agent 处理。子会话中不能再调用create_subtask- 会话设置不在时回错误、不建;要指定模型而 agent 不在时回错误、不建。
model写法不对(不是<提供者实例 id>/<模型 id>)时回错误;指向的模型之后被删掉,这个子任务的回合报「配置指向的模型不存在」 - 派发与
send_to_subtask的追问都是内部注入:source为subtask,userId为parent:<父会话 id>(归档用的来源标记,authority 查不到,等价匿名)。在入站回合里派出的带着派它的那一轮(fromTurn,见 schema-message),不带actor:子任务那一轮的参与者取派它的那次调用的参与者(批过的是批的人,几个人都能做、没经审批的是那几个人,子任务里照同样的逐动作规则判),权限服务按那一轮判这一轮的类别与主体(plugin-authority),不按父会话里别的哪一轮;不在入站回合里派出的(编排的工具节点、mcp-server)不带fromTurn,授权身份走actor(调用的唯一参与者,即 api-tools 的actingIdentity;匿名或几个参与者时不带,子任务同样匿名)。它们不经触发判定、不查回复后冷却;带着source,正文写成指令的也不执行,当普通消息交给子任务,指令的意图确认也不认它们;要求真人当面发起的工具拒绝子任务回合。派发与追问各自成一轮:追问经网关送达(不等),排在子任务在跑的那一轮之后;网关不在时send_to_subtask回错误 - 子任务里需要审批的工具调用按派它的那一轮上浮,规则与主会话相同(plugin-session-server):派它的是人亲手那一轮时能产生审批,推送范围按根会话。要 owner 确认的推到她的 WebUI、终端与 IM 私聊(她用这个机器人账号派的);几个人派出的子任务里有人自己做不了的动作,参与者审批发在父会话所在的群里,能批的人引用它回复。卡与审批消息多写一行「子任务:<名字>」(审批对象的来源链;没取名的显示默认名,见 api-session-manager 的
sessionLabel);WebUI 的卡挂在子会话的工具卡上,父会话的会话列表行与「后台」标签、运行状况里的这个子任务写「等你批准」。定时任务、编排、mcp-server 派的子任务照旧产生不了审批,回「这一轮里没有 owner 的话」那句。子任务等审批时,父会话的wait_subtasks可能先等满时限(默认 300 秒)回timedOut - 子会话没有窗口:她在子任务里说的话不交给平台,不发进父会话所在的群或私聊,只进子会话的历史(WebUI 经会话服务的时间线看),父会话靠子任务的结果与提醒拿到结果
- 会话设置不在时回错误、不建;要指定模型而 agent 不在时回错误、不建。
- 子会话的系统提示由
agent:llm:before中间件注入子任务上下文(任务名、任务指令、回答规则与协作规则) - 父会话侧不改动首条系统提示(避免破坏 provider 前缀缓存),而是在消息列表尾部插入一条独立的 system 消息"活跃子任务提醒",列出进行中、出错、已停止和已完成的子任务;最后一条是 user 消息时插在它之前,否则追加到末尾。每轮先移除上一轮的提醒,再按最新状态重新生成
- 子任务 agent 本轮以
replied结束、回复非空且子会话尚未完成或出错时,agent:turn:after中间件以该回复为结果调用sm.completeSession;这一轮结束时这个子会话还有不在这一轮里、没落定的追问(追问开的一轮只有它自己那一条)时,这一轮的回复不交回、不完成,等追问那一轮;若message-archive服务可用,调用前还会在子会话历史中合成一条report_to_parenttool call 记录及其结果消息,两条都带这一轮的回合 id,连对话回滚这一轮时随它删 - 子会话的收口由 session-manager 做,与根会话同一套:最后一轮出错收口为
error,被停止、静默的收口为completed,并记下这一轮(lastTurn)。被停止的子会话(completed且lastTurn.outcome为aborted)三处都报已停止(stopped),不算完成:check_subtask、wait_subtasks与父会话的子任务提醒。出错的子会话三处都带出lastTurn.error里的原因(工具结果的reason键,提醒里写在名字后面);没有原因时两个工具不带reason,提醒写「(无错误详情)」。不拿result充当原因:出错时它若有值,是此前某一轮回复收口时留下的结果 wait_subtasks监听session:completed/session:updated/session:deleted事件与追问落定等待全部子任务终结;子会话出错、被停止或到期销毁时随即返回,不等到超时。还有没落定的追问的子任务不算终结,check_subtask与父会话的子任务提醒按进行中报:会话管理在它前后两轮之间可能已把状态收口成已完成- 到期销毁:子会话的一轮结束(回复、出错或被停止都算)后闲置满
idleDeleteHours,就连同它的对话记录、各插件按会话存的数据和它下面的子会话一起删掉;追问一次在下一轮结束时重新计时,从没跑过一轮的不计时,重启不丢(按记下的最近一轮结束时间重算)。在飞的一轮与发出还没落定的追问(所在的一轮结束、被放掉、在网关相位被挡下都算落定)由本插件自己记:这期间不销毁,一轮一直不收尾时同样不判到期,追问落定时重新排。子会话有在跑的后台任务(api-tools 的backgroundTasks)时同样不销毁:正常结束(含失败)的由结束通知开的那一轮收尾时重新计时,被停止或中断的不发结束通知,结束时(background:changed)按最近一轮的结束时间重判。删除只在定时器里做,删除时这一轮当场停下(agent 在session:deleted里中止它) - 回报:到期销毁之前,把子任务最后的状态、出错原因、结果与任务写进父会话记录(
metadata.subtask)。父会话经check_subtask、wait_subtasks读到即删,父会话提醒里列出它的那一轮回复或静默收尾后删(那一轮出错或被停止时留着,下一轮照常列出);父会话在它还在时已经取走过同一份结果的不再写。父会话的消息历史被清空时回报一并清掉。每到期销毁一个子会话,父会话在会话列表里被顶到前面一次 - 父会话改子会话的设置:
set_subtask_setting经会话设置管理面的setAs写直接子会话本会话层的一项,与在那个子会话里用/session.set是同一份实现:认项、文字读法(列表与映射写[]、文字传空串即设成空)、门槛(按这次调用的授权身份判,即 api-tools 的actingIdentity:几个人的一轮里经 owner 批过的是 owner)与回执。项写错、不适用或重名时回执列出能改的项。与create_subtask同为 2 级加确认:模型给自己派的子会话放权等于自授,要 owner 确认;owner 配了免确认时只有 owner 参与的回合不弹,没有 owner 参与的回合发不起审批、用不了它 - 清空(
/clear)一个会话时它的子会话一并删除(会话管理做),删除父会话同样连带子会话
配置
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled | boolean | true | 启用子任务工具 |
maxWaitMs | number | 300000 | 单次等待最大时长 (ms) |
model | 模型 | 不设 | 「子任务模型」:可按会话设(适用 owner 会话与房间,1 级能改),按派子任务的会话取,能按群设;create_subtask 的 model 参数压过它;没设时写「不另指定(与本会话相同)」 |
idleDeleteHours | number | 24 | 交回后闲置销毁(小时),不设上限;0 即一轮结束立即销毁 |
相关插件
会话历史读取已拆分到独立插件:plugin-tool-session。
配置校验
enabled 的显式无效类型会报配置错误,避免悄悄启用工具。maxWaitMs 接受数字字符串;0 仍取默认值,负数仍保留现有超时语义。