13-bot-teams.md 25 KB

13 Bot 工作组

状态:一期已实施(2026-10-26),二期、三期待做;实施中的调整见 §15。决策见 ADR-0023(工作组协作模型)、ADR-0024(Bot API 与独立的 Bot Key)。

1. 背景与目标

现有的 AgentServer 是单 Agent 执行内核:Profile 定义角色,Session 保存一段对话,Run 执行一轮,MCP Gateway 提供工具,过程以事件形式记录、可回放。使用方式是"一个人对着一个 Agent"。

本功能在内核之上增加协作层:

  • 后台可以配置多个不同岗位的机器人,每个机器人有自己的技能
  • 使用者创建工作组,拉入机器人后下达命令
  • 复杂任务交给组长,由组长拆分并分派给组员,直到完成
  • 使用者也可以直接 @ 某个组员,让它完成单项工作

通过独立的 Bot API 对外提供,与现有的 Agent API(/v1)不共用 Key。

不在范围内:

  • 多用户(工作组里只有一个使用者,见 §3)
  • 跨工作组的记忆
  • 外部 IM 平台接入:由外部桥接服务调用 Bot API 实现,见 §10

2. 术语

术语 API 含义
机器人 bot 后台配置的一个岗位:身份(名称、头像、岗位描述)+ 关联的 Profile(模型、技能、MCP 服务、资料库、权限)
组长 lead 类型为组长的机器人:负责拆分任务、分派、汇总,自身不带业务技能
组员 worker 类型为组员的机器人:执行具体工作
工作组 team 一次协作的容器:1 个成员 + 0–1 个组长 + 0–多个组员,有共享目录和消息记录
成员 human 工作组里的人类使用者,每个工作组有且只有一个
任务 job 成员交给组长的一项复杂工作;同一工作组内的任务依次执行,不并行
子任务 task 组长分派给某个组员的单项工作
单项工作 direct 成员直接 @ 组员发起的工作,不经过组长
Bot Key bk_… 调用 Bot API 的密钥,与 Agent API 的 ask_… 互不通用

3. 工作组

3.1 组成规则

角色 数量
成员(人) 恰好 1 个
组长 0 或 1 个
组员 0 到多个
组长 + 组员 至少 1 个
  • 创建时和之后任何一次修改,都必须满足上面的规则:不能移除最后一个机器人;已有组长时不能再加第二个组长。
  • 成员只是调用方传来的显示身份 {id, name},AgentServer 不做认证(认证由外部平台负责,见 §10)。
  • 所有对话都必须在工作组内进行,没有脱离工作组的"直接和机器人对话"。只想让一个机器人干活时,建一个只有这个组员的工作组即可,不需要组长。

3.2 生命周期

状态 说明
进行中(active) 正常收发消息
已暂停(paused) 成员或系统(超出限额时)暂停:正在执行的 Run 被中断,新消息只记录不唤醒机器人;恢复后继续
已关闭(closed) 不再接收消息,进行中的任务被取消;历史、文件只读保留;可以重新打开(各机器人的引擎会话可以续接)
删除 永久清除消息、文件和底层会话;用量统计保留

已关闭的工作组以后纳入 M3 的工作区自动清理(关闭超过 N 天后删除)。

3.3 机器人与引擎会话

  • 每个"机器人 × 工作组"对应一个独立的引擎 Session(team_bot_sessions),机器人的记忆就是这个 Session。离开工作组后不带走,也不跨工作组。
  • Session 的来源(sessions.source)记为 team:<teamId>,不会出现在 Agent API 的会话列表中。
  • 组内所有机器人会话的工作目录就是工作组的共享目录(实施时的调整,见 §15)。共享目录与会话工作区同级,沿用现有的访问策略、systemd 可写目录和删除保护:

    <workspaceRoot>/<teamId>/
    ├─ uploads/       成员上传的文件
    ├─ outputs/       各机器人交付的文件(消息里以文件卡片显示)
    └─ .work/<机器人>/ 各机器人的中间文件
    
  • 同一个机器人在同一个工作组内按顺序执行。执行期间到达的新消息会合并起来,在下一轮一起交给它。不同机器人之间并行执行,受 RunManager 的全局和按引擎的并发限制。

