Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions apps/presentation/dashboard/smoke/team-plan-proposal-smoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ const plan = {
agent_id: "agent-backend",
acceptance: "the bounded Todo is created through the canonical owner",
staffing: "ready",
readiness: {
schema_version: "steward_lane_readiness_ladder_v0",
launchable: false,
verified_rungs: ["registered", "action_kind_supported"],
unverified_rungs: [
{ rung: "addressable", reason_code: "host_has_no_agent_presence_provider" },
{ rung: "bound", reason_code: "host_has_no_runtime_binding_readback" },
{ rung: "launchable", reason_code: "host_has_no_launch_probe" },
{ rung: "executing", reason_code: "lane_not_materialized_by_a_preview" },
],
},
first_todo: {
text: "Implement the bounded intake",
priority: "P1",
Expand Down Expand Up @@ -132,6 +143,7 @@ const translate = (key: string, values?: Record<string, string | number>) => {
"proposal.teamPlan.acceptanceShort": "acceptance",
"proposal.teamPlan.gapLane": "unstaffed",
"proposal.teamPlan.laneUnstaffed": "staffing gap, no first Todo",
"proposal.teamPlan.laneReadiness": "not launchable — verified: {verified}; unverified: {unverified}",
};
const template = table[key] ?? key;
return Object.entries(values ?? {}).reduce(
Expand All @@ -152,6 +164,12 @@ check(
&& readyLane?.value.includes("acceptance: the bounded Todo is created through the canonical owner") === true,
"a ready lane shows its first bounded Todo, its priority and its acceptance signal",
);
check(
readyLane?.value.includes("not launchable") === true
&& readyLane?.value.includes("verified: registered, action_kind_supported") === true
&& readyLane?.value.includes("unverified: addressable, bound, launchable, executing") === true,
"a staffed lane states which rungs this host verified and which it cannot",
);
check(
gapLane?.value.startsWith("unstaffed · agent_not_registered") === true
&& gapLane?.value.includes("Independently review the intake") === true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,7 @@ const en = {
"proposal.teamPlan.acceptanceShort": "acceptance",
"proposal.teamPlan.gapLane": "unstaffed",
"proposal.teamPlan.laneUnstaffed": "staffing gap, no first Todo",
"proposal.teamPlan.laneReadiness": "not launchable — verified: {verified}; unverified: {unverified}",
"proposal.workspace.current": "Current local workspace (no Repository bound)",
"proposal.workspace.named": "{workspace} (execution environment only; does not bind a repository)",
"proposal.workspaceGate.agentImpact": "Bind an Agent identity, then recheck the original action. No Goal has been written.",
Expand Down Expand Up @@ -1738,6 +1739,7 @@ const zhCN: Record<WorkspaceMessageKey, string> = {
"proposal.teamPlan.acceptanceShort": "验收",
"proposal.teamPlan.gapLane": "未配齐",
"proposal.teamPlan.laneUnstaffed": "编制缺口,无首个 Todo",
"proposal.teamPlan.laneReadiness": "尚未就绪 —— 已验证:{verified};未验证:{unverified}",
"proposal.workspace.current": "当前本地工作区(未绑定 Repository)",
"proposal.workspace.named": "{workspace}(仅提供执行环境,不会自动关联仓库)",
"proposal.workspaceGate.agentImpact": "先完成 Agent 身份绑定,再重新检查原操作;当前没有写入 Goal。",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,29 @@ function asText(value: unknown): string {
return typeof value === "string" ? value : "";
}

/**
* State the lane's readiness from the host's ladder instead of leaving the
* card's "ready" to imply a running lane. Only the two rungs this host verified
* are claimed; the rest are named as unverified.
*/
function teamPlanLaneReadiness(
lane: Record<string, unknown>,
t: WorkspaceTranslate,
): string {
const readiness = asRecord(lane.readiness);
if (readiness.launchable === true) return "";
const verified = (Array.isArray(readiness.verified_rungs) ? readiness.verified_rungs : [])
.filter((rung): rung is string => typeof rung === "string");
const unverified = (Array.isArray(readiness.unverified_rungs) ? readiness.unverified_rungs : [])
.map((rawRung) => asText(asRecord(rawRung).rung))
.filter(Boolean);
if (!verified.length && !unverified.length) return "";
return t("proposal.teamPlan.laneReadiness", {
unverified: unverified.join(", "),
verified: verified.join(", "),
});
}

export function teamPlanFields(
parameters: Record<string, unknown>,
t: WorkspaceTranslate,
Expand Down Expand Up @@ -67,6 +90,7 @@ export function teamPlanFields(
value: [
laneValue || t("proposal.teamPlan.laneUnstaffed"),
acceptance ? `${t("proposal.teamPlan.acceptanceShort")}: ${acceptance}` : "",
teamPlanLaneReadiness(lane, t),
].filter(Boolean).join(" · "),
});
});
Expand Down
17 changes: 17 additions & 0 deletions examples/personal-workspace-browser/team-plan.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ function teamPlanProposal() {
agent_id: "agent-backend",
acceptance: "the bounded Todo is created through the canonical owner",
staffing: "ready",
readiness: {
schema_version: "steward_lane_readiness_ladder_v0",
launchable: false,
verified_rungs: ["registered", "action_kind_supported"],
unverified_rungs: [
{ rung: "addressable", reason_code: "host_has_no_agent_presence_provider" },
{ rung: "bound", reason_code: "host_has_no_runtime_binding_readback" },
{ rung: "launchable", reason_code: "host_has_no_launch_probe" },
{ rung: "executing", reason_code: "lane_not_materialized_by_a_preview" },
],
},
first_todo: {
text: READY_TODO,
priority: "P1",
Expand Down Expand Up @@ -170,6 +181,12 @@ export const teamPlanScenario = {
previewText.includes("验收: the bounded Todo is created through the canonical owner"),
"a ready lane shows its acceptance signal",
);
check(
previewText.includes("尚未就绪")
&& previewText.includes("已验证:registered, action_kind_supported")
&& previewText.includes("未验证:addressable, bound, launchable, executing"),
"a staffed lane states which readiness rungs this host verified and which it cannot",
);
check(
previewText.includes("agent-reviewer")
&& previewText.includes("未配齐")
Expand Down
37 changes: 37 additions & 0 deletions loopx/control_plane/work_items/governed_transition_proposal.py
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,39 @@ def settle_governed_transition_proposals(
STEWARD_TEAM_PLAN_UNSUPPORTED_ACTION_KIND = "action_kind_not_supported"
STEWARD_TEAM_PLAN_HOST_GAP_REASONS = (STEWARD_TEAM_PLAN_UNSUPPORTED_ACTION_KIND,)

# A staffed lane is not a running lane. `staffing: "ready"` only ever meant that
# this Goal registers the Agent and this host ships the action kind, but a plan
# card that says "ready" implies work an executor will pick up. The ladder names
# each rung separately so a reader can see which ones this host actually
# verified and which ones it has no provider for.
STEWARD_LANE_READINESS_SCHEMA_VERSION = "steward_lane_readiness_ladder_v0"
STEWARD_LANE_READINESS_VERIFIED_RUNGS = ("registered", "action_kind_supported")
# The rungs this host cannot answer today. `agents/directory.py` has no presence
# provider and this host has no tool-eligibility probe or execution-capacity
# reading, so claiming them would be a guess dressed as a fact.
STEWARD_LANE_READINESS_UNPROVIDED_RUNGS = (
("addressable", "host_has_no_agent_presence_provider"),
("bound", "host_has_no_runtime_binding_readback"),
("launchable", "host_has_no_launch_probe"),
("executing", "lane_not_materialized_by_a_preview"),
)


def _staffed_lane_readiness() -> dict[str, object]:
"""Report the two rungs this host checked and the ones it cannot check."""

return {
"schema_version": STEWARD_LANE_READINESS_SCHEMA_VERSION,
# A lane may only be called launchable once every rung up to and
# including `launchable` is verified; none of them are today.
"launchable": False,
"verified_rungs": list(STEWARD_LANE_READINESS_VERIFIED_RUNGS),
"unverified_rungs": [
{"rung": rung, "reason_code": reason_code}
for rung, reason_code in STEWARD_LANE_READINESS_UNPROVIDED_RUNGS
],
}


def _plan_text(value: object, label: str) -> str:
text = " ".join(str(value or "").split())
Expand Down Expand Up @@ -971,6 +1004,10 @@ def validate_steward_team_plan_preview(
"agent_id": agent_id,
"acceptance": acceptance,
"staffing": "ready",
# `staffing: "ready"` stays the plan's own vocabulary; this is
# the host's reading of what it verified, so the two can be read
# together without either one implying the lane is running.
"readiness": _staffed_lane_readiness(),
"first_todo": normalized_todo,
}
)
Expand Down
6 changes: 3 additions & 3 deletions loopx/web/chat/asset-retention.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2",
"assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2",
"assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2",
"assets/index-ByNCYQIY.js",
"assets/index-BDb6ClZU.js",
"assets/index-C58Y-FU6.css"
],
[
Expand All @@ -28,8 +28,8 @@
"assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2",
"assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2",
"assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2",
"assets/index-C58Y-FU6.css",
"assets/index-CFOC0-T9.js"
"assets/index-ByNCYQIY.js",
"assets/index-C58Y-FU6.css"
]
]
}

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion loopx/web/chat/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
content="LoopX 个人 Agent 工作区:在同一个频道里查看、纠偏并推进 Goal。"
/>
<title>LoopX 个人 Agent 工作区</title>
<script type="module" crossorigin src="/chat/assets/index-ByNCYQIY.js"></script>
<script type="module" crossorigin src="/chat/assets/index-BDb6ClZU.js"></script>
<link rel="stylesheet" crossorigin href="/chat/assets/index-C58Y-FU6.css">
</head>
<body>
Expand Down
37 changes: 37 additions & 0 deletions tests/test_steward_team_plan_preview.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,43 @@ def test_a_staffed_lane_becomes_a_preview_that_cannot_apply() -> None:
assert preview["quota_envelope"] == {"slots_per_day": 4}


def test_a_staffed_lane_reports_which_rungs_the_host_verified() -> None:
"""`ready` is not `launchable`, and the preview has to say which is which.

The card read "ready" next to an Agent and a first Todo, which an owner can
read as work an executor will pick up. This host verifies exactly two rungs
— the Goal registers the Agent, and the host ships the action kind — and has
no provider for addressability, runtime binding, launch or execution. The
ladder states both halves so the weaker claim cannot be read as the
stronger one.
"""

readiness = _validate(_plan())["lanes"][0]["readiness"]

assert readiness["schema_version"] == "steward_lane_readiness_ladder_v0"
assert readiness["launchable"] is False
assert readiness["verified_rungs"] == ["registered", "action_kind_supported"]
assert readiness["unverified_rungs"] == [
{"rung": "addressable", "reason_code": "host_has_no_agent_presence_provider"},
{"rung": "bound", "reason_code": "host_has_no_runtime_binding_readback"},
{"rung": "launchable", "reason_code": "host_has_no_launch_probe"},
{"rung": "executing", "reason_code": "lane_not_materialized_by_a_preview"},
]


def test_a_gap_lane_carries_no_readiness_ladder() -> None:
"""A lane the host could not staff has no rungs to report."""

lane = {
**_lane(lane_id="lane-beta", action_kind="implement"),
"agent_id": "agent-not-registered",
}
gap_lane = _validate(_plan(lanes=[lane]))["lanes"][0]

assert gap_lane["staffing"] == "gap"
assert "readiness" not in gap_lane


def test_the_preview_must_name_the_goal_it_staffs() -> None:
"""Admission and settlement both need one named Goal's facts."""

Expand Down