Skip to content
Merged
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
28 changes: 16 additions & 12 deletions docs/product/use-cases/steward/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ fact the workspace surfaces render, not a claim about a live Goal.
| Beat | What the owner does | What the workspace shows | State |
| --- | --- | --- | --- |
| 1 | Looks at the first screen | Goal board lanes (needs you / running / observing / scheduled), each Goal card naming its Agent and its next sentence | Proven |
| 2 | Asks the steward in the Goal conversation | The ask becomes an accepted Turn and the admitted team plan card lands in the same conversation | Proven |
| 2 | Asks the steward in the Goal conversation | The steward's bounded prompt row (`询问下一步` / `向 Agent 获取进度报告` / `配置定时检查` / `看阻塞` / `查证据`) sends its message on click and leaves no draft, the ask becomes an accepted Turn, and the admitted team plan card lands in the same conversation | Proven |
| 3 | Reads the card | Per lane: the Agent, the first bounded Todo with priority and action kind, the acceptance signal, and an explicitly unstaffed lane that keeps the work it did not staff; the quota envelope and stop condition; a statement that confirming is what creates the lanes | Proven |
| 4 | Confirms | Exactly one apply and one durable write; the card reports that LoopX state will refresh | Proven, but see gap 2 |
| 5 | Checks who can actually work | — | Gap 3 |
| 6 | Corrects or pauses one lane | — | Gap 4 |
| 7 | Waits for a lane to fail and asks who fixes it / judges completion | — | Gaps 5, 6 |
| 4 | Confirms | Exactly one apply and one durable write; the card reports that LoopX state will refresh | Proven, but see gap 1 |
| 5 | Checks who can actually work | — | Gap 2 |
| 6 | Corrects or pauses one lane | — | Gap 3 |
| 7 | Waits for a lane to fail and asks who fixes it / judges completion | — | Gaps 4, 5 |

Beats 5–7 are recorded by the scenario as typed gaps with the probe that looked
for them. They are not "not implemented here" hand-waving: the scenario names
Expand All @@ -47,7 +47,7 @@ the selectors and phrases it searched for and what it found instead.
performs one durable write; the surface must not claim a lane exists before
that write, and must say what the write produced afterwards.
5. **Judge delivery by the returned result, not by the conversation.** A reply
or a message is not a completed lane. Until gap 6 closes, treat the
or a message is not a completed lane. Until gap 5 closes, treat the
conversation as the request channel and the Goal's own state as the truth.
6. **Correct in the conversation the work came from.** Steering an active run is
supported today; correcting a confirmed lane commitment is not yet, so avoid
Expand All @@ -74,12 +74,16 @@ gitignored. No live Goal, Agent, credential or local path is read or captured.

| # | Gap | Evidence the scenario recorded | Owner surface |
| --- | --- | --- | --- |
| 1 | The steward's bounded prompt set (`找下一步` / `看阻塞` / `查证据`) is defined in the client model but not reachable from the conversation | probe: no steward-prompt element, no prompt phrases before the owner types | workspace composer |
| 2 | A confirmed plan does not distinguish committed / partial / all-gap / stale / rejected per lane | probe: the only outcome sentence is the generic applied notice | steward plan commit (roadmap R1 remainder) |
| 3 | No per-lane readiness ladder (registered → bound → launchable → executing) | probe: no lane-readiness element or phrase | steward readiness (roadmap R2 / audit F6) |
| 4 | No lane-level correction (pause or supersede a confirmed commitment) | probe: no lane-correction element; only run steering exists | shared alignment (roadmap R4) |
| 5 | A failed lane does not name its blocker owner and next step | probe: no lane-blocker element or phrase | recovery/continuation (roadmap R3) |
| 6 | Completion is not judged by the lane's returned result | probe: no lane-return element or phrase | return delivery (roadmap R3) |
| 1 | A confirmed plan does not distinguish committed / partial / all-gap / stale / rejected per lane | probe: the only outcome sentence is the generic applied notice | steward plan commit (roadmap R1 remainder) |
| 2 | No per-lane readiness ladder (registered → bound → launchable → executing) | probe: no lane-readiness element or phrase | steward readiness (roadmap R2 / audit F6) |
| 3 | No lane-level correction (pause or supersede a confirmed commitment) | probe: no lane-correction element; only run steering exists | shared alignment (roadmap R4) |
| 4 | A failed lane does not name its blocker owner and next step | probe: no lane-blocker element or phrase | recovery/continuation (roadmap R3) |
| 5 | Completion is not judged by the lane's returned result | probe: no lane-return element or phrase | return delivery (roadmap R3) |