4. 消息与唤醒规则

4.1 消息

工作组的消息记录(team_events 中的 message.created 事件)是成员看到的时间线,和机器人内部的执行过程(引擎事件)分开存放:

发送者 消息类型
成员 普通消息(文字、@、附件)
组长 回复、任务开始和结束、子任务分派、申请加入机器人
组员 回复(单项工作)、子任务结果
系统 成员变化、暂停和恢复、超出限额、审批卡片

机器人每一轮的工具调用、思考过程不进消息记录。成员可以从某条消息点开,查看对应 Run 的完整过程(复用现有的事件和回放)。

4.2 谁被唤醒

来源 规则
成员发消息并 @ 了机器人 唤醒被 @ 的机器人(可以同时 @ 多个)
成员发消息,没有 @ 有组长就交给组长;没有组长但只有一个组员,就交给这个组员;否则返回 400 mention_required
组长分派子任务 唤醒对应组员
组员完成或失败一个子任务 唤醒组长
组员的单项工作完成 不唤醒任何机器人,回复直接给成员

组员之间不能互相 @,组员也不能主动 @ 组长,只能通过子任务的结果向组长汇报。机器人发出的消息里如果写了 @,只当作文字,不会唤醒任何人。

4.3 交给机器人的输入

每次唤醒时,服务端把"自上次以来与它有关的新内容"拼成这一轮的输入:

【工作组新消息】
[10:02] 成员 张三:@组长 帮我分析……(附件:shared/uploads/sales.xlsx)
【子任务 #3 已完成 · 数据分析员】
结果:……  文件:shared/outputs/summary.csv
  • 组长能看到工作组里的全部新消息。
  • 组员只收到:@ 它的消息、分派给它的子任务,以及这些内容引用的上下文。
  • 每个机器人 Session 记录"已读到的 seq",以后调试时能看到它在某一轮实际读到了哪些内容(作为事件记下来)。

5. 组长与任务

5.1 能力卡片

组长靠能力卡片了解每个组员会什么。卡片由服务端根据机器人的配置自动生成:

  • 名称、岗位描述(后台填写,写明擅长什么、不负责什么)
  • 技能:所用 Profile 中每个技能的 name 和 description(取自 SKILL.md 开头的元数据)
  • MCP 服务和工具摘要、可访问的资料库名称
  • 权限模式(只读 / 自动)

组长的 Session 开始时,把当前所有组员的卡片注入它的提示词。成员变化时补一条系统消息,组长也可以随时调用 team_members() 重新获取。

5.2 任务流程

成员 @组长 "……"
  └─ 组长判断这是一项任务 → job_start(标题, 计划)          同一时间只能有一个进行中的任务
       ├─ task_create(组员, 说明, 依赖的子任务) × N          不同组员的子任务可以并行
       │     └─ 组员执行 → task_complete(结果, 文件) / task_fail(原因)
       │           └─ 唤醒组长:继续分派 / 调整计划 / 重试
       └─ job_complete(总结, 文件)  或  job_fail(原因)      → 发到工作组,交给成员
  • 任务由组长判断后显式开始。闲聊、简单问题由组长直接回答,不开任务。
  • 任务进行中,成员发给组长的消息算作对当前任务的补充说明。
  • 已有进行中的任务时,job_start 会被拒绝;组长应先结束当前任务,或者告诉成员稍后再提。
  • 组员在子任务的这一轮结束时如果没有调用 task_complete 或 task_fail,服务端就把它最后的回复作为结果,按"完成"处理。
  • 成员可以随时取消任务:正在执行的子任务被中断,组长收到通知。
  • 任务进行中,成员仍然可以直接 @ 组员做单项工作;如果这个组员正在执行子任务,单项工作会排队等它做完。

5.3 组长申请加入机器人

