LittleAlety/dsh-team-conductor

dsh-team-conductor

Unified control plane for DeepSeek Harness Agent Teams: context-pressure visibility, member add/edit/rename/reassign/delete, runtime capacity, subagent steering, duplicate settlement-notice guard.

待分类agent-teamscordisdeepseek-harness

dsh-team-conductor

一句话定位: DeepSeek Harness Agent Teams 的统一控制面——一次看清 roster 与每个成员的上下文压力、就地增删与编辑成员、运行时调整容量、按名字或子会话 id 把消息送达,并自带共享任务板与可选的自动派活。

它有两种底层,工具面相同,靠一个配置切换(详见 两个委派引擎):

· kernel(默认) · conductor 成员是什么 · 内核 Agent Teams 的 roster 条目 · 本插件用 subagents.startContinuable 起的可续接子代理 名册在哪 · 内核 roster(只增不删、名字永不复用) · 插件侧车 teams/.json(随便改) 删除一个成员 · 墓碑 + 拦消息 + 上限 +1 补偿 · drain 子代理,名额还给池、名字可复用 需要内核 Team 包 · 需要 · 不需要(对应三条实体置 disabled: true)

九个工具全场一致:team_overview(roster + 上下文压力 + 容量 + 结算通知账本)、team_member_edit(13 个 action)、team_member_add、team_member_delete、team_capacity_set、team_message、以及自带任务板的 team_task_add / team_task_list / team_task_update(依赖、CAS revision、写作用域重叠警告)。

`kernel` 引擎下它叠加而不替换:不动任何内核行,内核的 spawn_teammate、send_message、list_agents… 全部照常工作。`conductor` 引擎下它接管:内核 agent-team / tool-agent-team / ui-agent-team 三条实体置 disabled: true,并把七个旧工具名注册成转发别名(spawn_teammate → team_member_add 等),所以任何还写着旧名字的提示、技能或习惯都会落到本插件,而不是报 "unknown tool"。

此外它还:注入一段 team:conductor:policy prompt 段(按引擎生成,不含另一套底层的说法)、安装两个包装型守卫(结算通知去重、已删成员消息拦截)、提供浏览器半边(工具卡片、设置里的控制台、会话页头的「Agent Team」按钮)和四条回环路由(/status、/client-hello、/team、/action)。

目录结构

dsh-team-conductor/
├── README.md            # 本文件
├── LICENSE              # MIT
├── package.json         # 包元数据 + dsh.bundle.patch 指向 cordis.patch.yml + dsh.client
├── cordis.patch.yml     # bundle patch:只 insert 一行 team-conductor
├── lib/index.js         # 宿主半边(host half,全部逻辑在这里)
├── lib/client.js        # 浏览器半边(工具卡片 + 设置控制台 + 页头按钮)
├── scripts/verify-live.mjs  # 断言运行中的进程确实是这一版
├── tests/smoke.mjs      # 自包含 smoke 套件(伪造 cordis ctx 与宿主服务)
└── docs/
    └── ISSUES.md        # 六个上游问题的坐标对照表(现象/根因/本插件能力/需要上游做的事)

package.json 无任何 dependencies;lib/index.js 只 import node:crypto、node:os、node:path、node:fs(要求 Node ≥ 20)。lib/client.js 是纯 JS 的浏览器半边,通过 window.__ModuleLoader__.load() 注册,不需要构建。

浏览器半边:工具卡片

GUI 的工具卡片是按工具名查 tool.call.toolview 插槽决定的;没有专属渲染时,结果就是一段原始 JSON。lib/client.js 为九个工具各注册一个卡片:

工具 · 卡片内容 team_overview · 容量一行(已用/上限/剩余)+ 每个成员一行(displayName · availability · 压力等级/占比),并列出前 3 条 diagnostics(如「空闲可复用」提示) team_member_edit · action → 成员,taskHandover 成功/失败(失败带原因),随后是 effects 与前 3 条 warnings team_capacity_set · 上限 before → after + 占用/剩余/configured team_message · kind → 目标 + 投递通道与状态 team_member_add · 新成员与状态 + 生效项 team_member_delete · 删除对象、内核记录处置(immutable-active 等)、名额变化、warnings