The steward prompt row used to be gap 1. The scenario now asserts it instead of
probing it: the row must expose the five shipped labels, clicking `看阻塞` must
post its message as an accepted Turn, and the composer must stay empty. A
regression fails with the missing labels named, so it cannot pass silently.

## What This Case Does Not Claim

Expand Down
27 changes: 15 additions & 12 deletions docs/product/use-cases/steward/README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
| 拍 | 老板做什么 | 界面显示什么 | 状态 |
| --- | --- | --- | --- |
| 1 | 看首屏 | Goal 看板四条 lane(需要你/执行中/观察中/已安排),每张 Goal 卡给出 Agent 与下一步那句话 | 已证明 |
| 2 | 在 Goal 对话里向管家提要求 | 这句话成为一个被接受的 Turn,被准入的团队计划卡落在同一个对话里 | 已证明 |
| 2 | 在 Goal 对话里向管家提要求 | 管家的有界快捷提示行(询问下一步 / 向 Agent 获取进度报告 / 配置定时检查 / 看阻塞 / 查证据)点击即发送、不留草稿,这句话成为一个被接受的 Turn,被准入的团队计划卡落在同一个对话里 | 已证明 |
| 3 | 阅读计划卡 | 每条 lane 的 Agent、第一刀 Todo(含优先级与 action kind)、验收信号,以及一条明确“未配齐”并保留未派工工作的 lane;配额包络与停止条件;以及“确认才会建 lane”的说明 | 已证明 |
| 4 | 确认 | 恰好一次 apply、一次 durable write;卡片提示 LoopX 状态将刷新 | 已证明,但见缺口 2 |
| 5 | 检查到底谁能干活 | — | 缺口 3 |
| 6 | 暂停或撤销某条 lane | — | 缺口 4 |
| 7 | 等某条 lane 失败,问谁负责修 / 用什么判定完成 | — | 缺口 5、6 |
| 4 | 确认 | 恰好一次 apply、一次 durable write;卡片提示 LoopX 状态将刷新 | 已证明,但见缺口 1 |
| 5 | 检查到底谁能干活 | — | 缺口 2 |
| 6 | 暂停或撤销某条 lane | — | 缺口 3 |
| 7 | 等某条 lane 失败,问谁负责修 / 用什么判定完成 | — | 缺口 4、5 |

第 5–7 拍由场景以 typed gap 记录,并带上“探针找过什么”的证据:不是“这里先不做”的一句话,而是列出了查找的选择器和文本、以及实际找到什么。

Expand All @@ -30,7 +30,7 @@
2. **确认前先看四件事**:Agent、第一刀 bounded Todo、验收信号、缺人情况。看不到缺口的卡片还不具备可评审性。
3. **缺人 lane 是信息,不是失败。** 未配齐的 lane 会保留它没能派出去的工作并给出原因,老板可以选择砍掉、补人、或接受部分交付。
4. **确认是一次落地的 durable 写入。** 确认只发一次 apply、只做一次 durable write;卡片不能在写入前声称 lane 已存在,写入后必须说明产生了什么。
5. **用回传结果判定交付,不要用对话判定。** 一条回复不等于一条 lane 完成。在缺口 6 关闭前,把对话当请求通道,把 Goal 自身状态当事实。
5. **用回传结果判定交付,不要用对话判定。** 一条回复不等于一条 lane 完成。在缺口 5 关闭前,把对话当请求通道,把 Goal 自身状态当事实。
6. **在产生工作的那个对话里纠偏。** 对运行中的 Turn 纠偏今天已支持;对已确认 lane 承诺的纠偏还没有,所以不要确认一张可能需要撤回 lane 的计划。

## 如何复现
Expand All @@ -52,12 +52,15 @@ LOOPX_PERSONAL_WORKSPACE_SCENARIO=steward-journey \