工作组可以只建一个组长,再由组长根据实际需要申请加入机器人:

  1. 组长调用 team_bot_catalog() 查看可以加入的机器人。范围是这个工作组所用的 Bot Key 允许使用、且不在本组的组员机器人,返回能力卡片。
  2. 组长调用 team_request_bot(botId, 理由),工作组里显示一张审批卡片,由成员批准或拒绝。
  3. 批准后机器人加入,添加系统消息,组长收到新组员的卡片。

成员通过 API 直接增减机器人时不需要审批。移除一个正在执行子任务的组员时,该子任务按失败处理,并通知组长。

5.4 协作工具(MCP Gateway 内置)

Gateway 根据 Run 令牌中的 teamId、botId 和角色,只列出该角色可以用的工具:

工具 组长 组员(子任务中) 说明
team_members() ✓ 当前组员的能力卡片
team_bot_catalog() ✓ 可以申请加入的机器人
team_request_bot(botId, reason) ✓ 申请加入机器人,需要成员审批
team_history(before?, limit?) ✓ ✓ 读取更早的消息
job_start(title, plan) / job_complete(summary, files?) / job_fail(reason) ✓ 开始和结束任务
task_create(botId, title, instructions, dependsOn?) ✓ 分派子任务
task_cancel(taskId) / task_list() ✓ 管理子任务
task_complete(result, files?) / task_fail(reason) ✓ 汇报子任务结果

机器人每轮的最终回复会自动作为一条消息发到工作组,不需要专门的"发消息"工具。

6. 审批

  • 设计上,机器人尽量不触发审批:Profile 的权限模式用 auto 或 readonly,第三方 MCP 工具的策略设成"直接调用"或"关闭"。
  • 如果仍然触发了审批或提问(包括 team_request_bot),就在工作组里显示一张审批卡片,由成员处理。这复用现有的交互服务,引擎侧不改。
  • 某个机器人在等审批时,只有它自己被挂起,其他机器人照常工作。
  • 审批超时(interactionTimeoutSec)按拒绝处理。对于子任务,就是子任务失败,再唤醒组长处理。

7. 防失控与限额

由于组员之间不能互相 @,协作链路只有"组长 ⇄ 组员"这一种往返,主要靠按任务设限额:

限额 默认值(后台可改,工作组创建时可以调低)
每个任务的子任务数 20
每个任务中组长的执行轮数 30
每个任务的成本上限 以美元计,按 Run 用量累加
每个任务的时长上限 2 小时
每个工作组同时执行的 Run 数 4

超出限额时,任务自动暂停,并发一条系统消息说明原因,由成员选择"继续"(这一项限额放宽一倍)或"取消"。工作组可以随时整体暂停。

8. 数据模型(新增)

表 主要字段
bots id、name、avatar、kind(lead / worker)、description(岗位描述)、profileId、enabled、时间戳
bot_api_keys id、name、keyHash、keyPrefix(bk_)、enabled、allowedBotIds、时间戳
teams id、source(Bot Key id 或 admin)、title、human({id, name})、status、workspaceDir(共享目录)、lastSeq、时间戳;二期增加 limits、debug({stepMode, stubBotIds},仅 admin,见 §12.2)
team_bots teamId、botId、role(lead / worker)、joinedAt
team_bot_sessions teamId、botId → sessionId、lastReadSeq
team_events teamId、seq、type、payload、ts。消息是 message.created 事件(sender、text、mentions、files、targets、runId);状态类事件(bot.status、file.changed、team.updated)只在内存缓冲中
jobs id、teamId、title、plan、status、summary、用量合计、限额计数、时间戳
tasks id、jobId、teamId、botId、title、instructions、dependsOn、status、result、fileIds、runId、时间戳

现有表的改动:

  • sessions.source 增加 team:<id>
  • interactions 不改:通过机器人会话关联到工作组
  • usage_daily 的 source 记为工作组的来源(Bot Key id 或 admin);按 botId 汇总留到二期
  • files 不改:工作组共享目录的文件以 teamId 登记(files.session_id = teamId),只能经 /bot/v1/files/:id 下载

