# 形态批(B 批)设计:团队形态字段 · agent-team 档 · teammate 交卷提示 > **状态**:✅ **已实施**(2026-09-26 设计评审 PASS → 2026-09-27 eng_coder 四阶段一次交付;文件 `lib/index.js` + `host-half.test.mjs`;**当次实测** `node host-half.test.mjs` → `1168 (failed: 0)`、`node client-half.test.mjs` → `269 (failed: 0)`;基线 `1127` / `269` ⇒ 新增 **41** 条宿主断言,其中 **33** 条打在**实施前**的实现上**实测红**(红相 B `1168 (failed: 34)` = 33 条本批判据 + 1 条既有参数键集锁),读数账本见 [verification-log.md](verification-log.md) 的「形态批(B 批)」一节;提交哈希:**`12f0874`(2026-09-27 由父会话回填)**)。**后续修复**:分歧审计修复轮 `DIVERGENCE(8)`(2026-09-27)在同一批基础上闭环 8 条分歧 —— 其中 **D8 是本轮唯一的门 / 写路径语义改动**(D2 / D3 只改读面文案与镜像渲染)—— 幂等放宽为「同档同 Lead + 账上有该行」,让「重发以补记」这条指路**可执行**;另有 D1(把真机读反的文档改成实测事实 + 文案通用化 + 补设置 UI 兜底路)/ D2(首行按分支)/ D3(镜像补两行 + `set-mode` 重写镜像)/ D4(能力矩阵第 7 行改已核口径)/ D6(「只改这两个键」改准)与 D5 / D7(补断言与测试加固);本轮新增 **16** 条宿主断言(`1184 = 1168 + 16`),读数见 [verification-log.md](verification-log.md) 的「分歧审计修复轮 `DIVERGENCE(8)`」一节与 [../CHANGELOG.md](../CHANGELOG.md) 同名条目。**收尾修复轮**(代码评审 R1–R6,2026-09-27):R1 把 `probeAgentTeams` 的 service-absent reason 从因果断言软化为「服务当前不可见(可能未装载该组合包,也可能尚未激活完成)」、R2 让可用性门与确认框正文**共用同一次**探测、R3 让镜像与形态史**同一行来源**(写时复检后的那一行)、R4 把幂等语义明示为「**首次声明也算一行**」、R5 让「无会话身份」时的形态诊断行**不判**(判据 `null`)、R6 把本档状态行与 §4.2 的「唯一的行为改动」收窄为「唯一的**门 / 写路径语义**改动」(R7 仅留档,代码与夹具一字未动);本轮新增 **9** 条宿主断言 + 改写 **1** 条(`1193 = 1184 + 9`),读数见 [verification-log.md](verification-log.md) 的「B 批收尾修复轮(R1–R6)」一节。**后续**:真机冒烟修复轮(L1 时基 / L2 镜像,2026-09-27)在同批基础上闭环两条**只在真机才看得见**的问题(见 [verification-log.md](verification-log.md) 的「真机冒烟修复轮(L1 时基 / L2 镜像)」一节与本档 §4.1 的「镜像范围」段、§4.2 的落笔行),套件读数升至 `1203 (failed: 0)` / `269 (failed: 0)`(新增 10 条宿主断言 ⇒ `1203 = 1193 + 10`)。 > **来源**:《台账 + 形态设计》§7 的**步 3**(形态字段 + agent-team 档)与**步 5**(teammate 交卷提示);拍板二(形态由人指定、可来回切,不必走 M4 令牌机制)与拍板三(**提示式**,不阻断);会诊 #9 Q3(2/2 主张:并存 + 两档)。 > **落点核对**:§3 的行号按 `89074fe` 的 `lib/index.js` **逐条实读**。 ## 1. 白话版 形态**不是功能开关,是通道选择**: - **多会话档**(默认):平级会话之间,靠本插件的**跨会话身份与信任层**(投递 / 配对 / 换届 / 黑板)。 - **agent-team 档**:一个会话**内部**的子代理团队(宿主 Agent Teams:Lead + teammate、任务板、邮箱)。 三句要紧的话: 1. **人说了算**:人在对话里说一声 → 模型发起 → **弹一次确认框** → 我点一下才落笔(并往 `decisions.md` 记一行)。 2. **切了就换通道,不换人**:**信任不自动迁移**(旧配对必须人重新批准)。 3. **宿主没开就报错指路,绝不悄悄退回多会话** —— 「悄悄降级」正是本仓最反对的那类事故。 本插件在 agent-team 档下**只做名册与指路**,**不代理**宿主的派活/任务板(那是宿主的属地)。 ## 2. 需求 | # | 角色 | 故事 | | --- | --- | --- | | US1 | 人 | 我能让现任协调者把团队切到 agent-team 档(或切回);**弹一次确认框**,我点一下才落笔 | | US2 | 人 + 模型 | 之后任何读面都**如实**告诉我:现在是什么档 · 这档下哪些能力用不上(能力矩阵)· 成员名册的来源是谁 | | US3 | 人 | 宿主没开 Agent Teams 时,切档请求**被拒绝并告诉我怎么开**;**绝不**自动退回多会话档 | | US4 | 人 | 从 agent-team 切回多会话时,**信任不自动回迁**(配对要我重新点一次批准) | | US5 | 人 | 切走前能看到「这些 teammate 不会跟过来」的提示(**不阻断**) | | US6 | 人 | 形态史**可审计**:每次切换都在 `decisions.md` 留一行 | | # | 非功能标准 | | --- | --- | | N1 | **只读宿主**:不 spawn / 不派活 / 不改宿主任务状态 | | N2 | **不悄悄降级**:不可用即刺眼报错,**绝不**把 agent-team 请求当多会话执行 | | N3 | **指针不拷贝**:只存 Lead 会话 id;成员名册**不落** settings,读时现算 | | N4 | **权威归宿主**:agent-team 档下本插件的名册是**投影**,不是权威 | ## 3. 现状与落点(as-of `89074fe`) | 现有件 | 位置 | 事实 | | --- | --- | --- | | `TeamConfig` schema | :657–667 | 六个字段:`name` / `createdAt` / `workspace` / `policy` / `roles` / `rotationBackup` —— **无形态字段** | | `normalizeTeams` | :897 | 不可读行的既有降级范式:「必须降级为**可读状态**,绝不降成不可读命名空间」 | | roster 动词闭集 | :2456 | `["get", "upsert-team", "set-role", "retire"]` —— 形态要么加第五动词,要么另开工具 | | writer gate | `policy.writer`(:661) | `coordinator` 时只有现任协调者会话可写;**现任空缺 ⇒ 会话路径一律拒绝**(设置 UI 永远是超级写者) | | 确认框 | :2648 / :3039 / :3886 三处 | `ctx.get?.("userQuestions")` → `.ask({questions:[{id, header, question, detail, options:[{label, description}]}], agent, signal})`;答案形 `answer.answers[].selected` | | 确认超时 | `ROTATION_CONFIRM_TIMEOUT_MS` 模式(:3048) | 计时器 + `AbortController`,超时按取消处理 | | decisions 记账 | `decisionRow` :2271 + `lastSeq` | 追加式,seq 每文件独立 | | 可选服务探测范式 | :4913 `ctx.get("workspaceRegistry")` | 甲(服务缺席 ⇒ 老实说)/ 乙(服务在但拒绝 ⇒ 指名原因)—— 本批沿用 | | **未复核面 → 已核(2026-09-27,见 §8.1 A1)** | —— | 宿主 `agentTeams` 服务在本部署能否 `ctx.get` 到、读面形状如何 —— 会诊声称「工具即委派 `ctx.agentTeams`」,但该 checkout **读不了**(`app.asar` 不可访问)。**复核结论(2026-09-27,含 `DIVERGENCE(8)` D1 的实证更正)**:① 服务名与读面形状**已核实**(`TeamService extends Service` → `super(ctx, 'agentTeams')`;读面 `tryMembership(agent)` / `listMembers(agent)`,**以活动代理本人为凭据**,没有「按 id 读某一队」的公开面);② **本部署已装载该组合包** —— 活动 profile 是 **desktop**(`DSH_PROFILE=desktop`),其 `dsh.profile.bundles` **含** `@deepseek-ai/dsh-experimental-agent-team-profile`,四个包也都在 `desktop-runtime.json` 的 `sharedPackages` 里;③ **服务已在运行实例中挂载**(决定性证据:本会话系统提示词同时含该工具包的 POLICY 常量与只由它注册的 `spawn_teammate` 工具声明,而它的 `inject` 含 `agentTeams`)⇒ 探针返回 `available:true`、**本部署不会走到 U11b 的拒绝路径、US1 可达**。此前记的「本部署未装载」(依据是便携残留的 `profile/profiles/web/...`,不是本部署)**是错的**,已按实测更正。读面仍按 §4.3(3) 的**分支**文案处置(服务在场而调用方不是成员时走中性首行)、写路径仍按 §4.2 处理真探测失败的情形 | ## 4. 方案 ### 4.1 形态字段(只落在 settings) `TeamConfig` 扩两字段: | 字段 | 形状 | 说明 | | --- | --- | --- | | `mode` | `z.string().default("sessions")`,闭集 `sessions` / `agent-team` | **默认 `sessions`** ⇒ 既有团队零迁移 | | `leadSessionId` | `z.string().default("")` | 仅 `agent-team` 档有意义;**指针,不是名册** | `normalizeTeams` 的降级:读到**闭集外**的 `mode` ⇒ 落 `sessions` **并留痕**(沿用既有范式:不可读 ⇒ 降成可读状态);`agent-team` 而 `leadSessionId` 为空 ⇒ 保留档位但读面标 `Lead 未知`(**不编造**)。 **镜像(`roster.md`)的范围**(D3 起镜像渲染这两行;本段由 2026-09-27 真机冒烟修复轮 L2 补准): - 镜像写 `- 形态(mode):…(闭集 sessions / agent-team;默认 sessions)` 与 `- Lead 会话(leadSessionId):…` 两行;**渲染器永不打印 `undefined`** —— 缺字段 / 空串 / 非字符串按 `normalizeTeams` 的**同一套缺字段语义**落默认值(`sessions` / 空 Lead),闭集外的值按读面的规则落默认档(**降级留痕只在读面**,镜像不带 —— §8.1 A3); - **写路径配合**:`team_link_roster action=upsert-team` **新建**团队时就把这两个默认值**显式写入** settings(schema 的默认值只有**读面**会补,镜像读的是写进去的那一行)⇒ 「settings 行 = 读面 = 镜像」三者一致; - **根因留痕(真机实测)**:此前新建那条路没写这两个字段,于是新建团队的镜像渲染成 `- 形态(mode):undefined` / `- Lead 会话(leadSessionId):undefined`(既有团队走归一化读面,所以旧团队看不出这个问题);本轮的断言同时钉住**新建路径的镜像**与**两个读面(roster get 形态段 / 状态卡 ⑦ 段)的零 `undefined` 泄漏**。 ### 4.2 写路径:`team_link_roster` 加第五个动词 `set-mode` **动词闭集从四值扩为五值**(`set-mode`),参数: | 参数 | 语义 | 边界 | | --- | --- | --- | | `mode` | `sessions` / `agent-team` | 闭集外 ⇒ **拒绝并列出两个值** | | `leadSessionId?` | agent-team 档的 Lead 会话 | **省略 ⇒ 默认取调用方自己的会话 id**(切换的人通常就是 Lead),并在确认框正文里**明示该默认值**(人可见即可否决);**显式空串 ⇒ 拒绝**(agent-team 档没有 Lead 就没意义);非空必须通过**两道校验**:① 形状 `^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$`;② **在场校验** —— 能在本次会话列表快照里找到(防转录错位,沿用本仓既有教训) | **宿主探测(只对「切**到** agent-team」这一向)**:切到 agent-team **之前**先探测宿主 Agent Teams 能力;探测不到 ⇒ **拒绝 + 指路(告诉人怎么开)+ 零写入**(US3)。切**回** sessions 档不需要探测(回默认档永远可用)。 **双重门(缺一即 fail-closed,零写入)**: 1. **writer gate 原样**(`policy.writer=coordinator` ⇒ 只有现任协调者可写;空缺 ⇒ 会话路径拒绝,**设置 UI 仍是兜底**); 2. **人类确认框**(`userQuestions.ask`):无确认服务 / 超时 / 取消 ⇒ **拒绝且零写入**(`settings` 逐字节不变、`decisions.md` 零新增)。 **落笔后**:`decisions.md` 追加一行(`形态 → agent-team(Lead=)` 或 `形态 → sessions`)+ best-effort 重写 `roster.md` 镜像(`DIVERGENCE(8)` D3:`set-mode` 也是一次**设置变更**,镜像自述「与设置变更同一事务内镜像」,所以与既有三个调用点**同形**地调用同一个 `writeRosterMirror`;失败只告警),返回体附**能力矩阵**提示。 **幂等**(`DIVERGENCE(8)` D8 放宽,**这是本轮唯一的门 / 写路径语义改动**—— D2 / D3 只改读面文案与镜像渲染):`mode` 与 `leadSessionId` 都与现状相同 **且账上已有这一档的形态史行** ⇒ 返回「已是该档」,**不弹框、不写 settings、零写入**;**账上缺那一行**(例如上一次 settings 写成功而 `decisions.md` 写失败、或上一次切换时团队还没有 workspace)⇒ **补记恰好一行**再返回,返回体如实说明这是补记。形态史行的识别走**共享常量函数**(写读同源:落笔与判据读同一个正文生成函数),只比正文、不看时间戳与作者;**账读不出来 / 没有 workspace ⇒ 不补记**并在返回体如实说明(fail-closed,不猜)。**首次声明也算一行**(收尾修复轮 R4 明示的口径):形态史记的是「**声明过这一档**」,不是「档位发生变化」—— 一个从未切换过的团队第一次收到**它自己那档**的 `set-mode`(例如默认档 `mode=sessions`)同样会**补记一行**,返回体如实标「补记」。**为什么必须补**:`decisions.md` 写失败时给出的指路是「补写路径后重发本次切换以补记」,而幂等分支原先会先命中并零写返回 ⇒ 那条指路**不可执行**,US6「每次切换留一行」会被永久破坏。 ### 4.3 读面:`team_link_roster`(get) 与状态卡(A 批)都加「形态」段 读出四件事: 1. **形态**(`sessions` / `agent-team`); 2. **Lead 会话 id**(agent-team 档); 3. **成员名册**(**投影**,读时现算)—— 来源标注写死;不可读时**首行按分支取**(`DIVERGENCE(8)` D2 的口径,探针回一个 `cause` 判别符): - 可读:`来源:宿主 agentTeams 投影(本部署可读)`; - 不可读且**服务缺席**:`成员名册不可读(本部署未提供该投影)—— 仍可读 Lead 与形态`; - 不可读但**服务在场**(缺方法 / 无会话身份 / **调用方非成员** / 读取抛错四条分支):中性首行 `成员名册不可读 —— 仍可读 Lead 与形态(哪一条读不到,见下一行的原因)`; - 两种不可读情形都在下一行给`(不可读原因:…)`,**首行不得与原因行自相矛盾**(改前无条件先打「本部署未提供该投影」,而真机最常走到的恰恰是「服务在场、调用方不是成员」)。 4. **能力矩阵**(**单一事实源 = 父档 §2.3 的表**,本批照抄、不改口径): | 能力 | 多会话档 | agent-team 档 | | --- | --- | --- | | 跨会话投递(`team_link_send`) | ✅ 主用途 | ❌ 成员是子代理,不是可投递目标 | | 双门批准 / 配对 | ✅ | ❌ 用不上(宿主那套没有独立信任模型) | | 换届(`rotate`)/ 恢复(`recover`) | ✅ | ❌ 没有可换届的会话;Lead 会话没了团队就散了 | | 看门狗(`watch`) | ✅ | ❌ teammate 不是根代理,盯不了 | | 黑板(decisions / discipline / tasks) | ✅ | ⚠️ 仍可用,但只有 Lead 一方读写 | | roster 身份 / 版本史 | ✅ | ⚠️ 只记「本团队是 agent-team 档 + Lead 是谁」 | | 团队状态卡(只读) | ✅ | ⚠️ 成员部分读宿主投影(2026-09-27 已核:本部署已挂载该服务 ⇒ 探针 available=true) | | 会话深链 / 导出 | ✅ | ✅ 与形态无关 | **读面必须同时给一句结论**(父档 :67):agent-team 档应叫「**单会话兜底档**」,不是「另一种平等的形态」—— 多会话不可用时才切,切过去就等于放弃跨会话的全部能力。 5. **形态诊断行**(父档 :50):若本工作区**除自己外没有其他可达会话**(会话列表快照判据),读面附一行 —— `⚠ 多会话通道看起来不可用(列表里没有其他会话)—— 你可以让协调者切到 agent-team 档(team_link_roster action=set-mode)`。**只提示,绝不自动切**。 > **跨批接缝**:A 批的状态卡(`team_link_status`)本批**加一段形态**;A 批设计 §7 第 6 条已声明「该档下状态卡行为由 B 批定义」。 ### 4.4 teammate 交卷提示(步 5,提示式) **切换的确认框正文里**列出「本次切换不迁移的东西」: ```text 切换后不会迁移:① 信任(配对 / 白名单 / 记忆目标 —— 都要人重新批准) ② 成员名册(agent-team 的成员不会变成会话) ③ 任务归属(宿主的任务板不跟随;台账仍是本插件的账) 当前 teammate(若可读):<成员列表> 或(成员名册不可读) 请确认它们的结论已落到黑板或文件 —— 本提示不阻断切换。 ``` **判据只做提示式**:不逐成员推断「有没有交卷」(那要读子代理日志,见 §7 不做)。 ## 5. 红线 1. **只读宿主**:不 spawn / 不派活 / 不改宿主任务状态;写面只在**自己的** settings 与黑板。 2. **不悄悄降级(**写路径与读路径处置不同,不许混用**)**: - **写路径**(切**到** agent-team 的请求)⇒ 宿主能力探测失败即**刺眼报错 + 指路 + 零写入**; - **读路径**(名册投影读不到)⇒ 按 §4.3(3) / U11 **如实标注**并保持其余功能可用(形态、Lead、能力矩阵照给); - 两条路都**绝不**把 agent-team 请求当多会话执行。 3. **信任不自动迁移**:切档**一行不写** `pairs` / `trustedSenders` / `rememberTargets`。 4. **双重门 fail-closed**:writer gate 与确认框**缺一即拒绝**,且**零写入**。 5. **指针不拷贝**:agent-team 档只存 `leadSessionId`;成员名册**不落** settings。 6. **切换永远由人触发**:不做自动切档(本插件不自己判断「该切了」)。 ## 6. 验收判据(U1–U12) | # | 判据 | 覆盖 | | --- | --- | --- | | U1 | 形态默认:既有团队读出来是 `sessions`,**零迁移**(老 settings 不写新字段也不炸) | 4.1 | | U2 | 正常路径:确认框选「确认」⇒ `mode`/`leadSessionId` 落笔 + `decisions.md` 追加**恰好一行** | 4.2 | | U3 | fail-closed:无确认服务 ⇒ 拒绝且**零写入**(settings 逐字节不变 + decisions 零新增) | 4.2 / 红线 4 | | U4 | 取消路径:确认框选取消 / 超时 ⇒ 零写入 | 4.2 | | U5 | writer gate:非现任协调者调用 ⇒ 拒绝(沿用既有文案);现任空缺 ⇒ 会话路径拒绝 | 4.2 | | U6 | 幂等:同档同 Lead **且账上已有这一档的形态史行** ⇒ 「已是该档」,**不弹框、不写 settings、零写入**;账上缺该行 ⇒ **补记恰好一行**再返回(返回体如实说明是补记);账读不出来 / 无 workspace ⇒ **不补记**并如实说明 | 4.2 | | U7 | 闭集:`mode` 非法 ⇒ 拒绝并列出两个值;`leadSessionId` 格式非法 ⇒ 拒绝 | 4.2 | | U8 | **不悄悄降级**:宿主 agentTeams 不可用 ⇒ 明确报错 + 指路;且**没有任何多会话路径被执行**(stub 断言) | 红线 2 | | U9 | **只读宿主**:`set-mode` 全流程对宿主**零写调用**(stub 断言:未调用任何 agentTeams 写方法) | 红线 1 | | U10 | 信任不动:切档前后 `pairs`/`trustedSenders`/`rememberTargets` **逐字节不变** | 红线 3 | | U11 | 读面(roster 的 `get`):显示 形态 + Lead + **完整能力矩阵**(**八行逐字锁**:四 ❌ / 三 ⚠️ / 一件与形态无关);名册不可读时**如实标注** —— 首行**按分支**取(服务缺席 ⇒ 说「本部署未提供该投影」;服务在场而读不到的四条分支 ⇒ 中性首行 + 原因行),两种情形各有断言 | 4.3 | | U11b | 写路径探测:宿主 Agent Teams 不可用 ⇒ 切**到** agent-team **被拒绝 + 指路(通用表述:按当前活动 profile 的 `dsh.profile.bundles` 自查)+ 设置 UI 兜底路 + 零写入**(而切**回** sessions 不需要探测,照常成功)。**本部署已装载该组合包**(§8.1 A1)⇒ 真机通常**不会**走到这一支,本条由替身夹具断言 | 4.2 / US3 | | U13 | 状态卡显示形态段(**依赖 A 批阶段 2**;A 未就绪时本条后置,不阻塞其余) | 4.3 / §10 顺序声明 | | U14 | 诊断行:除自己外无可达会话时出现、否则不出现;文案含「只提示」语义;**任何情况下都不自动切档** | 4.3(5) / 红线 6 | | U12 | 回归:roster 既有四动词、rotate、recover 的既有断言**零回归** | 红线 4 | ## 7. 明确不做(本批) 1. **不代理宿主的 spawn / 派活 / 任务板操作**(那是宿主属地)。 2. **不读子代理日志** ⇒ 「成果未外化」只做**提示式**,不逐成员推断。 3. **不做自动切档**(切换永远由人触发)。 4. **不改** pairs / trustedSenders 的迁移语义(本批只**声明**不迁移)。 5. **不做客户端面板**(与 A 批同一取舍:数据面先行)。 6. **不做跨形态改投**(会诊 #9 裁定 7):切档**不改投任何在飞消息、不建桥**;要跨形态说话,由模型自己用对应形态的手段说 —— 本插件只**声明**这一点,不做任何转发。 ## 8. 显式假设与待验 | # | 假设 | 不成立的后果 | 验证方式 | | --- | --- | --- | --- | | A1 | 宿主 `agentTeams` 服务可 `ctx.get` 到且读面形状可知 —— **2026-09-27 已核**(服务名 / 读面形状 / **本部署已装载该组合包 ⇒ 服务已挂载**,见 §8.1 A1;`DIVERGENCE(8)` D1 实证更正) | 成员名册不可读 ⇒ 按 §4.3(3) 的分支文案如实标注(**功能仍成立**:形态 + Lead + 能力矩阵照给) | 实施期**先做能力探测**,结果写进报告(探测不到也要如实写)—— 已做,见 §8.1 | | A2 | `userQuestions.ask` 在 `set-mode` 场景可用(既有三处调用形一致) | 切档只能走设置 UI(fail-closed) | 实施期实测 | | A3 | `normalizeTeams` 的降级留痕方式与既有范式一致 | 需补一处留痕通道 | 实施期实读 | | A4 | 当前会话 id 可从调用方上下文取(用于默认 Lead) | `leadSessionId` 必填 | 实施期实读 | ### 8.1 实施期实测结论(2026-09-27,逐条如实标注) | # | 结论 | 证据 | | --- | --- | --- | | A1 | **服务名与读面形状已核实;本部署已装载该组合包 ⇒ 服务已挂载 ⇒ 探针 `available:true`**(`DIVERGENCE(8)` D1:此处**曾把真机读反**,现按实测更正) | ① 读面形状:直读宿主包 `TeamService extends Service` → `super(ctx, 'agentTeams')`,读面 = `tryMembership(agent)` / `listMembers(agent)`(**以活动代理本人为凭据**,没有「按 id 读某一队」的公开面)。② 装载事实:活动 profile 是 **desktop**(`DSH_PROFILE=desktop`,目录 `\profiles\desktop`),其 `dsh.profile.bundles` **含** `@deepseek-ai/dsh-experimental-agent-team-profile`(写入 12:30 早于宿主启动 17:16),四个包也在 `desktop-runtime.json` 的 `sharedPackages` 里;**决定性证据**:本会话日志的系统提示词同时含该工具包的 POLICY 常量与**只由它注册**的 `spawn_teammate` 工具声明,而该工具包的 `inject = ["agents","agentTeams","tools","systemPrompt"]` ⇒ 它注入了 ⇒ **`agentTeams` 服务已在运行实例中挂载** ⇒ 探针返回 `available:true` ⇒ **U11b 的拒绝路径在本部署不会触发、US1 可达**。③ 此前那条「本部署未装载」的证据是 **`profile/profiles/web/...` —— 便携残留,不是本部署**(同一句也已被 `DIVERGENCE(8)` D1 从 `lib/index.js` / `README.md` / 本档 §3 与父档 §8 一并更正)。**读面仍按分支文案处置**(服务在场而调用方不是成员 ⇒ 中性首行 + 原因行),**写路径的拒绝分支仍由替身夹具断言**(真机不触发 ≠ 不需要) | | A2 | **成立** | 确认框与既有三处调用同形(`questions[].options` + `agent` + `signal`,计时器 + `AbortController`,复用 `ROTATION_CONFIRM_TIMEOUT_MS`);四条路径各有断言:确认 ⇒ 落笔 / 取消 ⇒ 零写入 / 3 分钟超时 ⇒ 零写入 / 无确认服务 ⇒ fail-closed 零写入 | | A3 | **成立,但确如设计所料需补一处留痕通道** | `normalizeTeams` 是**纯函数**(导出给测试、被 `normalizePolicy` 每次读取调用),拿不到 `ctx` 就写不了日志 ⇒ 留痕落在**读面**上,由一张**进程内**表(`TEAM_MODE_DEGRADATIONS`,每次归一化重建)承载 —— 与看门狗去抖同一条纪律(进程内、不持久化),**零新持久状态**(N3) | | A4 | **成立** | 默认 Lead 取自 `agentSessionId(exec)`(`exec.agent.id`),断言钉住「落笔的 Lead == 调用方会话 id」。**已知未断言的一条分支(如实记)**:`writer=any` 且执行上下文**没有会话身份**时,省略 `leadSessionId` 会走「请显式给 leadSessionId」的拒绝 —— 该分支有实现、暂无断言 | #### 8.2 实施期补记的两点口径(设计未写死、按本仓既有范式裁决) 1. **留痕的承载物**(A3 的落点):进程内表 + 读面渲染。**不落 settings**,也不新增任何持久物 —— 用户把 `mode` 改回合法值之后,那一行留痕自动消失(每次归一化重建)。 2. **在场校验的适用面**:§4.2 的「非空必须过两道校验」按**显式给出**的 `leadSessionId` 执行;**省略时的默认值(调用方自己的会话 id)不做在场校验** —— 它不是谁敲进来的 id(「防转录错位」这条理由对它不成立),而会话列表快照未必收录每个根代理。**代价如实声明**:这条口径是实施期裁决,界面上以确认框正文的「默认取本次调用会话自己」明示,人可见即可否决。 ## 9. 实施分批(4 阶段) | 阶段 | 目标 | 文件 | 自检 | | --- | --- | --- | --- | | 1 | 形态字段 + `normalizeTeams` 降级 + 读面形态段(不写) | `lib/index.js` + `host-half.test.mjs` | U1 / U11 转绿(新断言**实施前必红**) | | 2 | `set-mode` 动词(双重门 + decisions + 幂等 + fail-closed) | `lib/index.js` + `host-half.test.mjs` | U2–U7 / U10 转绿 | | 3 | 能力矩阵 + 宿主探测 + 不悄悄降级 + 交卷提示 | `lib/index.js` + `host-half.test.mjs` | U8–U9 转绿;U12 全程回归 | | 4 | 跨档同步 §10 | `README.md` / `CHANGELOG.md` / `docs/verification-log.md` / 父档 §7 | 读数与实测逐字一致 | **实测(2026-09-27,逐阶段实跑;测试先行 = 先写断言后写实现)**:阶段 1 → `1137 (failed: 9)`(10 条新断言,9 红)· 阶段 2 → `1157 (failed: 19)`(20 条新断言,19 红)· 阶段 3 → `1168 (failed: 2)`(11 条新断言,2 红;3 条已被阶段 2 的实现满足、6 条负相)。**合计新增 41 条宿主断言**;换回**实施前**的 `lib/index.js` 实测红相 B `1168 (failed: 34)`(33 条本批判据 + 1 条既有参数键集锁 —— 那条锁该红)。逐条红/负相拆分见 [verification-log.md](verification-log.md) 的「形态批(B 批)」一节。 ## 10. 跨文档同步清单(与实施**同批**必改) > **执行状态(2026-09-27)**:下列 **8 条已逐条完成**(第 1 条的「§三」在本仓 README 里是 §四 的 roster 动词表 —— 已按那张表同改;第 7 条的提交哈希按要求**已由父会话回填为 `12f0874`**)。 1. `README.md` §三:roster 动词表补 `set-mode`;新增「**团队形态**」段(两档白话 + 三句要紧的话) 2. `README.md`:**能力矩阵**表 —— **照抄父档 §2.3**(四件 ❌ / 三件 ⚠️ / 一件与形态无关 + 「单会话兜底档」那句结论),**不新造口径** 3. `README.md` 工具一览表:`team_link_roster` 的动词补 `set-mode` 4. `README.md` **设计文档索引**:新增本档一行 5. `CHANGELOG.md`:新增条目(修了什么 → 为什么 → 怎么验证) 6. `docs/verification-log.md`:本批红相/绿相读数 7. 《台账 + 形态设计》§7:步 3 / 5 标 ✅ 已实施 + 指向本档的链接 + 提交哈希(父会话回填) 8. A 批设计档 **§7 第 6 条**(跨批接缝那条):状态卡的「形态段」改标为「由 B 批实施」 **落码顺序声明**:B 批的**状态卡形态段(U13)必须晚于 A 批阶段 2**(`team_link_status` 落地)。若并行实施时 A 批尚未就绪,**该条判据后置**,B 批其余判据不受影响、不阻塞。