加载自证: 客户端半边 apply 时会 POST /dsh-team-conductor/client-hello(回环限定),宿主把它记进 state.clientHalf,于是状态路由多出两个字段:clientHalf(absent 或报到时间戳)与 `clientDiag`(每个贡献阶段的 "ok" / "error: …")。这样"客户端有没有真的加载、哪一步被静默拒绝"都可以用 curl 验证,不必看屏幕。clientDiag 里出现过两种典型失败:sidebarRightTabs unavailable(服务晚绑定、apply 时取不到)与 slot "sidebar.right.pane.tab" is not declared(直接 slots.register 而没有先 slots.inject 声明贡献)。

设置里的「Agent Team」面板

花名册面板注册在设置里,而不是右侧栏 tab:设置是"调整插件"的地方,正是用户去找它的位置;而且 settings.section 是静态声明的插槽,不需要等 sidebarRight/sidebarRightTabs 这些晚绑定的服务,也就不会踩上面那个坑。

ctx.slots.inject("settings.section", () => ctx.slots.register({
  name: "settings.section",
  id: "agent-team",
  order: 16,
  label: () => "Agent Team"   // 注意:这个插槽的 label 是函数,不是字符串
}, TeamPanel));

面板内容来自 GET /dsh-team-conductor/team(每 5 秒轮询)。团队是按 root 会话隔离的(roster 在内核里以 root agent 为键,侧车文件也是 .json),所以这个路由:

  • 不带参数时返回所有活跃团队 { ok, teams: [...], selected, sessionId, mismatch };
  • 带 ?session= 时只返回该会话那一个团队,匹配不到就给空并置 `mismatch: true`,绝不静默换一个团队给你看。

浏览器半边会尽力定位"当前会话"(先看 URL 里的 UUID,再在持久化 store dsh.sessions.current 的值里搜一个 UUID),定位到就直接显示它的团队;同时只要有多个活跃团队就渲染一排选择器(根会话前 8 位 · 已用名额),所以任何一个有活跃团队的会话都够得着。

能力边界:团队只在它的 Lead 会话存活时存在。会话关闭、Lead 被释放后,内核里就没有这支队伍了,面板会显示「无活跃团队」——这不是面板的缺陷。

打开路径:设置 → Agent Team。

注意: 新增/修改客户端半边需要重启宿主(客户端模块在组合期登记),之后按 F5 刷新页面;这与宿主半边"改代码必须重启"是同一类约束。

安装

Bundle 式安装(把本目录装进 desktop profile 的 dependencies 并选中该 bundle):

dsh plugin --profile desktop add "file:C:/Users/Little Alety/Documents/deepseek-harness/default-workspace/work/dsh-team-conductor"

等价的手工操作是:在 profile 的 package.json 里加 "dsh-team-conductor": "file:",并把 "dsh-team-conductor" 追加到 dsh.profile.bundles 数组。也可以在 Web 侧边栏的 Plugins 页安装/勾选。bundle 的 dsh.bundle.patch 会让 profile 应用本包自带的 cordis.patch.yml,其中只有一行 insert: [{ id: team-conductor, name: 'dsh-team-conductor' }]。

插件在每次组合装载时:为每个 live Team 成员(Lead 与 teammate)的 agent scope 注册九个工具 + 一个 prompt 段,并(在服务可用时)安装两个守卫——结算通知去重(装在激活注册表上)与已删成员消息拦截(装在 agentTeams.sendMessage 上)。工具只在 Team 成员的 scope 上注册——非成员 agent 看不到它们。

卸载

从 profile 撤下该 bundle:

  1. Web 侧边栏 → Plugins 页取消勾选 dsh-team-conductor,并用该页的卸载动作移除包(其内部执行 pnpm remove)。
  2. 手工等价操作: 从 profile 的 package.json 的 dsh.profile.bundles 数组里删掉 "dsh-team-conductor",再从 dependencies 删掉对应条目,然后在该 profile 目录执行 pnpm install。bundle 选择只影响 dsh.profile.bundles;仅禁用(保留 dependency)不会撤销 bundle。