9. Bot API

前缀 /bot/v1,只接受 Bot Key(bk_)、Bot 浏览器令牌或后台管理员的 Cookie(Bot Playground 使用,来源记为 admin),详见 ADR-0024。仅 admin 可用的调试接口(替身结果、逐步执行的"继续")也放在这个前缀下,其他调用方访问时返回 403。

方法与路径 说明
GET /bots 这个 Key 可以使用的机器人及其能力卡片
POST /teams {title, human:{id,name}, leadBotId?, workerBotIds[], limits?}
GET /teams、GET /teams/:id 这个 Key 创建的工作组
PATCH /teams/:id 修改标题、限额
POST /teams/:id/bots、DELETE /teams/:id/bots/:botId 增减机器人(需满足 §3.1 的组成规则)
POST /teams/:id/messages {text, mentions?, fileIds?},发送者固定是这个工作组的成员
GET /teams/:id/messages?after=&limit= 消息历史
GET /teams/:id/stream SSE,支持断线续传(Last-Event-ID)
POST /teams/:id/files、GET /teams/:id/files/:fileId 上传到 shared/uploads/,下载共享目录中的文件
POST /teams/:id/interactions/:iid 处理审批或回答提问
GET /teams/:id/jobs、POST /teams/:id/jobs/:jobId/cancel 任务和子任务的进度;取消任务
POST /teams/:id/jobs/:jobId/continue 超出限额后继续
POST /teams/:id/pause、/resume、/close、/reopen、DELETE /teams/:id 生命周期
GET /teams/:id/runs/:runId/events 某一轮的完整执行过程(复用现有事件格式)
POST /browser-tokens 签发 Bot 浏览器令牌

SSE 事件:

  • message.created
  • bot.status(空闲 / 排队 / 工作中 / 等待审批)
  • job.updated、task.updated
  • interaction.required、interaction.resolved
  • team.updated(成员、状态变化)

10. AgentServer 与外部的分工

AgentServer 负责 外部负责
机器人定义、Bot Key(后台) 使用者的身份和登录;把自己的用户映射为成员 {id, name}
工作组、成员、消息、共享文件的存储 工作组的聊天界面(可以用 SDK 提供的组件,见 §12)
唤醒规则、调度、组长和组员的协作工具 谁可以建工作组、可以拉哪些机器人等业务规则(Bot Key 的范围之内)
审批卡片、限额、暂停、关闭 通知和推送
事件流、用量、审计 外部 IM(飞书等)接入:桥接服务调用 Bot API,把外部的群映射为工作组
后台观察和调试页面

11. 对现有模块的影响

模块 改动
引擎适配器、沙箱 不改
Profile 不改,机器人引用 Profile
文件访问策略 不改:共享目录就是机器人会话的工作目录
RunManager 新增 start() 入口(附件是共享目录中的文件);/v1 不能向工作组的会话发消息;用量来源可由工作组换成 Bot Key
会话服务 createForTeam();工作组的会话不能经 /v1 修改或删除,删除工作组时只删记录、不删共享目录以外的东西
MCP Gateway Run 令牌带上 teamId、botId、role、jobId、taskId;新增 §5.4 的协作工具;team_request_bot 复用 Gateway 内发起审批的现有流程
交互服务 不改:工作组服务订阅机器人会话的事件,转成工作组的 interaction.* 事件
鉴权 按路径前缀区分 Key 类型(ADR-0024)
后台 一期新增"机器人"(含 Bot Keys 页签)和"Bot Playground"两个页面(§12)

新增模块:apps/server/src/teams/,包括工作组服务、消息服务、调度器、协作工具、能力卡片和 Bot API 路由。

12. 后台、Bot Playground 与 SDK

12.1 后台页面

  • 机器人:列表、新建和编辑(名称、头像、类型、岗位描述、Profile);预览能力卡片;"在 Playground 中测试"
  • Bot Keys:签发、停用、允许使用的机器人
  • Bot Playground:管理员建工作组、以成员身份下命令,用于验证机器人(§12.2)
  • 工作组:所有来源的工作组,视图与 Playground 相同但只读,只能暂停、关闭;可以"复制到 Playground"(按相同成员组成新建一个工作组),用来复现问题

