GPU_GUARD_MONOREPO/docs/superpowers/specs/2026-04-18-session-subagent-design.md
2026-05-21 11:20:19 +08:00

13 KiB
Raw Blame History

GPU Guard 会话内 Subagent 设计

日期2026-04-18

1. 背景

当前 netaclaw 存在两条普通 Agent 执行入口:

  • WebSocket 聊天入口:gateway/server.ts
  • HTTP 聊天入口:service/agent_executor.ts + controller/chat.ts

同时,系统已具备:

  • 普通会话内的 todo 任务规划能力
  • 独立的 crew 编排体系

本次目标是为普通 Agent 会话增加类似 Hermes 的会话内 subagent 能力,但必须满足以下架构原则:

  • 不复用 crew_* 数据表和 /crew 监控体系
  • 不让 WebSocket 与 HTTP 两条入口行为分叉
  • 不让实时态与历史恢复态使用不同批次锚点
  • 不让普通会话只在 prompt 层“声明可委派”,而运行时没有可执行工具

2. 目标与非目标

2.1 目标

  • 在普通 Agent 会话中支持动态派生 subagent
  • subagent 支持:
    • 预设 Agent
    • 临时克隆 Agent
  • 引入独立的会话级持久化模型
  • 在聊天页增加专属 subagent 卡片并支持历史恢复
  • 支持全局默认配置与 Agent 级覆盖配置
  • 统一 WebSocket 与 HTTP 聊天执行链路
  • 第一版仅支持单层委派

2.2 非目标

  • 不接入 crew 画布、监控页、调度体系
  • 不开放 subagent 独立详情页
  • 不支持 subagent 调用 clarify
  • 不支持 subagent 递归调用 delegate_task / delegate_parallel
  • 不提供第一版人工中止、人工接管、失败重试 UI

3. 架构结论

本次功能不能直接在现有网关路径上“补一个工具”完成,必须先统一普通会话执行编排。

3.1 新的普通会话编排层

新增共享服务:ChatOrchestratorService

职责:

  • 统一普通会话执行入口
  • 装配 Agent 运行时上下文
  • 为普通会话注入 subagent supervisor context
  • 保存 user / assistant message
  • 生成 assistant message 的稳定锚点
  • 写回 assistant metadata
  • 对外暴露事件回调接口

调用关系:

  • WebSocket 网关调用 ChatOrchestratorService.executeChat(...)
  • HTTP agent_executor.ts 也调用 ChatOrchestratorService.executeChat(...)

这样可以保证:

  • 同一个 Agent 在 WS 和 HTTP 下行为一致
  • subagent 逻辑只实现一次
  • metadata 写回、工具注入、prompt 组装规则一致

3.2 场景化工具实例化

现有 tool_resolver 不能只返回 toolNames,因为 delegate_task / delegate_parallel 当前只在 crewRole === 'master' 时创建运行时实例。

因此本次必须明确区分两层:

  • 工具可见性层:决定名字是否进入 prompt
  • 工具实例化层:决定当前场景下是否存在实际 handler

普通会话新增场景:

  • delegationRole: 'supervisor'
  • delegationRole: 'subagent'

结论:

  • 普通会话 supervisor 可见且可执行 delegate_task / delegate_parallel
  • subagent 运行时必须剥离这些工具
  • 不能再用“把工具名放入 toolNames 即代表可执行”这一假设

4. 用户体验

4.1 主流程

  1. 用户在普通聊天页向 Agent 发消息
  2. ChatOrchestratorService 创建或续用会话
  3. 运行主 Agent
  4. 主 Agent 触发 delegate_task / delegate_parallel
  5. 聊天页显示“子代理执行”卡片
  6. 子代理状态实时更新
  7. 主 Agent 汇总结果并生成最终回复
  8. assistant message 保存后,其数据库 id 作为本轮 subagent batch 的稳定锚点
  9. 历史恢复时按 assistant message metadata 或数据库聚合重建卡片

4.2 稳定锚点规则

本次固定采用“预创建 assistant 占位消息”方案,不使用临时 UUID 作为批次锚点。

执行顺序:

  1. 保存 user message
  2. 立即预创建一条 assistant message占位内容为空拿到数据库 assistantMessageId
  3. 本轮所有 subagent_session.parentMessageId、WebSocket 事件 parentMessageId、前端卡片 key 都使用这个 assistantMessageId
  4. 主 Agent 完成后,更新这条 assistant message 的最终 contentthinkingmetadata

这样可以保证:

  • 实时事件与历史恢复使用同一个稳定键
  • 不需要 clientBatchId -> assistantMessageId 的二次归并
  • 多轮对话中,每个 subagent 卡片都能精确归属到对应 assistant 回复

