状态:一期已实施(2026-10-26),二期、三期待做;实施中的调整见 §15。决策见 ADR-0023(工作组协作模型)、ADR-0024(Bot API 与独立的 Bot Key)。
现有的 AgentServer 是单 Agent 执行内核:Profile 定义角色,Session 保存一段对话,Run 执行一轮,MCP Gateway 提供工具,过程以事件形式记录、可回放。使用方式是"一个人对着一个 Agent"。
本功能在内核之上增加协作层:
通过独立的 Bot API 对外提供,与现有的 Agent API(/v1)不共用 Key。
不在范围内:
| 术语 | API | 含义 |
|---|---|---|
| 机器人 | bot |
后台配置的一个岗位:身份(名称、头像、岗位描述)+ 关联的 Profile(模型、技能、MCP 服务、资料库、权限) |
| 组长 | lead |
类型为组长的机器人:负责拆分任务、分派、汇总,自身不带业务技能 |
| 组员 | worker |
类型为组员的机器人:执行具体工作 |
| 工作组 | team |
一次协作的容器:1 个成员 + 0–1 个组长 + 0–多个组员,有共享目录和消息记录 |
| 成员 | human |
工作组里的人类使用者,每个工作组有且只有一个 |
| 任务 | job |
成员交给组长的一项复杂工作;同一工作组内的任务依次执行,不并行 |
| 子任务 | task |
组长分派给某个组员的单项工作 |
| 单项工作 | direct |
成员直接 @ 组员发起的工作,不经过组长 |
| Bot Key | bk_… |
调用 Bot API 的密钥,与 Agent API 的 ask_… 互不通用 |
| 角色 | 数量 |
|---|---|
| 成员(人) | 恰好 1 个 |
| 组长 | 0 或 1 个 |
| 组员 | 0 到多个 |
| 组长 + 组员 | 至少 1 个 |
{id, name},AgentServer 不做认证(认证由外部平台负责,见 §10)。| 状态 | 说明 |
|---|---|
进行中(active) |
正常收发消息 |
已暂停(paused) |
成员或系统(超出限额时)暂停:正在执行的 Run 被中断,新消息只记录不唤醒机器人;恢复后继续 |
已关闭(closed) |
不再接收消息,进行中的任务被取消;历史、文件只读保留;可以重新打开(各机器人的引擎会话可以续接) |
| 删除 | 永久清除消息、文件和底层会话;用量统计保留 |
已关闭的工作组以后纳入 M3 的工作区自动清理(关闭超过 N 天后删除)。
team_bot_sessions),机器人的记忆就是这个 Session。离开工作组后不带走,也不跨工作组。sessions.source)记为 team:<teamId>,不会出现在 Agent API 的会话列表中。组内所有机器人会话的工作目录就是工作组的共享目录(实施时的调整,见 §15)。共享目录与会话工作区同级,沿用现有的访问策略、systemd 可写目录和删除保护:
<workspaceRoot>/<teamId>/
├─ uploads/ 成员上传的文件
├─ outputs/ 各机器人交付的文件(消息里以文件卡片显示)
└─ .work/<机器人>/ 各机器人的中间文件
同一个机器人在同一个工作组内按顺序执行。执行期间到达的新消息会合并起来,在下一轮一起交给它。不同机器人之间并行执行,受 RunManager 的全局和按引擎的并发限制。
工作组的消息记录(team_events 中的 message.created 事件)是成员看到的时间线,和机器人内部的执行过程(引擎事件)分开存放:
| 发送者 | 消息类型 |
|---|---|
| 成员 | 普通消息(文字、@、附件) |
| 组长 | 回复、任务开始和结束、子任务分派、申请加入机器人 |
| 组员 | 回复(单项工作)、子任务结果 |
| 系统 | 成员变化、暂停和恢复、超出限额、审批卡片 |
机器人每一轮的工具调用、思考过程不进消息记录。成员可以从某条消息点开,查看对应 Run 的完整过程(复用现有的事件和回放)。
| 来源 | 规则 |
|---|---|
| 成员发消息并 @ 了机器人 | 唤醒被 @ 的机器人(可以同时 @ 多个) |
| 成员发消息,没有 @ | 有组长就交给组长;没有组长但只有一个组员,就交给这个组员;否则返回 400 mention_required |
| 组长分派子任务 | 唤醒对应组员 |
| 组员完成或失败一个子任务 | 唤醒组长 |
| 组员的单项工作完成 | 不唤醒任何机器人,回复直接给成员 |
组员之间不能互相 @,组员也不能主动 @ 组长,只能通过子任务的结果向组长汇报。机器人发出的消息里如果写了 @,只当作文字,不会唤醒任何人。
每次唤醒时,服务端把"自上次以来与它有关的新内容"拼成这一轮的输入:
【工作组新消息】
[10:02] 成员 张三:@组长 帮我分析……(附件:shared/uploads/sales.xlsx)
【子任务 #3 已完成 · 数据分析员】
结果:…… 文件:shared/outputs/summary.csv
组长靠能力卡片了解每个组员会什么。卡片由服务端根据机器人的配置自动生成:
name 和 description(取自 SKILL.md 开头的元数据)组长的 Session 开始时,把当前所有组员的卡片注入它的提示词。成员变化时补一条系统消息,组长也可以随时调用 team_members() 重新获取。
成员 @组长 "……"
└─ 组长判断这是一项任务 → job_start(标题, 计划) 同一时间只能有一个进行中的任务
├─ task_create(组员, 说明, 依赖的子任务) × N 不同组员的子任务可以并行
│ └─ 组员执行 → task_complete(结果, 文件) / task_fail(原因)
│ └─ 唤醒组长:继续分派 / 调整计划 / 重试
└─ job_complete(总结, 文件) 或 job_fail(原因) → 发到工作组,交给成员
job_start 会被拒绝;组长应先结束当前任务,或者告诉成员稍后再提。task_complete 或 task_fail,服务端就把它最后的回复作为结果,按"完成"处理。工作组可以只建一个组长,再由组长根据实际需要申请加入机器人:
team_bot_catalog() 查看可以加入的机器人。范围是这个工作组所用的 Bot Key 允许使用、且不在本组的组员机器人,返回能力卡片。team_request_bot(botId, 理由),工作组里显示一张审批卡片,由成员批准或拒绝。成员通过 API 直接增减机器人时不需要审批。移除一个正在执行子任务的组员时,该子任务按失败处理,并通知组长。
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) |
✓ | 汇报子任务结果 |
机器人每轮的最终回复会自动作为一条消息发到工作组,不需要专门的"发消息"工具。
auto 或 readonly,第三方 MCP 工具的策略设成"直接调用"或"关闭"。team_request_bot),就在工作组里显示一张审批卡片,由成员处理。这复用现有的交互服务,引擎侧不改。interactionTimeoutSec)按拒绝处理。对于子任务,就是子任务失败,再唤醒组长处理。由于组员之间不能互相 @,协作链路只有"组长 ⇄ 组员"这一种往返,主要靠按任务设限额:
| 限额 | 默认值(后台可改,工作组创建时可以调低) |
|---|---|
| 每个任务的子任务数 | 20 |
| 每个任务中组长的执行轮数 | 30 |
| 每个任务的成本上限 | 以美元计,按 Run 用量累加 |
| 每个任务的时长上限 | 2 小时 |
| 每个工作组同时执行的 Run 数 | 4 |
超出限额时,任务自动暂停,并发一条系统消息说明原因,由成员选择"继续"(这一项限额放宽一倍)或"取消"。工作组可以随时整体暂停。
| 表 | 主要字段 |
|---|---|
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 下载前缀 /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.createdbot.status(空闲 / 排队 / 工作中 / 等待审批)job.updated、task.updatedinteraction.required、interaction.resolvedteam.updated(成员、状态变化)| AgentServer 负责 | 外部负责 |
|---|---|
| 机器人定义、Bot Key(后台) | 使用者的身份和登录;把自己的用户映射为成员 {id, name} |
| 工作组、成员、消息、共享文件的存储 | 工作组的聊天界面(可以用 SDK 提供的组件,见 §12) |
| 唤醒规则、调度、组长和组员的协作工具 | 谁可以建工作组、可以拉哪些机器人等业务规则(Bot Key 的范围之内) |
| 审批卡片、限额、暂停、关闭 | 通知和推送 |
| 事件流、用量、审计 | 外部 IM(飞书等)接入:桥接服务调用 Bot API,把外部的群映射为工作组 |
| 后台观察和调试页面 |
| 模块 | 改动 |
|---|---|
| 引擎适配器、沙箱 | 不改 |
| 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 路由。
做法与现有 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) | 把"成员组成 + 第一条命令"保存为用例,以后批量重放并比对结果 |
@agent-server/client 增加 BotClient(Bot API、工作组 SSE 订阅、断线续传)@agent-server/chat-ui 增加 <TeamChat> 组件:消息时间线、@ 选择、任务进度、审批卡片、替身卡片(仅在 debug 生效时显示)、文件卡片| 期次 | 内容 | 验收 |
|---|---|---|
| 一期 | 机器人定义、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 中完成一期、二期的验收流程 |
| # | 问题 | 倾向 |
|---|---|---|
| T1 | 组长的提示词和协作规范放在哪里 | 内置一个"组长"Profile 和配套技能,后台可以复制修改 |
| T2 | 组长能否申请移除组员 | 一期、二期不支持,由成员操作 |
| T3 | 工作组长期运行后,组长的 Session 越来越长 | 依赖引擎自身的压缩;任务结束时由组长写一份任务纪要到共享目录,以后再观察是否需要每个任务重新开一个组长 Session |
| T4 | 能力卡片里是否列出全部 MCP 工具 | 只列服务名和说明,工具较多时不展开 |
已实现:机器人与 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/ 文件挂到它的回复上。两个机器人同时改动文件时仍可能互相计入,二期再细化。/v1 返回 401。