12.2 Bot Playground

做法与现有 Playground 相同:没有单独的后端,后台用同源 Cookie 调用 /bot/v1,工作组的来源记为 admin;界面用 BotClient 和 <TeamChat>,再加后台专用的调试面板。

要验证的东西分三层:

验证对象 关心什么 支持方式
单个组员 技能、权限、产出质量 只有这个组员的工作组,@ 它干活,查看每一轮的执行过程
组长 是否读懂能力卡片,拆分、分派、收尾是否合理 能力卡片预览、每轮输入;替身组员
整个工作组 端到端能否完成、成本、是否失控 任务看板、分项用量、逐步执行、调低限额

布局沿用现有 Playground 的工作台结构:

┌ 侧栏 ────────────┬ 工作组时间线 ─────────────────────────────┬ 调试面板 ───────────────┐
│ BOT PLAYGROUND + │ [工作组标题]  ● 进行中  [⏸暂停][⏭逐步][⋯]   │ 成员│任务│过程│输入│文件│用量│
│                  │                                          │                         │
│ ▸ 周报测试        │ ┌ 我:@组长 分析 Q3 数据并出报告 📎 ┐      │ 👑 组长    ● 工作中      │
│ ▸ 组长拆分测试    │ └────────────────────────────────┘      │ 📊 数据员  ○ 空闲 [替身] │
│ ▸ 数据员单测      │ 👑 组长:开始任务「Q3 分析」,计划:…     │ 📝 文档员  ◐ 排队        │
│                  │ ├ ▸ #1 → 📊数据员:清洗并汇总    ✓       │ [+ 添加机器人]           │
│                  │ ├ ▸ #2 → 📝文档员:生成 Word 报告 …       │                         │
│                  │ ⚙ 系统:组长申请加入「图表员」 [批准][拒绝] │                         │
│                  │ 📊 数据员 · #1 结果:…  📄summary.csv    │                         │
│                  ├──────────────────────────────────────────┤                         │
│                  │ [@ 选择机器人]  输入命令…           [➤]  │                         │
└──────────────────┴──────────────────────────────────────────┴─────────────────────────┘

调试面板的页签:

页签 内容
成员 各机器人的状态(空闲 / 排队 / 工作中 / 等审批)、能力卡片预览、增减机器人、替身开关
任务 任务和子任务的树形视图:状态、依赖、耗时、成本,点击跳到时间线中的位置
过程 选择机器人和轮次,查看完整执行过程。每个机器人底层是普通 Session,复用 Timeline 和现有的 DebugPanel
输入 每一轮服务端实际交给该机器人的内容(拼好的新消息、读到的 seq)。排查组长问题时先看这里
文件 共享目录,预览和下载
用量 按机器人、任务、子任务分别统计

点击时间线中任意一条机器人消息,都会跳到"过程"页签的对应轮次。

调试功能(保存在工作组的 debug 字段中,只有来源为 admin 的工作组可以设置,Bot Key 设置会被拒绝):

  • 替身组员(stubBotIds):分派给替身的子任务不执行,时间线中显示一张卡片,由管理员手动填写结果(可以附文件)或标记失败。用来在不花组员成本的情况下反复测试组长的拆分、重试和汇总,也可以故意返回失败,看组长怎么处理。
  • 逐步执行(stepMode):调度器每次唤醒机器人之前先停下,在时间线中显示"下一步:唤醒 📊数据员(子任务 #1)",管理员点"继续"才执行。用来观察协作链路,防止配置有问题时一下子跑出一大串 Run。

入口与联动:

入口 行为
机器人编辑页"在 Playground 中测试" 组员:新建只有它的工作组。组长:新建"组长 + 本后台可用组员的替身"
工作组页面"复制到 Playground" 按相同成员组成新建工作组
修改机器人配置 下一轮生效;组员的岗位描述变化后,组长收到一条更新能力卡片的系统消息
回归用例(09 §3.6,M3) 把"成员组成 + 第一条命令"保存为用例,以后批量重放并比对结果