撤下后九个工具、prompt 段与两个守卫一起消失;侧车状态文件(见下)不会自动删除。

服务解析:两个作用域

插件自身 inject 的只有 agents / tools / systemPrompt,其余服务全是惰性获取。agentTeams 从插件 ctx 直接取;compaction 与 subagents 这类在 shipped profile 里挂在 `isolate:` 组(Agent 作用域)下的服务,按「先插件 ctx、取不到再退回 agent.ctx」的两段式解析(lib/index.js 的 resolveService())。两个作用域都取不到时,team_overview.diagnostics 会明说 compaction is not mounted: action=compact cannot run.,action=compact 会回一条 warning 而不是假装成功。

九个工具

九个工具都在 Team 成员的 agent scope 上注册,返回约定一致:单个 `text` 块,其内容是 JSON 文档。这一输出契约对九个工具都相同(lib/index.js 的 TEXT_OUTPUT_SCHEMA;tools.register() 要求 output { schema, render },见 @deepseek-ai/dsh-tools/lib/index.js:2878-2886)。

所有 parameters 手写为内核接受的 JSON Schema 子集(type / oneOf / properties / required / additionalProperties / items / enum / const + 注解;子集定义见 @deepseek-ai/dsh-tools/lib/index.js:34-49),九个工具的参数对象都 additionalProperties: false——多传字段会被拒绝(lib/index.js 的 assertArgumentShape() 在每次执行前校验)。

1. team_overview

一次调用拿到:roster(别名/职责/优先级/标签/是否已退休/是否已删除)、每个成员的上下文占用与饱和等级、容量(已用/上限/剩余/服务中/已退休/已删除/失败/是否已满)、本团队的非 roster 子代理、以及本会话的结算通知账本。它是只读的,不改任何状态。

字段 · 类型 · 必填 · 说明 include_settlements · boolean · 否 · 是否包含结算通知账本。默认 true;仅显式传 false 才省略。 include_children · boolean · 否 · 是否列出本团队的非 roster 子代理作为可寻址目标。默认 true;仅显式传 false 才省略。 include_pressure · boolean · 否 · 是否测量每个成员的上下文占用。默认 true;传 false 时整段跳过(contextPressure.source 为 "skipped"、known 为 false),用于「只要 roster、不想付测量成本」的场合。

{ "include_settlements": true, "include_children": true, "include_pressure": true }
为什么要 `include_pressure`: 未加载成员的测量要读它的持久日志并造一个 detached 副本,是每成员一次的开销。成员多、或只是想知道「谁在、谁在跑」时,include_pressure: false 把这次 O(成员数) 的会话工作整段省掉。

返回对象形状(字段全部真实存在,null 表示该项未知):

{
  "team": {
    "root": "<root session id>",
    "caller": { "target": "lead" | "<name>", "role": "lead" | "teammate" },
    "capacity": {
      "used": 1, "max": 8, "free": 7,
      "configured": 8,        // profile 配的 maxMembers;取不到为 null
      "serving": 1,           // 未删除的名额占用者
      "retired": 0,           // 已退休且未删除
      "deleted": 0,           // 已删除(墓碑)
      "failed": 0,            // 内核判为 failed 且未删除
      "full": false
    }
  },
  "members": [ {
      "target": "<durable name>", "displayName": "diff-reviewer", "alias": "rev" | null,
      "formerNames": ["reviewer"], "role": "lead" | "teammate",
      "status": "running" | "inactive" | "provisioning" | "failed",
      "description": "...", "originalDescription": "...", "context": "fresh" | "fork" | null,
      "provider": "...", "model": "...", "priority": 5 | null, "tags": ["review"],
      "retired": false, "deleted": false, "deletedAt": null,
      "availability": "lead" | "working" | "idle-ready" | "retired" | "deleted",
      "comments": ["re-briefed 2026-..."], "loaded": true,
      "contextPressure": {
        "known": true, "source": "live" | "persisted" | "unloaded" | "unreadable",
        "tokens": 150000, "contextWindow": 200000, "tokenBudget": null,
        "ratio": 0.75, "remaining": 50000,
        "level": "ok" | "watch" | "near_saturated" | "over" | "unknown",
        "detail": "..."            // 仅当测量失败时出现
      },
      "diagnostics": [ "..." ]
  } ],
  "children": [ { "id": "...", "parent": "<本队 root>", "status": "running" | "inactive",
                  "sameTeam": true, "model": "..." } ],
  "settlements": { "total": 3, "unique": 2, "repeats": [ { "child": "...", "summary": "...",
                     "digest": "...", "count": 2, "seqs": [0, 1] } ],
                   "recent": [ { "seq": 2, "child": "...", "summary": "...", "digest": "...",
                                 "characters": 42 } ] },
  "diagnostics": [ "..." ],
  "guard": {
    "settlementDedupe": true, "settlementDedupeStatus": "active", "suppressed": 1,
    "blockDeletedTargets": true, "blockDeletedTargetsStatus": "active", "blockedDeletedSends": 0
  }
}