5. 数据模型

新增实体:netaclaw_subagent_session

建议字段:

字段 类型 说明
id int 主键
sessionId varchar 所属会话 ID
parentMessageId int 归属 assistant message ID
parentToolCallId varchar nullable 归属工具调用 ID
parentAgentId int 主 Agent ID
sourceType varchar preset
presetAgentId int nullable 预设 Agent ID
name varchar 展示名称
goal text 委派目标
context longtext nullable 委派上下文
status varchar queued / running / completed / failed / cancelled
model varchar 实际执行模型
toolNames json nullable 工具列表
summary longtext nullable 结果摘要
resultPayload json nullable 结构化结果
error longtext nullable 错误信息
tokenUsage json nullable token 用量
sortOrder int 同批次排序
startedAt datetime nullable 开始时间
endedAt datetime nullable 结束时间
createTime datetime 创建时间
updateTime datetime 更新时间

5.1 索引要求

必须显式设计以下索引:

  • (sessionId, parentMessageId, sortOrder)
  • (sessionId, status)
  • (parentAgentId, createTime)

理由:

  • 历史恢复按会话 + 父消息聚合
  • 会话清理和排障按 sessionId / status 检索
  • 后续监控和排查按 parentAgentId / 时间检索

5.2 清理要求

现有 session 删除逻辑只会删除:

  • netaclaw_session
  • netaclaw_message

因此本次必须同步扩展删除链:

  • 删除单个 session 时删除对应 subagent_session
  • 删除全部 session 时同步清理

否则会出现孤儿记录。

6. Agent 配置模型

netaclaw_agent 新增字段:subagentConfig

type AgentSubagentConfig = {
  enabled?: boolean;
  maxConcurrent?: number;
  allowedPresetAgentIds?: number[];
  allowedToolNames?: string[];
};

6.1 配置优先级

Agent 级配置 > 全局默认配置 > 系统保底值

6.2 全局默认配置

config.default.ts 中新增:

netaclaw: {
  subagent: {
    enabled: true,
    maxConcurrent: 3,
    blockedToolNames: [
      'delegate_task',
      'delegate_parallel',
      'clarify',
      'memory_save',
      'memory_recall',
    ],
  }
}

7. 工具设计

7.1 delegate_task

type DelegateTaskArgs = {
  goal: string;
  context?: string;
  agentId?: number;
  mode?: 'preset';
  toolNames?: string[];
  maxToolRounds?: number;
};

7.2 delegate_parallel

type DelegateParallelArgs = {
  tasks: Array<{
    goal: string;
    context?: string;
    agentId?: number;
    mode?: 'preset';
    toolNames?: string[];
    maxToolRounds?: number;
  }>;
};

7.3 工具实例化原则

  • supervisor 场景:有 schema有实际 runtime handler
  • subagent 场景:无 schema无 runtime handler
  • 普通 agent 关闭 subagent 配置时:不暴露 schema

因此实现不应依赖“复用 crew toolset 名称”作为唯一机制,而应在 resolver 内部引入会话委派场景的显式分支。

8. Prompt 设计

第一版不复用 crewRole: 'sub'

新增 delegationRole: 'subagent' prompt guidance。

子 Agent prompt 必须明确:

  • 当前身份是会话内 subagent
  • 只能完成当前目标
  • 不得向用户发问
  • 不得再次委派
  • 输出必须是返回给父 Agent 的摘要

注意:

  • 临时克隆不能简单复用父 Agent 原始 systemPrompt
  • 必须追加一层强约束 guidance
  • 否则父 Agent 指令中的“向用户提问”“自己规划 todo”等行为会泄漏到子 Agent

9. 运行时设计

9.1 新模块

  • entity/subagent_session.ts
  • service/subagent.ts
  • service/chat_orchestrator.ts
  • tools/builtin/delegate_task.ts
  • tools/builtin/delegate_parallel.ts

9.2 ChatOrchestratorService

职责:

  • 统一 WS 与 HTTP 执行入口
  • 创建和保存 user / assistant message
  • 构造主 Agent 运行时配置
  • 注入 session-subagent context
  • 收集 subagent batch
  • 写 assistant metadata
  • 返回给调用方:
    • finalContent
    • usage
    • toolCallCount
    • assistantMessageId
    • subagentBatches

9.3 SubagentService

职责:

  • 解析 preset
  • 限制可用工具
  • 构建子 Agent prompt
  • 创建 / 更新 subagent_session
  • 回调 supervisor
    • onBatchStart
    • onUpdate
    • onDone

10. WebSocket 协议