12.3 SDK

  • @agent-server/client 增加 BotClient(Bot API、工作组 SSE 订阅、断线续传)
  • @agent-server/chat-ui 增加 <TeamChat> 组件:消息时间线、@ 选择、任务进度、审批卡片、替身卡片(仅在 debug 生效时显示)、文件卡片
  • 两者在一期实现,先由后台使用;三期随 SDK 发布

13. 分期

期次 内容 验收
一期 机器人定义、Bot Key 和 /bot/v1 鉴权(含后台 Cookie)、工作组的组成规则与生命周期、消息、唤醒规则(只有 @ 组员的单项工作)、共享目录、工作组 SSE、审批卡片;BotClient、<TeamChat>;Bot Playground 基本形态(时间线、成员、过程、文件页签) 在 Bot Playground 中建一个有两个组员的工作组,分别 @ 它们完成单项工作,过程中处理一次审批;用 Agent API 的 Key 访问 Bot API 被拒绝
二期 组长、能力卡片、任务与子任务、协作工具、限额与暂停、组长申请加入机器人;Playground 的任务、输入、用量页签,替身组员,逐步执行,"在 Playground 中测试"入口 只建一个组长的工作组,组长申请加入两个机器人(经审批),把一项复杂任务拆成多个子任务完成并交付文件;人为触发子任务数上限后能继续或取消;全部组员设为替身时,能手动填写结果完成一项任务
三期 BotClient、<TeamChat> 随 SDK 发布,接入示例,子任务依赖和并行的完善;后台工作组页面"复制到 Playground";回归用例 在 platform-demo 中完成一期、二期的验收流程

14. 待决事项

# 问题 倾向
T1 组长的提示词和协作规范放在哪里 内置一个"组长"Profile 和配套技能,后台可以复制修改
T2 组长能否申请移除组员 一期、二期不支持,由成员操作
T3 工作组长期运行后,组长的 Session 越来越长 依赖引擎自身的压缩;任务结束时由组长写一份任务纪要到共享目录,以后再观察是否需要每个任务重新开一个组长 Session
T4 能力卡片里是否列出全部 MCP 工具 只列服务名和说明,工具较多时不展开

15. 一期实施记录(2026-10-26)

已实现:机器人与 Bot Key、/bot/v1 全部一期接口与鉴权隔离、工作组组成规则与生命周期、唤醒规则、同一机器人顺序执行与消息合并、审批卡片、共享目录与文件、工作组 SSE;BotClient、<TeamChat>;后台"机器人"页面与 Bot Playground(时间线、成员、过程、文件页签)。组长在一期只按普通机器人回复(任务与协作工具在二期)。

与前文设计相比的调整:

设计 实施 原因
每个会话有自己的工作区,共享目录作为额外可写路径 会话的工作目录就是共享目录 <workspaceRoot>/<teamId> 系统提示词中"工作目录 / uploads / outputs"的约定保持一致;沿用现有访问策略、systemd 可写目录与删除保护,不需要改访问策略
team_messages 表 team_events 表,消息是其中的 message.created 事件 消息、交互卡片与状态共用一个 seq,SSE 断线续传与会话事件流一致
interactions 增加 teamId 不改表 由机器人会话关联到工作组即可

实现要点:

  • 每轮开始前先把共享目录同步到该机器人会话的文件索引,本轮结束时只有本轮改动的 outputs/ 文件挂到它的回复上。两个机器人同时改动文件时仍可能互相计入,二期再细化。
  • 服务重启时未完成的工作被标记为中断;没有交给机器人的消息不会自动重新处理,成员再次发消息时一并交给它。
  • 已用真实 Claude(haiku)验收:两个组员并行处理、其中一个触发审批并在工作组中批准、交付文件以卡片显示、续接会话保留记忆;Bot Key 访问 /v1 返回 401。