要点:

  • contextPressure.level 是插件的梯子,用来给 Lead 一个「该不该再唤醒它」的判断:over / near_saturated / watch / ok,测不到就是 unknown。等级本身不写入内核、不触发任何自动动作。阈值是 lib/index.js 里的 RATIO_WATCH / RATIO_NEAR_SATURATED / RATIO_OVER 常量;拿不到 context window 时退化为 TOKENS_WATCH / TOKENS_NEAR_SATURATED / TOKENS_OVER 的绝对阈值。成员若显式设了 token_budget,则 min(contextWindow, tokenBudget) 是实际预算(一个高于窗口的预算永远不会被触及,所以它不许掩盖窗口压力)。
  • capacity.used 计的是 roster teammate 行数,包含已退休和已删除的成员(内核的名字与名额都不复用);serving / retired / deleted / failed 是更细的口径。
  • children 只列本团队自己的非 roster 子代理(parentSession === team.root):别的会话的子代理属于别的 Lead,不列进来。因此每条 sameTeam 恒为 true。
  • settlements 按 (child, summary, 正文 SHA-256 摘要) 分组,报告每一组的重复次数——重复可能来自不同 Activation(合法),需要与守卫的 suppressed 计数一起看。
  • 账本测不到时(调用方没有 live session、或日志读不出来)settlements 仍是同一个对象形状,只是 total / unique 为 0、repeats / recent 为空数组——0.4.0 起不再返回 []。
  • guard 报告的是实际装上的东西:settlementDedupe / blockDeletedTargets 只在对应 status 为 active 时才为 true,另有 settlementDedupeStatus / blockDeletedTargetsStatus 给出 active / pending / unsupported / disabled / failed。
  • diagnostics 是给人看的一句话提醒:有成员 near_saturated / over 时会点名并建议 compact 或换新 subagent;Team 已满时给出 team_member_add / team_member_delete / team_capacity_set 三条出路;存在墓碑时会点名并提示 team_member_add 做替代者;有 availability: "idle-ready" 的空闲成员时会点名并建议用 reassign 复用;被抑制的通知与被拦截的消息也会报计数。

2. team_member_edit

编辑一个成员:别名、改名、职责、优先级、标签、token 预算、re-brief、reassign(改名接手下一段共享任务)、retire/restore、compact、interrupt。retire / restore / rename / reassign 是 Lead-only;compact / interrupt 对自己放行、对别人 Lead-only。