新增事件:

  • subagent_batch_start
  • subagent_update
  • subagent_done

10.1 payload 原则

实时事件必须直接使用 parentMessageId = assistantMessageId,不引入 clientBatchId

事件形态:

  • subagent_batch_start: { parentMessageId, title, total }
  • subagent_update: { parentMessageId, subagent }
  • subagent_done: { parentMessageId, summary }

因为 assistant 占位消息在 Agent 运行前已经创建所以实时状态、数据库记录、assistant metadata、前端消息流中的卡片都使用同一锚点。

11. 消息持久化与历史恢复

assistant message 的 metadata.subagents 结构:

type SubagentBatch = {
  parentMessageId: number;
  title: string;
  items: Array<{
    id: number | string;
    name: string;
    sourceType: 'preset';
    status: 'queued' | 'running' | 'completed' | 'failed' | 'cancelled';
    goal: string;
    summary?: string;
    error?: string;
    tokenUsage?: { inputTokens: number; outputTokens: number };
    sortOrder: number;
  }>;
  summary: {
    total: number;
    completed: number;
    running: number;
    failed: number;
  };
};

11.1 恢复优先级

  1. 从 assistant message metadata.subagents 恢复
  2. 若 metadata 缺失,再按 (sessionId, parentMessageId) 聚合 subagent_session

11.2 前端恢复要求

store/chat.ts 现有逻辑只恢复:

  • todoState
  • skillExecutions

因此本次必须新增显式恢复函数:

  • restoreSubagentBatches(msgs)

不能只依赖实时事件。

11.3 消息级内联展示要求

subagent 卡片必须归属于具体 assistant message而不是作为会话级全局面板展示。

前端数据结构应支持:

  • 每条 assistant message 从 metadata.subagents 读取自己的 subagent batches
  • 当前流式 assistant message 使用 parentMessageId 匹配实时 subagent batches
  • 渲染时卡片跟随对应 assistant message 出现在消息流中

多轮会话中,如果第 2 轮和第 5 轮都触发 subagent则页面应在第 2 轮 assistant 回复附近展示第 2 轮卡片,在第 5 轮 assistant 回复附近展示第 5 轮卡片,不应把所有卡片集中显示在当前输入区上方。

12. 前端设计

12.1 新组件

  • components/subagent-batch-card.vue

12.2 聊天页顺序

  1. 当前 assistant message 关联的 todo-card
  2. 当前 assistant message 关联的 subagent-batch-card
  3. 当前 assistant message 关联的 skill-card / tool-card
  4. thinking-block
  5. assistant 消息内容

12.3 Agent 编辑页

agent-edit.vue 中增加“子代理”区块。

但要注意:

  • 当前页面没有现成的 “published agent options” 数据源
  • 需要新增后端 options API或复用 admin page 数据并做轻量转换

因此这是一个明确的子任务,不是现成字段拼装。

13. 错误处理

  • 单个 subagent 失败不终止主 Agent
  • 并行委派允许部分失败
  • subagent 超时仅影响自身
  • subagent 调用 clarify 直接失败
  • 会话删除必须删除 subagent_session

14. 测试要求

14.1 后端

  • resolver 的场景化工具实例化
  • chat orchestrator 在 WS / HTTP 下行为一致
  • subagent service 的来源解析、工具约束、状态持久化
  • session 删除时清理 subagent_session
  • assistant metadata 写回与恢复字段一致

14.2 前端

  • store 实时事件合并
  • store 历史 metadata 恢复
  • subagent-batch-card 渲染
  • 刷新后恢复

15. 风险与取舍

15.1 主要风险

  • 普通会话与 crew 语义混淆
  • WS 与 HTTP 行为分叉
  • 实时批次键与历史恢复键不一致
  • toolNames 可见但 runtime handler 缺失

15.2 对应措施

  • 独立实体、独立服务、独立组件
  • 引入 ChatOrchestratorService 统一入口
  • 使用 assistant message id 作为最终锚点
  • resolver 显式区分“工具可见”和“工具实例化”

16. 结论

本方案保留“普通会话内 subagent”目标但修正为一个可在 Neta 现有架构中落地的版本:

  • 普通会话新增共享编排层 ChatOrchestratorService
  • subagent_session 独立持久化
  • delegate_task / delegate_parallel 采用场景化工具实例化
  • assistant message id 作为唯一稳定历史锚点
  • 聊天页新增独立 subagent-batch-card
  • WebSocket 与 HTTP 执行链路统一

这版方案可以避免当前代码结构下最容易出现的前后端断裂、数据库孤儿记录、历史恢复错位和 runtime handler 缺失问题。