fix(manager): keep an unstaffable team lane a typed gap and offer the admitted plan as a card - #4569
Conversation
huangruiteng
left a comment
There was a problem hiding this comment.
Head reviewed: 05e5df5
动机
线上 todo_5713ec39c3f3 的验收发现:管家把一句话团队请求答对了,业主却没有任何可确认的东西。逐层证据(已安装 release、manager 通道、executor=dsh)显示 envelope 完整存在,proposals 却为空。根因不是"skill 没写清楚",而是宿主从未告诉管家它 ship 哪些 action kind,且一条 lane 的宿主可配人性事实会拒绝整份计划。
改动思路
- 把"Agent 未注册"与"kind 本机不 ship"当作同一类 lane 级事实:类型化 gap + 保留 declined work,计划其余 lane 照常准入。
- 让已确认载荷成为校验器的不动点(无 work 的 lane 保留为 gap,不按当前事实重新推导),因此 apply 不会给业主看到是 gap 的 lane 配上人。这一条同时修掉了"带 gap lane 的已确认计划落地必报错"的潜伏缺陷。
- 准入不等于确认面:业主自己的管家通道把已准入预览投影成恰好一张
team.plan卡片;投影不创建 work、按计划幂等、失败只记 typed Turn 事件。
具体改动
- 新增
loopx/capabilities/manager_context/team_plan.py:准入事实查询 + 卡片投影(含TeamPlanProjector/TurnEventSink协议)。registry_path=None时对任何 Goal 返回None而不是抛错,保持"没有 registry 的宿主照常回答"。 governed_transition_proposal.py:action_kind_not_supported宿主词表;_unstaffed_lane/_declined_todo统一 gap 形状;无first_todo的 lane 保留其 gap。- 管家 skill 点名 17 个 shipped advancement action kind,并修正"未注册 Agent 会被丢弃"的旧表述。
ChatActionService.project_team_plan_preview:context {kind: manager, goal_id}、idempotency_key=team-plan:<plan digest>。- 前端:响应 schema 接纳团队预览 + 类型守卫;管家会话只把 todo 做成候选卡;团队计划给出"已生成可确认卡片(),确认后才会创建 lane"。
对主干的风险
- 唯一默认行为变化是补救性的:不支持的 kind 从"整份计划消失"变成"该 lane 类型化 gap";计划仍是预览、
applies: false、落地仍走 canonical owner。 - 投影只作用于业主本地
manager通道;远端与 Goal 通道明确不写卡片,且投影失败不让回答失败。 - 前端只放宽读取与窄化判定,未触碰确认/落地路径。打包 bundle 在第二笔 commit 出货。
- 已记录未覆盖:远端管家受众(Lark)自有面尚无卡片。
我的整体评价
审查了我自己的 diff 并复核了关键路径:准入仍只显示、落地仍重新校验、投影不产生 work,授权面没有扩大。新增的单测覆盖了 kind gap、词表分离、校验器幂等、服务层投影/幂等/落地读回,以及 runtime 投影作用域和 typed 失败事件。chat_runtime.py 从 1560 行降到 1531 行,维持在其 reviewed module budget 之内。
验证:34 项 team-plan/action/guidance 单测 + 131 项 manager/steward/chat 测试通过;workspace 浏览器 smoke(含 team-plan 场景)通过;dashboard chat bundle typecheck + team-plan smoke 通过;docs governance 与 maintainability ratchet 通过;loopx canary premerge --from-git-diff 通过。test_host_loop_activation.py 等 9 项在本机需要特定 Python/CLI 环境的失败在未改动的 checkout 上同样复现,与本 PR 无关。
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
English verdict: APPROVE
… admitted plan as a card A live steward answered a one-sentence team request correctly -- Goal, lanes, each lane's first bounded Todo, quota envelope, acceptance and stop condition -- and the owner still had nothing to confirm. The answer's machine-readable preview named an action kind this host does not ship for one lane, the validator treated that as a malformed payload and raised, and Chat admission therefore dropped the *entire* plan while the prose answer arrived. Two independent gaps were behind that single symptom: - nothing told the steward which action kinds this host ships, so a lane's `action_kind` was written from the request's own vocabulary rather than from the host's (`public_smoke_quality_repair` is apt and unshippable); - a host staffability fact about *one lane* refused the whole plan, while the same class of fact about an Agent (`agent_not_registered`) was already a typed gap that keeps the work it declined. The validator now treats both facts the same way: an unsupported kind becomes `action_kind_not_supported` on that lane, keeps the declined first Todo, and the plan is still admitted with its other lanes ready. The reasons a plan may declare about itself and the reasons the host reports stay two vocabularies, so a plan cannot claim the host's verdict. The confirmed payload is also a fixed point of the validator now: re-reading an admitted preview preserves a lane the preview reported as unstaffed instead of re-deriving it from the host's current facts, so an apply cannot staff a lane the owner was shown as unstaffed -- which is what the apply already relied on, and what made an admitted preview with a gap lane unappliable before this change. Admission alone still leaves the owner nothing to click, because the product surfaces list typed actions. The owner's own manager channel therefore hands the preview it admitted to the chat action service, which stores exactly one `team.plan` card scoped to the Goal the plan staffs; the projection is idempotent per plan, creates no work, and reports a typed Turn event instead of failing the answer when the surface cannot store it. A Turn response now carries the preview beside its Todo proposals, so the transcript reads one response shape instead of failing the parse of an answer the steward already produced. The team-plan host contract (the facts a preview is validated against, and the hand-off to a surface that can confirm it) moved into the capability that owns the steward channel, which also keeps `chat_runtime` under its reviewed module budget instead of growing it. RFC: docs/architecture/rfcs/harness-selection-dsh-pi-v0.md (EN + zh-CN). Validation: 34 team-plan/action/guidance unit tests and 131 manager/steward/chat tests pass; the workspace browser smoke passes with its `team-plan` scenario; the dashboard chat bundle typechecks and its team-plan smoke passes; docs governance and the control-plane maintainability ratchet pass. A remote manager audience still keeps the preview in its answer text with no card on its own surface, which the RFC names as the remaining coverage gap. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
…lan answer loopx/web/chat is tracked, so a manager Turn that carries an admitted team plan and the transcript that reads it only reach an installed LoopX after the bundle is rebuilt. Rebuilt with npm run build:chat: new entry assets/index-D8O4R6-3.js retires the previous generation assets/index-67F_HQF0.js with assets/index-B21-YvBH.css, and assets/index-DWWKKUgg.js stays retained. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
05e5df5 to
65bd1d5
Compare
huangruiteng
left a comment
There was a problem hiding this comment.
Head reviewed: 65bd1d5 (rebased onto 262923d, feat(semantics): generate shared Turn contracts and controller rules (M2))
动机
线上 todo_5713ec39c3f3 的验收发现:管家把一句话团队请求答对了,业主却没有任何可确认的东西。逐层证据(已安装 release、manager 通道、executor=dsh)显示 envelope 完整存在,proposals 却为空。根因不是"skill 没写清楚",而是宿主从未告诉管家它 ship 哪些 action kind,且一条 lane 的宿主可配人性事实会拒绝整份计划。
改动思路
- 把"Agent 未注册"与"kind 本机不 ship"当作同一类 lane 级事实:类型化 gap + 保留 declined work,计划其余 lane 照常准入。
- 让已确认载荷成为校验器的不动点(无 work 的 lane 保留为 gap,不按当前事实重新推导),因此 apply 不会给业主看到是 gap 的 lane 配上人。这一条同时修掉了"带 gap lane 的已确认计划落地必报错"的潜伏缺陷。
- 准入不等于确认面:业主自己的管家通道把已准入预览投影成恰好一张
team.plan卡片;投影不创建 work、按计划幂等、失败只记 typed Turn 事件。
具体改动
- 新增
loopx/capabilities/manager_context/team_plan.py:准入事实查询 + 卡片投影(含TeamPlanProjector/TurnEventSink协议)。registry_path=None时对任何 Goal 返回None而不是抛错。 governed_transition_proposal.py:action_kind_not_supported宿主词表;_unstaffed_lane/_declined_todo统一 gap 形状;无first_todo的 lane 保留其 gap。- 管家 skill 点名 17 个 shipped advancement action kind,并修正"未注册 Agent 会被丢弃"的旧表述。
ChatActionService.project_team_plan_preview:context {kind: manager, goal_id}、idempotency_key=team-plan:<plan digest>。- 前端:响应 schema 接纳团队预览 + 类型守卫;管家会话只把 todo 做成候选卡;团队计划给出"已生成可确认卡片(),确认后才会创建 lane"。
对主干的风险
- 唯一默认行为变化是补救性的:不支持的 kind 从"整份计划消失"变成"该 lane 类型化 gap";计划仍是预览、
applies: false、落地仍走 canonical owner。 - 投影只作用于业主本地
manager通道;远端与 Goal 通道明确不写卡片,且投影失败不让回答失败。 - 前端只放宽读取与窄化判定,未触碰确认/落地路径。第二笔 commit 出货打包 bundle;
#4499未改动apps/presentation/dashboard,bundle 仍然是最新构建。 - 已记录未覆盖:远端管家受众(Lark)自有面尚无卡片。
我的整体评价
rebase 到 #4499(Turn 契约/controller 规则 M2)后重跑:60 项 manager/steward/team-plan 测试与 canary premerge --from-git-diff(含 public boundary)均通过,无新增债务。chat_runtime.py 在 1531 行,维持 reviewed module budget。
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
English verdict: APPROVE
… offered it (#4572) * fix(manager): state where a team plan is confirmed in the answer that offered it PR #4569 made an admitted steward team preview reach the owner's own channel as a typed `team.plan` card. Live readback showed the card is reachable, but only under the Goal the plan staffs: the manager conversation renders no typed-action cards, and a Lark manager audience has no card at all. So the owner who asked the sentence could read a correct plan and still not know how to confirm it -- the steward's prose says "you confirm and it applies", not where. Every manager answer that admitted a preview now carries one channel-authored pointer line naming the Goal whose workspace holds the card, and stating that no lane exists before that confirmation. It is an operational receipt, in the same way the delegation path states its own receipt, so the steward's prose is preserved verbatim and a Goal channel is never annotated. A remote manager audience receives the same line, which is what makes one manager answer actionable for an audience that cannot itself confirm; no card is written on its behalf, and that remaining gap stays named in the RFC. The dashboard no longer composes its own version of that sentence: the channel is the one owner of it, so the transcript shows the answer it was given. Validation: 86 manager/steward/team-plan unit tests pass, including the new pointer cases (manager channel annotated, remote audience annotated without a card, Goal channel untouched, a plan-free answer never annotated); workspace browser smoke, dashboard typecheck and the team-plan smoke pass; the rebuilt chat bundle ships the frontend change; docs governance, the maintainability ratchet and `loopx canary premerge --from-git-diff` pass. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> * chore(dashboard): ship the rebuilt chat bundle for the manager dialogue line The channel now states where a team plan is confirmed, and the dashboard stopped composing its own version of that sentence, so the packaged chat surface needs the rebuilt bundle. Rebuilt with npm run build:chat: new entry assets/index-Cb7S1yHW.js retires assets/index-D8O4R6-3.js as the previous generation. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> --------- Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Co-authored-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
A team plan may contain a lane this host cannot staff: the Agent registration, capability grant, or action kind does not exist here. The shipped behavior (#4569) keeps the plan admitted, names the lane as a typed gap, and creates only the lanes that can run, but no catalog entry named that boundary. IP-034 fills it in the Planning Governance family: name the gap instead of inventing the lane, keep the admitted plan, show the lane instead of dropping it silently, and require explicit new intent before a gap is filled. IP-018 owns plan-to-todo writeback and IP-024 owns the repair delta; neither covers part of an admitted plan having no honest owner. Adds the family table row, the family-to-canary matrix entry, and three durable assertions to examples/interaction-pattern-catalog-smoke.py. Closes #4638 Refs GH-C37 Signed-off-by: BigDataDZ <76271875+BigDataDZ@users.noreply.github.com>
动机
线上验收(
todo_5713ec39c3f3)里,管家把"一句话要一支团队"答对了:Goal、两条 lane、每条 lane 的首个有界 Todo、quota 包络、验收标准、停止条件都齐,但业主没有任何东西可以确认——proposals=[]。逐层追下去是两个独立缺口,不只是"skill 没写清楚":
public_smoke_quality_repair。agent_not_registered)时,契约早就把它做成类型化 gap 并保留 declined work。线上原文(托管段落的最终消息)已确认 envelope 完整:
{"kind":"steward_team_plan_preview", ...}带着两条 lane、完整 quota 包络与 stop condition,缺的只是准入。改动思路
first_todo.action_kind不在本机 shipment 里,就在那条 lane 上记action_kind_not_supported,保留declined_first_todo,其余 lane 照常 ready,计划继续被准入。声明用词表与宿主回报用词表保持两套,计划不能冒用宿主对 lane 的判断。first_todo),再校验直接first_todo must be an object—— 也就是说"业主确认了带 gap lane 的计划"在落地时必然报错。现在无 work 的 lane 被保留为它本来就是的 gap,而不会按宿主当前事实重新推导成 ready,所以落地永远不会给一条业主看到的是 gap 的 lane 配上人(不扩大已确认范围)。team.plan卡片(作用域为该计划点名的 Goal),按计划幂等;投影不创建工作;卡片存不下时记一条 typed Turn 事件,而不是让正确回答变成失败回答。loopx/capabilities/manager_context/team_plan.py;chat_runtime因此保持在其 reviewed module budget 之内(1560 行上限),没有新增模块度量债务。具体改动
loopx/capabilities/manager_context/team_plan.py(新增):team_plan_admission_context(按 Goal 解析准入事实,通道范围与 registry 缺失都 fail closed)+project_team_plan_preview(把已准入预览投影成卡片,幂等、typed 失败事件)。loopx/control_plane/work_items/governed_transition_proposal.py:新增宿主侧 gap 词表action_kind_not_supported;_unstaffed_lane/_declined_todo统一 gap lane 形状;无first_todo的 lane 保留其 gap(校验器幂等)。loopx/capabilities/manager_context/skills/loopx-manager/SKILL.md:点名本机 ship 的 17 个 advancement action kind;说明"Agent 未注册"与"kind 本机不 ship"是同一类事实、由 Core 报成 gap、计划其余部分仍到业主;修正"未注册 Agent 会被丢弃"的旧表述。loopx/chat_actions.py:ChatActionService.project_team_plan_preview—— 幂等(team-plan:<plan digest>)、context {kind: manager, goal_id}、不创建工作。loopx/chat_runtime.py/loopx/chat_server.py:runtime 暴露注入点并在 Turn 结算前调用;chat_server 把 action service 的方法接上(没有投影器的宿主照常回答)。apps/presentation/dashboard/src/data/{chat,chat-model}.ts、views/dashboard-page.tsx、smoke/team-plan-proposal-smoke.ts:响应 schema 接纳团队预览 + 类型守卫;管家会话只把 todo 提案做成候选卡,并为团队计划给出"已生成可确认卡片(),确认后才会创建 lane"的一行提示。docs/architecture/rfcs/harness-selection-dsh-pi-v0.md(+ zh-CN):两类词表、lane 级 gap、已确认载荷不动点、第 6 条 shipped enforcement(准入预览 → 可确认卡片),并点名仍然缺的覆盖:远端(Lark)管家受众目前只在正文里带预览,其自有面还没有卡片。对主干的风险
applies: false,落地仍经 canonical Todo owner,配额与授权边界未动。manager通道;远端通道与 Goal 通道明确不写卡片。投影失败不会让回答失败(typedteam_plan.projection_failed事件),因此不会把"卡片存不下"变成"管家答不出"。我的整体评价
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
改动聚焦在"业主能确认"这唯一目标上,没有扩大任何授权:准入仍然只显示、落地仍然重新校验、投影不产生 work。
team.plan卡片在 gap lane 上落地必失败的潜伏缺陷,是这次做端到端验收时才暴露并被测试固定住的,值得作为本 PR 的一部分。验证:34 项 team-plan/action/guidance 单测 + 131 项 manager/steward/chat 测试通过;workspace 浏览器 smoke(含
team-plan场景)通过;dashboard chat bundle typecheck + team-plan smoke 通过;docs governance 与 control-plane maintainability ratchet 通过;loopx canary premerge --from-git-diff通过(无新增债务)。tests/test_host_loop_activation.py等 9 项在本机需要特定 Python/CLI 环境的失败在未改动的 checkout 上同样复现,与本 PR 无关。English verdict: APPROVE