字段 · 类型 · 必填 · 说明 target · string · 是 · teammate durable name、conductor 别名、历史名,或 child session id。 action · string · 是 · alias / rename / describe / priority / tags / budget / rebrief / reassign / retire / restore / compact / interrupt。 value · string · 条件 · 新别名或新编排层名字(alias / rename / reassign),或新职责文本(describe)。名字截到 64 字符,description 截到 2000 字符。 message · string · 条件 · 自包含简报(rebrief / reassign)。 tags · string[] · 条件 · 整体替换标签表(action=tags);最多 32 个,每个截到 64 字符。 priority · integer · 条件 · 越大越先跑(action=priority)。必须是整数。 token_budget · integer · 条件 · 每成员上下文预算(tokens);0 清除它(action=budget)。 task_id · string · 否 · 要交接的共享任务(action=reassign);先把该任务 reassign 给这个成员,再发简报。 compact_first · boolean · 否 · action=reassign 时先压缩该成员上下文。默认 false。

{ "target": "reviewer", "action": "alias", "value": "rev" }
{ "target": "rev", "action": "budget", "token_budget": 160000 }
{ "target": "rev", "action": "rebrief", "message": "新的自包含任务:复核 work/x 的 diff 并给出结论。" }
{ "target": "rev", "action": "retire" }
{ "target": "rev", "action": "rename", "value": "diff-reviewer" }
{ "target": "diff-reviewer", "action": "reassign", "value": "docs-checker",
  "task_id": "task-7", "compact_first": true,
  "message": "Next task: verify docs/ISSUES.md against lib/index.js." }

任务流转(推荐做法):一个成员干完当前任务后,不要为下一段任务新建成员(内核名字永不复用、名额还占着)。先 team_task_list 找到下一段可领的共享任务,再用 reassign 一次完成「改名 + 把共享任务 reassign 给它 + 派新简报 + 清掉 retired + 用简报首行更新职责描述」。team_overview 会把空闲成员标成 availability: "idle-ready" 并在 diagnostics 里提示可复用。

逐 action 的准确行为:

action · 做什么 · 边界 alias · 写侧车 members[].alias,此后 target 可用别名寻址 · 别名只在本插件的侧车与工具里有效,内核 list_agents 看不到。拒绝 `lead`(大小写不敏感):resolveTarget 先匹配 Lead 伪行,这样的别名会被永久遮蔽;也拒绝与其它成员 durable name / 别名 / 历史名撞车 rename · Lead-only;写新 alias,把旧名压进 formerNames,此后新旧名字都能寻址 · 内核 durable name 不可改(投影拒绝 prior.name !== member.name,lib/types/projection.js:196-201);改名只发生在编排层。拒绝 `lead` 与其它成员的 durable name / 别名 / 历史名;但可以改回自己用过的旧名(有意如此,使改名可逆) describe · 写侧车 description(不改内核 roster 的 description) · 同上 priority · 写侧车 priority · 纯记录值;内核不据此调度 tags · 写侧车 tags(整体替换) · 同上 budget · 写侧车 tokenBudget;0 清除 · 影响 team_overview 的饱和判定,不改内核任何行为 rebrief · 经 team 通道给该成员投递一条 message,并在侧车记一条 note · 非 roster 的 child 目标走 subagent 通道 reassign · Lead-only;改名(若给了 value)+ 用 task_id 把共享任务 reassign 给该成员(读 getTask 拿 revision,再 updateTask(action="reassign"))+ 派新简报 + 清 retired + 用简报首行更新 description + 记 note · 任务交接的结果显式放在返回体的 taskHandover: { attempted, ok, reason } 里(不再只藏在 warnings):任务板不可用 / revision 过期 / 任务不可 reassign 时 ok: false 并给 reason,简报仍会照发——调用方必须看这个字段才知道「改名 + 认领 + 派活」是否真的三步都成了;compact_first 对未加载成员或服务缺失也只回 warning retire · Lead-only;成员正在跑就先 interrupt,再 roster.stopTeammates(root,[id])(15s 超时)释放运行激活,最后写 retired: true + retiredAt · 可逆的软下线:roster 行不会删除,名额继续被占用;stopTeammates 不可用时只写标记并回 warning restore · Lead-only;把 retired 翻回 false · 下一条消息会 cold-resume 该成员 compact · 对已加载成员调 compaction.compactNow(live, signal, "dsh-team-conductor:manual"),并在前后各测一次上下文,回报 tokens 变化 · 对别人 Lead-only、对自己放行;未加载成员只给 warning(要求先 rebrief);compactNow 返回 null 表示「没有安全且有用的压缩区间」;服务在两个作用域都取不到时回 warning interrupt · 调内核 service.interrupt(caller, name),回报原状态 · 需要 roster teammate;对别人 Lead-only、对自己放行;内核自身也强制 Lead-only