| # | 缺口 | 场景记录的证据 | 归属面 |
| --- | --- | --- | --- |
| 1 | 管家快捷提示(找下一步 / 看阻塞 / 查证据)只定义在客户端模型里,对话里点不到 | 探针:老板输入前既无 steward-prompt 元素,也无提示文本 | 工作台输入区 |
| 2 | 确认后不区分 committed / partial / all-gap / stale / rejected | 探针:只有一条通用的“已应用”提示 | 管家计划落地(roadmap R1 剩余项) |
| 3 | 没有 per-lane readiness 阶梯(registered → bound → launchable → executing) | 探针:无 lane-readiness 元素或文本 | 管家 readiness(roadmap R2 / 审计 F6) |
| 4 | 没有 lane 级纠偏(暂停或撤销已确认承诺) | 探针:无 lane-correction 元素;只有运行中 Turn 的纠偏 | shared alignment(roadmap R4) |
| 5 | lane 失败后不说明阻塞归属与下一步 | 探针:无 lane-blocker 元素或文本 | 恢复与继续(roadmap R3) |
| 6 | 不用 lane 的回传结果判定完成 | 探针:无 lane-return 元素或文本 | 交付回收(roadmap R3) |
| 1 | 确认后不区分 committed / partial / all-gap / stale / rejected | 探针:只有一条通用的“已应用”提示 | 管家计划落地(roadmap R1 剩余项) |
| 2 | 没有 per-lane readiness 阶梯(registered → bound → launchable → executing) | 探针:无 lane-readiness 元素或文本 | 管家 readiness(roadmap R2 / 审计 F6) |
| 3 | 没有 lane 级纠偏(暂停或撤销已确认承诺) | 探针:无 lane-correction 元素;只有运行中 Turn 的纠偏 | shared alignment(roadmap R4) |
| 4 | lane 失败后不说明阻塞归属与下一步 | 探针:无 lane-blocker 元素或文本 | 恢复与继续(roadmap R3) |
| 5 | 不用 lane 的回传结果判定完成 | 探针:无 lane-return 元素或文本 | 交付回收(roadmap R3) |

管家快捷提示行原为缺口 1。现在场景对它做断言而不是探针:提示行必须出现五个已发布标签,
点击“看阻塞”必须把消息作为一次被接受的 Turn 发出,输入区必须保持为空。回归时失败信息
会点名缺失标签,因此不会静默通过。

## 本文案不主张什么

Expand Down
54 changes: 46 additions & 8 deletions examples/personal-workspace-browser/steward-journey.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -163,15 +163,53 @@ export const stewardJourneyScenario = {
await stewardCard.first().click();
const goalNavigation = page.getByRole("navigation", { name: "Goal 视图" });
await goalNavigation.getByRole("button", { name: "Chat" }).click();
// Probed before the owner types, so the owner's own words cannot be
// mistaken for a rendered steward affordance.
gaps.push(await probe(page, {
beat: "2-steward-prompts",
need: "the steward's bounded prompt set (找下一步 / 看阻塞 / 查证据) reachable from the conversation",
selectors: ["[data-testid='personal-steward-prompts']", "[data-steward-prompt]"],
phrases: ["看阻塞", "查证据"],
}));
// The row is asserted rather than probed: it ships with the product, and
// a regression here sends the owner back to typing, which is the
// behaviour this beat exists to prevent. Read before the owner types, so
// the owner's own words cannot be mistaken for a rendered affordance.
const composer = page.getByLabel("向 LoopX 发送消息");
const promptRow = page.locator(".personal-quick-prompts");
await promptRow.first().waitFor({ state: "visible", timeout: 15_000 });
const promptLabels = (await promptRow.first().locator("button").allInnerTexts())
.map((label) => label.replace(/\s+/g, " ").trim())
.filter(Boolean);
const expectedPromptLabels = [
"询问下一步",
"向 Agent 获取进度报告",
"配置定时检查",
"看阻塞",
"查证据",
];
const missingPromptLabels = expectedPromptLabels.filter(
(label) => !promptLabels.some((observed) => observed.includes(label)),
);
check(
missingPromptLabels.length === 0,
`the shipped quick-prompt row exposes the steward prompt set (missing: ${missingPromptLabels.join(" / ") || "none"}; observed: ${promptLabels.join(" / ") || "none"})`,
);
const gatePrompt = "当前 Goal 有哪些 Gate 或阻塞?哪些需要我决定?";
const gateChip = promptRow.first().getByRole("button", { name: "看阻塞" });
const gateChipPresent = (await gateChip.count()) > 0;
check(gateChipPresent, "the 看阻塞 steward chip is rendered in the quick-prompt row");
let chipTurn;
if (gateChipPresent) {
await gateChip.click();
for (let attempt = 0; attempt < 40 && !chipTurn; attempt += 1) {
chipTurn = api.turnRequests.find((request) => request.message === gatePrompt);
if (!chipTurn) await page.waitForTimeout(50);
}
}
check(Boolean(chipTurn), "clicking the 看阻塞 chip posts its message as an accepted Turn");
const composerAfterChip = await composer.inputValue();
check(
composerAfterChip === "",
`a chip click sends immediately and leaves no draft behind (composer: ${JSON.stringify(composerAfterChip)})`,
);
record("2-steward-prompts", {
prompt_labels: promptLabels,
gate_prompt_sent: Boolean(chipTurn),
composer_after_click: composerAfterChip,
});
await composer.fill(`${STEWARD_PROMPT}:请给我一份当前 Goal 的下一步。`);
await page.getByRole("button", { name: "发送", exact: true }).click();
let turn;
Expand Down