From 13711493d61b3bbe4f554168aef218b02b207e5c Mon Sep 17 00:00:00 2001 From: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Date: Wed, 16 Sep 2026 23:02:20 +0800 Subject: [PATCH] fix(manager): ship the typed team preview the steward channel admits A live team-plan request on the installed manager channel answered with a correct plan - Goal named, two lanes, their Agents, first bounded Todos, quota envelope, acceptance and stop condition - and still produced proposals=[]. The preview the product can offer for confirmation is admitted only from a machine-readable item of kind steward_team_plan_preview, and the steward's own guidance never stated it: the channel that teaches the envelope is the generic turn prompt, whose example only shows a todo proposal. The manager skill now carries the exact item, its identifiers, the per-lane fields, the staffing-gap shape and the lane limit, so the prose answer and the confirmable action describe one plan. The guidance test pins those identifiers to the validator constants, so wording and contract cannot drift apart. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> --- .../skills/loopx-manager/SKILL.md | 38 +++++++++++++++ tests/test_manager_team_plan_guidance.py | 46 +++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/loopx/capabilities/manager_context/skills/loopx-manager/SKILL.md b/loopx/capabilities/manager_context/skills/loopx-manager/SKILL.md index ab3b5e4114..e93b371dad 100644 --- a/loopx/capabilities/manager_context/skills/loopx-manager/SKILL.md +++ b/loopx/capabilities/manager_context/skills/loopx-manager/SKILL.md @@ -129,6 +129,44 @@ apply receipt returns; if the confirmation was refused, or the plan went stale because the Agents it named changed, say exactly that instead. Never widen the confirmed scope while applying it, and never charge quota for the preview itself. +The preview is machine-readable, so the product surface can offer it as one +typed action. Your prose answer stays the answer; the same team plan also rides +in the response envelope's `proposals` as exactly one item of kind +`steward_team_plan_preview`: + +```json +{ + "kind": "steward_team_plan_preview", + "schema_version": "steward_team_plan_preview_v0", + "goal_id": "", + "objective": "", + "lanes": [ + { + "lane_id": "", + "agent_id": "", + "acceptance": "", + "first_todo": { + "text": "", + "priority": "P1", + "task_class": "advancement_task", + "action_kind": "" + } + } + ], + "quota_envelope": { "" }, + "stop_condition": "" +} +``` + +At most 8 lanes, each with a distinct `lane_id`. A priority is `P0`, `P1`, +`P2` or `P3`. A lane you cannot staff keeps its `lane_id`, `agent_id` and +`acceptance`, declares `staffing_gap` with one of `agent_not_registered`, +`capability_not_granted` or `audience_not_authorized` plus a note, and declares +no `first_todo`. A preview that arrives without this item, names a Goal you are +not authorized for, or names an Agent that Goal does not register is dropped +rather than shown: the owner must never be offered a confirmation for work that +cannot be staffed. + Core owns truth and permissions. This skill supplies reasoning guidance, not new authority. Keep front-end and group answers within their respective scopes; give concise, concrete answers with source and coverage notes where they matter. diff --git a/tests/test_manager_team_plan_guidance.py b/tests/test_manager_team_plan_guidance.py index 3e430135c6..180e883260 100644 --- a/tests/test_manager_team_plan_guidance.py +++ b/tests/test_manager_team_plan_guidance.py @@ -56,3 +56,49 @@ class _Unknown(RuntimeError): error_code = "some_future_code" assert manager_failure_reply(_Unknown("x"))[0] == "processing_failed" + + +def test_manager_guidance_ships_the_machine_readable_preview_contract() -> None: + """The preview the owner confirms is the one admission can validate. + + Prose alone cannot reach the product surface: admission only surfaces a + team preview that arrives as this typed item, so guidance that never states + the item leaves a live steward answering correctly and still offering the + owner nothing to confirm. The guidance and the validator therefore share + their identifiers, and drift fails here instead of in a live answer. + """ + + from loopx.control_plane.work_items.governed_transition_proposal import ( + STEWARD_TEAM_PLAN_GAP_REASONS, + STEWARD_TEAM_PLAN_LANE_LIMIT, + STEWARD_TEAM_PLAN_PREVIEW_KIND, + STEWARD_TEAM_PLAN_PREVIEW_SCHEMA_VERSION, + ) + + text = manager_skill_text() + + assert STEWARD_TEAM_PLAN_PREVIEW_KIND in text + assert STEWARD_TEAM_PLAN_PREVIEW_SCHEMA_VERSION in text + assert "proposals" in text + assert f"{STEWARD_TEAM_PLAN_LANE_LIMIT} lanes" in text + for field in ( + '"goal_id"', + '"objective"', + '"lanes"', + '"lane_id"', + '"agent_id"', + '"acceptance"', + '"first_todo"', + '"action_kind"', + '"task_class"', + '"quota_envelope"', + '"stop_condition"', + ): + assert field in text, field + assert "staffing_gap" in text + assert "declares\nno `first_todo`" in text + assert "advancement_task" in text + for reason in STEWARD_TEAM_PLAN_GAP_REASONS: + assert reason in text, reason + # The preview never claims an effect of its own. + assert "is dropped\nrather than shown" in text