返回形状:

{
  "target": { "requested": "rev", "resolved": { "kind": "teammate", "id": "...",
               "coreName": "reviewer", "alias": "rev", "deleted": false, "retired": false } },
  "action": "alias",
  "member": { "target": "reviewer", "role": "teammate", "status": "inactive",
              "alias": "rev", "deleted": false } | null,
  "record": { "coreName": "reviewer", "alias": "rev", "retired": false, ... } | null,
  "effects": [ "alias resolved to \"rev\"" ],
  "warnings": [ "..." ]
}

target 解析顺序:lead → roster 里 durable name 精确匹配 → 侧车 alias(不区分大小写)→ live child agent id → roster 行 id → 看起来像 session id 的裸串。已被 team_member_delete 墓碑化的名字会直接被拒(teammate "" was deleted (); deletion is irreversible — create a new member with team_member_add),不会被半寻址。都不匹配则报错并附上已知 teammate 名单。team_message 走同一套解析。

3. team_capacity_set

运行时改成员上限,即直接写 teamService.roster.maxMembers。

字段 · 类型 · 必填 · 说明 max_members · integer · 是 · 新上限,至少 1;不得低于当前已占用的名额数。 reason · string · 否 · 随变更记入侧车 capacity.reason(截 200 字符)。

{ "max_members": 24, "reason": "六问题插件工作" }

返回:

{ "before": 8, "after": 24, "used": 1, "free": 23, "configured": 24,
  "note": "applies to this process only; the profile's configured maxMembers returns on restart" }
  • 两处上限会被同时写: roster.maxMembers(准入真正读的那个字段)与 service.config.maxMembers(UI/诊断读的那个)。只写前者会让 configured 与 max 永久分叉、自相矛盾,所以 0.5.0 起双写;返回体与 team_overview.capacity.configured 因此始终与生效值一致(config 不可写时为 null)。
  • 为什么需要它:本机 desktop profile 把 maxMembers 配成 8(@deepseek-ai/dsh-experimental-agent-team-profile/cordis.patch.yml:20),内核默认值是 16(@deepseek-ai/dsh-experimental-agent-team/lib/types/index.js:15 DEFAULT_MAX_MEMBERS),而内核 roster 的设计是「名字永不复用、行不可删除」。于是「满了又没有退役工具」就是死锁;提额是直接出路,team_member_add / team_member_delete 会在需要时自动各补一个名额。
  • 上限判定发生在 @deepseek-ai/dsh-experimental-agent-team/lib/types/roster.js:241-250 的 spawnAdmitted 事务里:state.members.length >= this.maxMembers 就抛 TEAM_MEMBER_LIMIT(文案 Team member limit N reached)。maxMembers 是普通可写字段(同文件 :41-45),所以运行时可改。
  • 降低上限时若低于已用名额,直接拒绝:cannot lower the ceiling to N: M member slots are already taken (names are never reused upstream)。它不会为了满足新上限而驱逐成员。

4. team_message

按名字/别名给 durable teammate 发消息,或把消息直接投递给一个非 roster 的 continuable subagent——也就是团队版 send_message 够不到的通道。

字段 · 类型 · 必填 · 说明 target · string · 是 · teammate 名字、teammate 别名,或 child session id。 message · string · 是 · 自包含的消息正文。

{ "target": "reviewer", "message": "把最后的 diff 摘要发我。" }
{ "target": "abcdef01-2345-6789-abcd-ef0123456789", "message": "继续,从第 3 步接着做。" }

返回:

{ "target": { "kind": "child", "id": "abcdef01-...", "coreName": "abcdef01-...", "alias": null,
              "deleted": false, "retired": false },
  "delivery": { "channel": "subagent", "messageId": "child-msg-1", "status": null } }
  • teammate/lead 目标走 team 通道(channel: "team",带内核返回的 messageId / status);child 目标走 resolveService(ctx, "subagents", caller) 解析出的 subagent 服务(先插件 ctx、取不到再退回 agent.ctx)上的 sendMessage(caller, id, content, { signal })(channel: "subagent")。
  • 给自己发消息会被拒绝(a member cannot send a message to itself)。
  • 内核 send_message 只解析 roster 成员:@deepseek-ai/dsh-experimental-agent-team/lib/types/roster.js:18-27 的 resolveActiveMember 对非 active 成员抛 active teammate "" not found(TEAM_MEMBER_NOT_FOUND)。模型用 subagent 通道创建的子代理不在 roster 里,所以没有名字——它们的可达地址就是自己的 session id,这正是本工具补上的那一块。
  • 目标串必须「看起来像 session id」才会被当作 child:只接受 8-4-4-4-12 UUID 或 session-,或者是当前 live agent id。随便一串都按名字/别名处理——包括纯 16+ 位十六进制这种容易被误判成 id 的形状(0.5.0 起收紧,避免全 hex 的成员名被误读成 session id)。

5. team_member_add

新建一个 durable teammate,同时把编排层的别名/优先级/标签一起记下——满员时它自己提额,所以不会再撞内核的 TEAM_MEMBER_LIMIT。Lead-only。

字段 · 类型 · 必填 · 说明 name · string · 是 · 唯一 lower-kebab-case 名字;内核规则是「lower-kebab-case、≤64 字符、不能叫 lead」(@deepseek-ai/dsh-experimental-agent-team/lib/index.js:702-706),且名字永不复用。 description · string · 是 · 职责简述(截到 200 字符)。 brief · string · 是 · 完整、自包含的初始任务。 context · string · 否 · fresh / fork;默认 fresh(只接受这两个值,其他值按 fresh 处理)。 alias · string · 否 · 侧车别名,供 team_message / team_member_edit 使用(截到 64 字符)。 priority · integer · 否 · 编排优先级(越大越先跑)。 tags · string[] · 否 · 编排标签(最多 32 个,每个截到 64 字符)。 auto_capacity · boolean · 否 · 满员时自动把上限 +1;默认 true。

{ "name": "reviewer", "description": "复核 diff 并给出结论",
  "brief": "复核 work/x 的改动,逐条列出问题与结论。", "context": "fresh",
  "alias": "rev", "priority": 5, "tags": ["review"] }

行为:先数已用名额;满员且 auto_capacity === false 直接报错(提示先 team_capacity_set),否则 raiseCeiling(+1) 并记一条 effect;然后调内核 service.spawnTeammate(caller, { name, description, prompt, context, provider, signal }),provider 取配置的 freshProvider(默认 "spawn")或 forkProvider(默认 "fork");成功后写侧车 alias / priority / tags、deleted: false、retired: false 与一条 note。

返回:

{ "member": { "target": "reviewer", "role": "teammate", "status": "running", "context": "fresh" },
  "record": { "coreName": "reviewer", "alias": "rev", "priority": 5, "tags": ["review"], ... },
  "effects": [ "member ceiling raised 8 → 9 to make room" ],
  "warnings": [] }

6. team_member_delete

硬删除一个 teammate,不可逆。 它做四件事:中断并释放运行激活、在编排层立墓碑、拦掉所有走 Team 服务(agentTeams.sendMessage,内核 send_message 也走这条路)的投递、再把成员上限 +1 让替代者放得下。Lead-only。

字段 · 类型 · 必填 · 说明 target · string · 是 · teammate durable name 或侧车 alias(非 roster 的 child agent 会被拒:它没有 roster 记录可删)。 reason · string · 否 · 记在墓碑上的原因(截到 200 字符)。 seal_kernel · boolean · 否 · 是否尝试把内核 roster 记录密封成终态。默认 true;只有成员仍在 `provisioning` 时才可能成功(见下)。 auto_capacity · boolean · 否 · 是否把成员上限 +1 让替代者放得下。默认 true。