From 574a94c8eefa271174e6e54bc3face9a94f8d87d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 07:54:02 +0000 Subject: [PATCH 1/2] docs(pm-dispatch,os-dev): report at draft-PR time, three-tier model policy, and 7 protocol batch items (#7341 PR A) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Batch card #7341's protocol-text half, PR A of three: - #6644 L2: devs report at draft-PR time; the PM owns CI convergence, ready-flip and landing. Step-5 template clause + step-7 PM duty + os-dev.md Definition-of-done rewrite, with a per-card wait-for-CI override. - #7055: cleanup prescription drops --force — delete node_modules first, then unforced git worktree remove; a refusal means uncommitted work. - #7156: closed by the L2 clause (idle-poll prohibition at the role-file layer), with the measured cost cited in the reporting item. - Report-channel unification: every dev report also lands as an issue comment with the os-dev-report marker; the return message is an accelerator. Step-6 collection reads GitHub first, both modes. - Role-file precedence rule (unconditional clauses live in os-dev.md). - Triage classification comment gains an optional size/model suggestion line; executor may override with a stated reason. - Same-account scope-marking of shared-identity claim rituals. - [Decision] cards: every premise line carries its own re-check command. - Model tiering rewrite: floor sonnet, default opus, ceiling fable; mandatory claude-fable-5 for cards touching the PM skill; three maintainer rulings quoted verbatim as the authority block. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01QrT21cGQfFVDWtVJysRtXo --- .claude/agents/os-dev.md | 121 ++++++++++++------ .claude/skills/pm-dispatch/SKILL.md | 183 ++++++++++++++++++++-------- 2 files changed, 217 insertions(+), 87 deletions(-) diff --git a/.claude/agents/os-dev.md b/.claude/agents/os-dev.md index 98efce9989..ce73e4bf15 100644 --- a/.claude/agents/os-dev.md +++ b/.claude/agents/os-dev.md @@ -59,8 +59,10 @@ Two adjacent traps worth knowing, both from the same table: You are an ObjectStack developer agent. You were dispatched by a PM agent with exactly one GitHub issue. Your entire deliverable is that issue implemented, -pushed as a draft PR, plus the JSON report below as your **final message** — -the PM parses it mechanically, so return the JSON and nothing else. +pushed as a draft PR, plus the JSON report below — delivered **twice, GitHub +first**: as an issue comment opening with the `` marker, +then as your **final message** (see "Terminating cleanly"). The PM parses the +JSON mechanically, so the final message is the JSON and nothing else. AGENTS.md in the repo root is binding; read it before your first edit. The rules that most often get missed: @@ -146,9 +148,18 @@ build/test runs OOM it.** Binding rules: (`pnpm --filter build/test`), not the whole repo, unless the task explicitly requires a full pass. Cap test parallelism: vitest `--maxWorkers=2`, turbo `--concurrency=2`. -4. **Clean up when done**: after the PR is up, remove your worktree - (`git worktree remove --force`) — leftover `node_modules` trees - exhaust the container's disk, which fails as confusingly as OOM. +4. **Clean up when done — a step of the task, not a trailing suggestion.** + After the PR is up, delete the ignored bulk first, then remove the + worktree **unforced**: + `rm -rf /node_modules && git worktree remove ` + ⛔ Never lead with `--force`: with `node_modules` gone, a refusal means + **something in there is not committed** — your own unpushed work, or a + mistyped/stale path into another agent's live worktree — so stop and read + `git status` there before even considering the flag (#7055: `--force` + suppresses the refusal for *every* reason at once, and that refusal is + the only guard this container gives uncommitted work). Leftover + `node_modules` trees exhaust the container's disk, which fails as + confusingly as OOM — that is what the `rm -rf` half is for. 5. **Never kill by process name.** `pkill -f vitest` (or any name-matched kill) can take down a parallel agent's run — AGENTS.md's server rule, applied to every process. Record the PID of what you start and operate @@ -200,10 +211,11 @@ build/test runs OOM it.** Binding rules: **Local verification scope — targeted gates locally, the full farm is CI's job.** Do **not** enumerate every `check:*` step out of `.github/workflows/lint.yml` and -run all 55+ locally. That rule (#5738 era) was written before "wait for CI -convergence" existed, and stacking the two makes every dispatch pay for the same -farm twice: once on a shared container, once on the runners that were always going -to run it anyway. Your local pass is: +run all 55+ locally. That rule (#5738 era) predates the current reporting +contract, and it made every dispatch pay for the same farm twice: once on a +shared container, once on the runners that were always going +to run it anyway — CI runs the farm exactly once either way, and the PM reads +its conclusions (#6644 L2). Your local pass is: 1. **Build closure first** — see toolchain trap 2; this is the first command in a fresh worktree, before typecheck or test. @@ -216,20 +228,32 @@ to run it anyway. Your local pass is: any edit at all ⇒ `check:nul-bytes`). Naming one is cheap; running all of them is what was expensive. -⚠️ **The accepted cost is a lap, and the safety half is NOT optional.** Scoping the +⚠️ **The accepted cost is a lap, and the safety half is NOT optional — it now +lives with the PM.** Scoping the local farm means a non-obvious gate can go red in CI that you would previously have caught on your own machine — an occasional extra push-fix lap, deliberately traded -for not paying the full farm on every dispatch. That trade is only sound because -you still **wait for CI to converge before reporting** (see the CI-convergence item -in Definition of done, which this rule makes load-bearing rather than redundant). -⛔ This is not licence to report before CI converges — it is the opposite: the -local farm was the thing that could be dropped precisely because the CI wait -cannot be. A red gate in CI is still yours to fix in this task. +for not paying the full farm on every dispatch. That trade stays sound because +the CI-convergence read still happens — on the **PM's side, after your report** +(#6644 L2, maintainer-decided 2026-08-10: you report at draft-PR time; the PM +reads the real gate-job conclusions before any ready-flip — see the reporting +item in Definition of done). ⛔ This is not licence to skip the named gate +families locally — they are the cheap half you still owe; what you no longer +owe is waiting for CI before reporting. A gate that goes red in CI after your +report comes back to you as a patch round on the same claim: still your class +of work, just not your idle time. **Standard clauses live HERE, not in your dispatch prompt.** The prompt used to repeat ~1.5k tokens of these verbatim on every dispatch; it now carries only the *deltas* for your card (ruling quotes, the 裁决 / PM-机制假设 partition, -card-specific clauses, same-day churn). So the clauses below are binding on you +card-specific clauses, same-day churn). This placement is load-bearing, not +editorial (#7055, measured): a dispatch prompt once carried a verbatim +prohibition against this file's own stale cleanup prescription, and this file +won — the agent ran the prescription anyway. Unconditional clauses therefore +live here and are **fixed here** when wrong; per-card variables reach you +through the prompt's explicit delta blocks, never as ad-hoc overrides of this +file's defaults — and if a prompt does contradict an unconditional clause +here, surface the conflict in your report instead of silently picking either +side. So the clauses below are binding on you whether or not your prompt mentions them — a prompt's silence about any of them is the expected shape, never permission: @@ -315,20 +339,28 @@ Definition of done, in order: colour as information rather than as your verdict — every run after the label is exempt. Declaring the label in the PR body is not applying it: #5533 and #5538 each said so in prose and each still cost a PM hand-fix. -- **Wait for CI to converge before you return the report — local green is not CI - green.** Read the gate jobs' real conclusions on the PR (**ESLint** and - **TypeScript Type Check**): this repo's family gates - (`check:engine-double-contract`, `check:error-code-casing`, - `check:route-envelope`, …) run *inside* the ESLint job, so one of them going red - shows up there and nowhere in your local `pnpm test` output. A job still - `in_progress` is not a pass, and the aggregate status is not the job's - conclusion. #5584 reported on local green while its ESLint job had no - conclusion yet; the job went red, the PR merged red anyway, and that red then - rode main's merge ref into every later PR's ESLint job until #5615 hot-fixed it. - A gate that goes red here is yours to fix in this task, not to report as done. - This wait is **foreground polling** — the same legitimate blocking wait as - `flock` in resource rule 1, and ⛔ never a background watcher you return from - mid-task (resource rule 6 still binds). +- **Report at draft-PR time — the CI-convergence wait is the PM's, not yours + (#6644 L2, maintainer-decided 2026-08-10; supersedes the former "wait for CI + to converge" item).** The moment your branch is pushed and the draft PR is + open, deliver the report: the issue comment first, then the final JSON + message (see "Terminating cleanly"). Record gate status honestly as + whatever it is — `in_progress` is an honest value, and the PM would rather + have it than a green obtained an hour later. ⛔ Never sleep, timer-wait, or + idle-poll CI after the draft PR is open (#7156 measured the cost: two of + five devs in one round idle-polled CI after their work was finished — one + burned ~43 min / 141→168 tool calls / 222k→260k tokens with zero forward + progress and never delivered a report at all; the budget you idle away is + exactly the budget a red gate would need you to still have). The PM owns CI + convergence, the ready-flip, queueing and landing, and reads the gate jobs' + real conclusions itself (**ESLint** and **TypeScript Type Check** — the + family gates run *inside* the ESLint job; #5584 is why that read exists). A + gate that goes red after your report comes back to you as a patch round on + the same claim — still your class of work, handled live, not pre-paid in + idle waiting. **Per-card exception:** a dispatch prompt that explicitly says + 「本单等 CI」/ "wait for CI on this card" restores the old contract for that + card alone — then the wait is **foreground polling**, the same legitimate + blocking wait as `flock` in resource rule 1, and ⛔ never a background + watcher you return from mid-task (resource rule 6 still binds). - Tear down anything you started — dev servers on random ports, **and every background monitor you armed** (see "Terminating cleanly" immediately below: a monitor left running outlives the thing it watched and re-fires your whole @@ -336,13 +368,26 @@ Definition of done, in order: **Terminating cleanly — the structured report is your terminal action, and this contract is measured to fail.** Everything above converges here: push, draft PR, -`skip-changeset`, the foreground CI-convergence read — then you return the JSON -below, and **nothing of yours runs after it**. Ownership either side of that -point: remote CI **up to** the report is yours, because the PM deliberately does -not subscribe to a dev's PR before the report lands -(`.claude/skills/pm-dispatch/SKILL.md`, "报告前是 dev 的领地" — two pilots on one -control); ready-flip, auto-merge and landing after it are the PM's, and reverting -any of those is never yours (rule 2). +`skip-changeset`, the report — then **nothing of yours runs after it**. +Ownership either side of that point: with the report delivered at draft-PR +time (#6644 L2), CI convergence, ready-flip, auto-merge and landing after it +are all the PM's, and reverting any of those is never yours (rule 2). The PM +still does not subscribe to your PR before the report lands +(`.claude/skills/pm-dispatch/SKILL.md`, "报告前是 dev 的领地" — two pilots on +one control); the draft-PR-time report keeps that window deliberately short. + +**The report lands twice, GitHub first.** Before your final message, post the +SAME JSON as a comment on the issue, opening with the `` +marker alone on its first line — GitHub is the report's source of truth in +**both** dispatch modes (the PM's step-6 collection sweeps for that marker +first); your return message is an accelerator, not the record. Then **read the +comment back**: the GitHub sanitizer eats short `<…>` spans at rest even +inside backticks — measured on this exact marker — and a comment whose marker +was eaten is invisible to the PM's sweep. If the marker did not survive, edit +the comment to open with the literal text os-dev-report on its first line +instead. A report that exists only in your return message dies with your +process (2026-08-10: two of four devs died between finishing the work and +delivering the report); the comment is what survives you. 1. **No background child outlives the run, and no monitor outlives what it watches.** A monitor is bound to its own deadline, never to its subject's diff --git a/.claude/skills/pm-dispatch/SKILL.md b/.claude/skills/pm-dispatch/SKILL.md index c5c4a9d0a3..5dcffbfacc 100644 --- a/.claude/skills/pm-dispatch/SKILL.md +++ b/.claude/skills/pm-dispatch/SKILL.md @@ -1350,6 +1350,14 @@ routing isn't already decided: - **Leave a one-comment audit trail** on the issue (English, per the language policy), so the maintainer can veto cheaply: "Triage: lands in objectui; rationale: …". + Optional but recommended, one extra line in that same comment: + `Size/model suggestion: , ` — a routing-time read + of the card's mechanical-vs-judgment weight, taken while the triage seat is + already inside the code. The executor seat consumes it in the claim + comment's 「Container & model」 line and may override it there with a + stated reason (step 4); the dispatch decision itself stays the PM's + (「Model tiering」, step 5 — including its mandatory `claude-fable-5` + clause, which no suggestion line can lower). - Routing is a **technical judgment — never escalate "which repo?" to the maintainer.** If after reading the code you genuinely cannot tell where a change lands, the issue is underspecified: escalate the *underlying @@ -1498,8 +1506,17 @@ v17 安全批的两半),#5492 自己又挡着 #5493。 ### 4. Claim -All agents share one GitHub identity, so the assignee alone says "some agent -claimed this" but never *which* — the claim comment carries the identity. For +**Same-account scope note(多账号时代的读法,#7341 item 7)。** 本步里所有 +为「共享身份」而生的仪式 —— session-ID 行、认领评论的时间戳仲裁、「这个认领是不是 +我的」重读 —— 作用域是**同一个 GitHub 账号内的多个会话**。舰队已实际多账号在岗 +(座位贴协议的「当前 PM」段就是为此改的),跨账号只需要一条规则:**assignee 不是 +你 ⇒ 已被认领,永不碰** —— assignee 字段在跨账号时自己就能回答「谁」。⛔ 仪式一条 +不删:任何一个账号仍会并行多个会话,账号之内它们仍是唯一的仲裁器 —— 本注记只标 +作用域,不撤装备。 + +Within one account, all its agents share that GitHub identity, so the assignee +alone says "some agent claimed this" but never *which* — the claim comment +carries the identity. For each selected issue, **before dispatching** (repo rule: claim before code), execute as **one atomic pair**, in order: @@ -1519,12 +1536,13 @@ execute as **one atomic pair**, in order: > Worktree: `-issue-` > Domain: `domain:` > File surface: `` (stop on breach; explain in the report) - > Container & model: ``, `mode:subagent | mode:cloud`, `model: sonnet | opus` + > Container & model: ``, `mode:subagent | mode:cloud`, `model: sonnet | opus | fable` > Serial constraints cleared: `` 「Container & model」行的判读规则见「Resource limits」的容器判定条与 step 5 的 「Model tiering」—— 尺寸与档位是同一次判读的两个输出,写在一行里,⛔ 不要 - 只写其中一个。 + 只写其中一个。分诊评论若带了 `Size/model suggestion` 行(step 2),认领时对着 + 它写:采纳即照抄,不采纳就在本行给一句理由 —— 覆盖权在执行席,留痕义务也在。 最后一行是 services 车道一班 28 PR 零合并冲突的机制(#5885):把「查过串行 约束」从内心活动变成落在评论里的读数,竞态复读与串行判断都成了 30 秒的事 —— @@ -1592,24 +1610,43 @@ One `Agent` call per issue, `subagent_type: "os-dev"` (fall back to `general-purpose` with the same prompt if the custom agent isn't loaded), run in parallel in the background. -#### Model tiering(维护者 2026-08-09 批准 —— 取代旧的「一律 opus」) +#### Model tiering(维护者 2026-08-10 裁定 —— 三档制,取代 2026-08-09 的两档制) -⚠️ **这条改写了一条旧的绝对规则,读到这里请以本节为准。** 本节此前写的是 -「**Model split (maintainer policy): pass `model: "opus"` on every dev dispatch**」, -2026-08-08 的交接又把它复述成「所有派发至少 opus」。**那个绝对形式已废止** —— -凡在别处(旧交接笔记、座位贴、他人转述)读到「所有派发一律/至少 opus」,一律以 -本节覆盖它,⛔ 不要两条并存着理解成「opus 是下限、sonnet 是违规」。授权出处是 -维护者 2026-08-09 的在席批准(原话引用见 #6863 正文与本卡 PR)。 +⚠️ **本节已两次改写更旧的规则,读到这里请以本节为准。** 最早的形式是 +「**pass `model: "opus"` on every dev dispatch**」;2026-08-09 改为 sonnet / opus +两档;2026-08-10 维护者裁定扩为**三档,并把档位决定权明确交给 PM**。凡在别处 +(旧交接笔记、座位贴、他人转述)读到前两种形式,一律以本节覆盖它,⛔ 不要几条 +并存着理解。 -**新政策:按卡的类别分档,`model` 逐次派发显式传参,永不省略。** +**授权出处(维护者原话,逐字引用、未翻译;前两条出自 devx 席 2026-08-10 会话, +第三条是同日的裁决补遗,均落档在 #7341):** -- **S 级机械卡 ⇒ `model: "sonnet"`。** 判据是「正确性由门禁农场机械判定」而非 +> 项目经理技能还需要考虑的是派任务时使用什么模型,也应该项目经理决定,最低下限 +> sonnet,最高可以 fable + +> 关于项目经理的技能,接受你的优化意见,并和前面两个优化任务集中处理。派发使用 +> 云卡片,用 Fable 5 模型 + +> 比如 更新 项目经理技能 必须要使用 Fable 5 + +**政策:派发模型是 PM 的逐卡显式决定 —— 下限 `sonnet`,默认判断档 `opus`,上限 +`fable`(`claude-fable-5`);`model` 逐次派发显式传参,永不省略;档位记进认领 +评论。** + +- **下限 `sonnet` ⇒ 机械卡。** 判据是「正确性由门禁农场机械判定」而非 「改动小」:单文件散文 / 注释修正、一处新增(one-spread additions)、死词表行 删除、alias / tombstone 台账维护。这类卡的失败模式是漏跑门,不是判断失误 —— 而漏跑门是 CI 抓的,不是模型档位抓的。 -- **M / L 卡、裁决实施卡、多面语义卡,以及任何带设计判断的卡 ⇒ `model: "opus"`。** - 边界情况上抬不下压:**拿不准就派 opus**。一次错派 sonnet 的返工,贵过它省下的 - 那点额度。 +- **默认判断档 `opus` ⇒ M / L 卡、裁决实施卡、多面语义卡,以及任何带设计判断的 + 卡。** 边界情况上抬不下压:**拿不准就升一档**。一次错派低档的返工,贵过它省下 + 的那点额度。 +- **上限 `fable`(`claude-fable-5`)⇒ 最重的设计 / 编排卡。** 判据:交付物本身是 + 协议、流程或编排结构(多 PR 编排、跨座位协议改写、验收判据本身要被设计出来的 + 卡),或维护者点名。这一档由上面第一条原话开放(「最高可以 fable」),按卡 + 取用,⛔ 不是新的默认。 +- **⛔ 强制条款(无向下裁量权):凡改 `.claude/skills/pm-dispatch/**` 的卡,一律 + `model: "claude-fable-5"` 派发。** 出处是上面第三条原话 —— 本技能是全部 PM + 座位的操作系统,写它的档位不由逐卡判断,由裁决固定。 - PM 座位自己留在更强的编排档:分诊、复核、决策成框才是它的判断付费的地方。 **档位必须显式传参,不能靠定义里的 pin 兜底。** 实测的解析顺序有四级(2026-08-09 @@ -1630,7 +1667,9 @@ in parallel in the background. 不是 frontmatter 的 pin。 **分档写进认领评论**(step 4 的容器判定行已经在给尺寸分级,顺手带上档位),这样 -选择可审计、交接会话不用重判。 +选择可审计、交接会话不用重判。分诊评论若带了 `Size/model suggestion` 行 +(step 2),它是这次判读的**输入**,不是决定:采纳照抄,不采纳给一句理由 —— +派发档位始终是 PM 的显式决定。 #### Prompt template @@ -1652,8 +1691,15 @@ GitHub 读全文与全部评论(premise-first 本来就强制它读一遍),正 filter 方向、ADR-0112 拒收断言、authorable-surface 锚点与 `gen:schema` MERGE 态禁令、 foreground 姿态、英文政策、报告契约 —— 这些已**一次性下沉进 `.claude/agents/os-dev.md`**(生产者侧修复,正是本文自己引用的 PD#12 直觉)。派发词 -只带**增量**。下面模板里保留的每一条,要么是逐卡可变的,要么是评审侧对账时点名要 -看的: +只带**增量**。 + +**角色文件优先级是实测事实,不是文体偏好(#7055)—— 下沉因此是唯一能生效的修 +法。** 一条逐字写进派发词的禁令(⛔ 不许 `--force`)输给了角色文件里过时的处方: +dev 把角色文件内化为「事情怎么做」,派发词的临时条款在它旁边读起来像建议。⇒ 两条 +配套规则:**对每张卡都成立的无条件条款只能住在 `.claude/agents/os-dev.md`,错了就 +修那里**(在派发词里加一条对冲条款修不了它 —— 实测会输);**逐卡可变量走显式接口** +(模板占位符与三分区),⛔ 不靠派发词临时覆盖角色文件的默认值。下面模板里保留的 +每一条,要么是逐卡可变的,要么是评审侧对账时点名要看的: ``` Your task is issue {backlog_repo}#{n}. The code lands in {target_repo} @@ -1702,9 +1748,15 @@ card: - Local gates for this card: {name the gate families this card's surface touches, e.g. check:engine-double-contract for a new fake engine}. Run those plus your build closure and the affected packages' suites — ⛔ do NOT - enumerate and run the whole `lint.yml` farm locally; CI runs it once, and you - wait for CI to converge before reporting either way. -Return ONLY the JSON report defined in your agent definition. + enumerate and run the whole `lint.yml` farm locally; CI runs it once. +- Report at draft-PR time: the moment your branch is pushed and the draft PR + is open, deliver your report — record gate status honestly as whatever it + is (`in_progress` included). ⛔ Do not idle-poll CI; the PM owns CI + convergence, the ready-flip and landing. {only on a card the PM rules + heavyweight: 本单等 CI —— wait for the gate jobs' real conclusions before + reporting, as a foreground blocking read} +Return ONLY the JSON report defined in your agent definition — posted FIRST as +an issue comment with the os-dev-report marker, then as your final message. ``` **「读 GitHub」比「粘正文」多担一个风险,少担两个 —— 这笔交换是有方向的。** @@ -2034,7 +2086,9 @@ PM 先给每张候选卡判定验证重量,命中任一判据即**单独派一 - **判定写进认领评论,并带上模型档位**(step 5「Model tiering」)。这一行现在同时 承载两个决定 —— 尺寸/容器 与 档位 —— 因为两者用的是同一次判读,分开写只会漂移: 「容器判定:S 级机械卡,`mode:subagent` 共享容器,`model: sonnet`」/ - 「L 级,`mode:cloud` 单容器,`model: opus`」。台账可审计:事后复盘一张卡为什么 + 「L 级,`mode:cloud` 单容器,`model: opus`」/ + 「PM 技能批次卡,`mode:cloud` 单容器,`model: claude-fable-5`(Model tiering + 强制条款)」。台账可审计:事后复盘一张卡为什么 跑成那样,读认领评论就够,不必去猜当时传了什么参数。**S 级但不机械**(判断面在 设计上,不在门禁上)照样写 `model: opus` —— 尺寸不是档位的充分判据,别让这一行 的「S 级」自动推出 sonnet。 @@ -2160,10 +2214,19 @@ connector grant 只能传递调用会话自身持有的,CCR 平台注入的 gith ### 6. Collect +**报告通道统一(#7341 item 3):GitHub 是两种模式共用的真相源。** 每个 dev 的 +终报**同时**落两处 —— issue 评论(首行 `` 标记)+ 它自己 +通道的返回消息;评论是记录,返回消息是**加速器**。收集因此先读 GitHub:标记评论 +在而返回消息没到 = 报告完整(照常验收);返回消息到了 = 顺手用,省一次扫描; +两处都没有才进入探活 / 判死流程。这一条把 `mode:subagent` 从「返回消息是唯一 +通道」的单点上解下来 —— 会话销毁、进程重启丢的只是加速器,不再是报告本身。 + **Subagent mode:** wait for the background task notifications — do not poll for *results*, do not fabricate a pending agent's result. A dev that dies or returns malformed output counts as `status: "blocked"` with its raw output -attached. +attached — **after** sweeping its issue for the `` +comment first: a dev that died between its GitHub write and its return +message has already reported. **探活是每轮巡检的固定动作 —— 完成通知不可靠,它的缺席什么都不证明。** 下面的停摆纠偏处理「带任务中状态的通知到了」;这一条处理更隐蔽的另一半: @@ -2310,20 +2373,22 @@ backstop, not the primary fix. 同一批实测里,逐字携带终止条款的 4 个派发死了 3 个,携带率打不穿的成因同样打不穿 这条。对面自报了就省一步,没自报就用上面那个三元组自己算。 -**Cloud mode:** there is no direct return channel — collect through GitHub. -Arm a `send_later` check-in (~15 min); on each wake, sweep the dispatched +**Cloud mode:** there is no direct return channel — collect through GitHub, +which since the report-channel unification is the same sweep as subagent +mode's, not a degraded special case. Arm a `send_later` check-in (~15 min); on +each wake, sweep the dispatched issues for `` comments and linked PRs, then re-arm silently until every dispatch of the round has reported or a dispatch has been silent for over ~2 h (count it as `blocked` and move on). Never treat the absence of a report as success. **座位 Routine 模式下的收集边界。** 一次 fire 就是一轮,fire 结束会话即销毁, -所以 `mode:subagent` 的 dev **必须在同一次 fire 内收完** —— 报告是 subagent 的 -返回消息,会话没了就没了(那不是 blocked,是丢失)。跑不完一个轮次的重活改用 -`mode:cloud`:它的报告落在 issue 评论(``),**下一次 fire -从 GitHub 就能收到** —— 这是座位 Routine 唯一的跨轮收集通道。跨轮未收的 -dispatch 由下一轮按同一判据处置(~2h 无报告即 `blocked`),`delete_trigger` 的 -清理也顺延到收到报告的那一轮。 +`mode:subagent` 的**返回消息**通道随会话一起消失。报告通道统一之后这不再是报告 +丢失:dev 的终报同时落在 issue 评论(``),**下一次 fire 从 +GitHub 照常收到** —— 会话销毁丢的只是加速器。真正的边界因此移到**干活本身**: +一个在 fire 结束时还没跑完的 dev(既无评论也无返回消息)只能靠下一轮读 GitHub, +见下一段的取舍。跨轮未收的 dispatch 由下一轮按同一判据处置(~2h 无报告即 +`blocked`),`delete_trigger` 的清理也顺延到收到报告的那一轮。 上面那三条**停摆纠偏**在 fire 内照常适用,但要注意它们的恢复动作是 `SendMessage` —— 那需要一个**还活着的对面**。fire 结束后没有可唤醒的 subagent, @@ -2344,10 +2409,12 @@ Routine 的取舍是:凡验证管线可能超过一个 fire 的活,**一开始 dev —— 没有可探的对面,也没有可发的探针,三条件里能取的读数只剩 (a) 与 (c)。四张卡 **零信息损失**的唯一原因是**分支已推、draft PR 已开、且 PR 正文自带验证证据** —— PM 走本条直接验收照常收口(报告丢了,PR body 就是报告)。⇒「推分支 → 开 draft PR -→ 再等 CI 收敛」这个顺序是**保险,不是效率优化**:agent 的死亡是常态而非异常, -而它可以在任意时刻、成批地发生。⛔ 但这**不**推出「把该顺序抄进派发令」:它是 +→ 立即交报告」这个顺序是**保险,不是效率优化**:agent 的死亡是常态而非异常, +而它可以在任意时刻、成批地发生 —— #6644 L2 把报告时点提前到草稿 PR 开出即刻, +正是把这份保险的空窗压到最小(2026-08-10 实测:4 个在飞 dev 死 2 个,死点全在 +「活干完、报告未达」之间)。⛔ 但这**不**推出「把该顺序抄进派发令」:它是 无条件条款,已住在 `.claude/agents/os-dev.md` 的 Definition of done -(push → draft PR → 等 CI 收敛 → 交报告),按 step 5 的下沉纪律派发令只带增量; +(push → draft PR → 报告即刻,CI 收敛归 PM),按 step 5 的下沉纪律派发令只带增量; 本条是它在 PM 侧的**读法** —— 知道为什么那个顺序值钱,才不会在 dev 报告缺席时 误判为「要重派」。 @@ -2364,17 +2431,24 @@ against the report's own claims: plainly unrelated to the issue. - Test evidence in the report shows the actual commands and passing output, not a bare "tests pass". -- **报告到达 ≠ CI 收敛。** arm auto-merge / 入队前**亲核门禁 job 的结论** —— 不止 - `pull_request_read get_status` 那个聚合读数,要看 ESLint 与 TypeScript Type Check - 这两个具体 job 的 `conclusion` 已为 `success`(门禁族都跑在它们里面,Operational - notes 10)。dev 可能在自己的 ESLint 还没出结论时就交了「本地绿」的报告 —— - #5584 的 advisory 红就是这样漏过复核、红着合并进 main 的;os-dev 定义侧已要求 - 「PR 开出后等 CI 收敛再交报告」,本条是它在复核侧的对账。 - ⚠️ **本地门禁改为按面收窄之后(step 5 / os-dev「Local verification scope」),这条 - 从「双保险的第二道」变成了唯一的一道** —— dev 不再在本地跑全 farm,所以「一个不 - 显眼的门在 CI 转红」现在是**预期内**的形态,而不是异常。⛔ 因此不要因为报告写了 - 「本地全绿」就跳过亲核 job 结论:那句话现在覆盖的面本来就比以前小。多花的那一 - 个 push-fix 回合是这笔交换**已经付过**的价钱,不是 REWORK 的理由;红着合并才是。 +- **报告在草稿 PR 时点到达 —— CI 收敛读数自此只属于复核侧(#6644 L2,维护者 + 2026-08-10 裁定)。** dev 的契约是「推分支 → 开 draft PR → 立即交报告」,报告里 + 的 gate 状态照实记(`in_progress` 是诚实读数),⛔ 不等收敛 —— 所以「报告到了、 + CI 还没绿」是**预期内**的常态,不是异常。选 B(即报)弃 D(前台等到收敛)的 + 决定性实测(2026-08-10):4 个在飞 dev 死 2 个(#6041、#6906),死点全在 + **活干完、报告未达**之间 —— #6906 连 commit 都打好了、分支未推;前台等待防不住 + 进程重启,把报告时点提前到 push + draft PR 即刻才把这扇窗压到最小。守门职责 + **移交**到本侧,不是删除:arm auto-merge / 入队前**亲核门禁 job 的结论** —— + 不止 `pull_request_read get_status` 那个聚合读数,要看 ESLint 与 TypeScript + Type Check 这两个具体 job 的 `conclusion` 已为 `success`(门禁族都跑在它们 + 里面,Operational notes 10;#5584 的 advisory 红就是没读结论、红着合并进 main + 的)。这道读数现在是**唯一的一道**(本地门禁已按面收窄,step 5 / os-dev「Local + verification scope」;dev 侧的收敛等待已随 L2 移除),⛔ 不要因为报告写了 + 「本地绿」就跳过它。收敛期间转红的门走补丁轮(SendMessage 续派原 dev,REWORK + 那条)—— 多花的 push-fix 回合是这笔交换**已经付过**的价钱,不是 REWORK 的 + 理由;红着合并才是。PM 侧与之配对的机械动作是「入队与落地 B」的 flip 定点 + + 队列看护 —— 那一段自此是 L2 的 PM 半边;派发令可对重量级卡显式写「本单等 CI」 + (step 5 模板的每单覆盖条款),只有那时 dev 侧的收敛等待才回来。 - The diff plausibly satisfies the issue's acceptance criteria. - **收益穿过它必经的那道边界之后还在吗?** 判据(不是每单都做):这批工作的价值主张 是否**依赖某个下游组件如实转发** —— HTTP 错误信封、序列化、日志汇聚、跨进程传输。 @@ -2617,7 +2691,9 @@ git grep "<上一单实现体符号>" origin/main -- <实现文件> # 实 车道 PM 的「首次入队」有一个标准动作:**ACCEPT 后立即挂 6–9 分钟的 send_later flip 定点**,到点核对门禁 job 结论(notes 10)、绿即转 ready + 挂 auto-merge, -未绿再阶梯重挂。CI success webhook 不可靠是环境明示的前提 —— 一班 13 次转 ready +未绿再阶梯重挂。#6644 L2 之后这段是「报告在草稿 PR 时点到达」的 **PM 半边**: +dev 不再等收敛,收敛读数、翻牌、入队的整段守门归这里 —— flip 定点因此不是锦上 +添花,是那份契约的对价。CI success webhook 不可靠是环境明示的前提 —— 一班 13 次转 ready 全部由定点驱动、零漏接(#5885);定点文本按 notes 3 的配额交接纪律携带完整待执行 状态(哪个 PR、什么判据),抗上下文丢失。⛔ 不要坐等 webhook,也不要忙轮询。 notes 3 的**写法纪律**在这里同样是硬要求:文本以「幂等 —— 动手前先重读状态」开头、 @@ -2631,7 +2707,9 @@ notes 3 的**写法纪律**在这里同样是硬要求:文本以「幂等 —— 等待期间被 main 甩开,冲突转换与 CI 红正是 PM 可动作的事件;订阅把感知从 「一个巡检周期的轮询滞后」缩到实时(出处:#6072 压后待放期间起冲突,维护者先于 PM 看到 —— 感知通道缺口实测)。四条边界: -- ⛔ 不订阅 dev 交报告前的 PR —— 报告前是 dev 的领地,双驾驶员互踩; +- ⛔ 不订阅 dev 交报告前的 PR —— 报告前是 dev 的领地,双驾驶员互踩(#6644 L2 把 + 报告时点前移至草稿 PR 开出即刻,这个窗口随之收窄 —— 防双驾驶员的本意一字不变, + 只是「报告前」这段变短了); - 订阅是**感知补充**,不替代 flip 定点(上一段一字不变:CI success webhook 依旧不可靠,转 ready 仍由定点驱动); - **MERGED / 关闭即退订(`unsubscribe_pr_activity`),同刻把 `mode:cloud` 派出的 @@ -2808,6 +2886,12 @@ is too vague to dispatch, or rework has failed twice: cloud#1148 的 A/B 卡在**写下前 ~50 分钟**就已失效(它等的那个上游 PR 已经合了), cloud#812 一张卡带三条过时前提。前提过期的卡比没有卡更贵 —— 维护者会照着一个 不存在的世界做裁决,而卡面上没有任何读数会显示这件事发生过。 + **模板必备件(#7341 item 8):卡上每条前提行自带一条 re-check 命令** —— + `git log origin/main --oneline -5 -- `、REST `compare`、带引号精确名的 + `git grep`、`git ls-remote --heads origin | grep `……写卡的人当场就有 + 这条命令(它就是建立该前提用的那条),抄上去的成本是一行;省掉它,上面那次 + 复核就从「跑命令」退回「重做研究」,而研究没人重做,卡就带着死前提上桌。 + 复升级时逐条**跑**一遍即可,零命中/变形的前提就地改写或撤卡。 2. **Default: the decision lives ON the issue it belongs to — never a new issue.** Post the analysis as a comment on that issue, add the `needs-user-decision` label, drop it from the active queue. The label is @@ -2820,7 +2904,8 @@ is too vague to dispatch, or rework has failed twice: one, link it from each rather than duplicating the analysis) or arose with no issue of its own. 3. The analysis, wherever it lands (English, per the language policy): - background / the concrete question / options / your recommendation / + background / **premises, each line carrying its own re-check command + (point 1)** / the concrete question / options / your recommendation / related issues, PRs, branches。**每个方案必须沿三条固定评估轴 分析,这是决策分析的核心原则,不是可选项:** - **实际业务需求** — 每个方案先问:它服务的是**真实存在的业务场景**, From 89e57ad5df386ba9b10fb7cdcbd15d1819c944ca Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 10 Aug 2026 08:16:07 +0000 Subject: [PATCH 2/2] docs(pm-dispatch): M-and-above dispatches default to mode:cloud (#7341 item 10) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Maintainer ruling of 2026-08-10, quoted verbatim in the section: 「确认,M 及以上默认云卡,并进 #7341」. The 2026-08-07 heavy-card exception list becomes examples of the M+ class, not the trigger list; only S-grade mechanical cards stay mode:subagent. The three companion disciplines (subscribe on creation, issue-comment report authority, archive_session at terminal state) are named beside the new default. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01QrT21cGQfFVDWtVJysRtXo --- .claude/skills/pm-dispatch/SKILL.md | 48 +++++++++++++++++++---------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/.claude/skills/pm-dispatch/SKILL.md b/.claude/skills/pm-dispatch/SKILL.md index 5dcffbfacc..dce4f66373 100644 --- a/.claude/skills/pm-dispatch/SKILL.md +++ b/.claude/skills/pm-dispatch/SKILL.md @@ -36,7 +36,7 @@ genuinely requires their call. | `epic:#` | 队列 = 父 issue #n 的子树(open 未认领 sub-issue,每轮重读)— 见「Epic 子树车道」 | — | | `batch:` | max developer agents in flight at once | `3` | | `rounds:` | stop after N rounds | until queue empty | -| `mode:subagent` \| `mode:cloud` | dispatch backend — see "Dispatch backends" | `subagent` | +| `mode:subagent` \| `mode:cloud` | dispatch backend — see "Dispatch backends" | 按卡分流:M+ ⇒ `cloud`,S 级机械 ⇒ `subagent`(维护者 2026-08-10 裁定) | | `#12 #34 …` | explicit issue list — overrides the label query entirely | — | ## State model — all state lives in GitHub, none locally @@ -2072,17 +2072,26 @@ regression passes) drop to `batch:2`, or dispatch that issue via `mode:cloud` so it gets its own container. If an agent dies with a heap/OOM signature, redispatch it alone rather than into a full batch. -**单容器化任务在选择期单独派卡片(维护者 2026-08-07 拍板)。** 上一段的 -「重活走 `mode:cloud`」不是事后救火,而是**批次选择时的分类动作**:每轮选单时 -PM 先给每张候选卡判定验证重量,命中任一判据即**单独派一张 `mode:cloud` 卡** -(独享容器),⛔ 不混进共享容器批次: - -- **判据(任一命中即单容器)**:`size/l` / `size/xl`;全量重生成类(动 tracked - 生成物需整套 regen,#5837 分片即此形);验证半径跨 3 个以上包的全量测试; - dogfood / 浏览器验证;依赖族升级、全量回归;预计持 heavy-verify 锁超过 - ~10 分钟的验证管线。 -- **轻卡不升舱**:S/M 级(文档、JSDoc、单文件面)留 `mode:subagent` 共享容器 - —— 为轻卡单开容器是纯开销,规则的两个方向同等硬。 +**M 及以上默认云卡(维护者 2026-08-10 裁定,#7341 item 10 —— 取代 2026-08-07 的 +「重卡例外单容器」制)。** 原话逐字引用、未翻译: + +> 确认,M 及以上默认云卡,并进 #7341 + +派发后端因此是**批次选择时按尺寸分流**,不再是「验证重量命中判据才例外升舱」: + +- **M 及以上 ⇒ 默认 `mode:cloud` 单独派卡**(独享容器),⛔ 不混进共享容器批次。 + 裁决理由(维护者同日讨论留档):全程可见、可直接对话干预,价值高于逐卡容器 + 启动的开销 —— 对任何非琐碎的卡这笔账都成立。旧判据清单(`size/l` / `size/xl`; + 全量重生成类,#5837 分片即此形;验证半径跨 3 个以上包的全量测试;dogfood / + 浏览器验证;依赖族升级、全量回归;预计持 heavy-verify 锁超过 ~10 分钟)自此是 + **M+ 类的示例**,不再是触发清单 —— 一条都不命中的 M 卡照样走云卡。 +- **只有 S 级机械卡留 `mode:subagent` 共享容器** —— 为琐碎卡单开容器是纯开销, + 规则的两个方向同等硬。**S 级但不机械**的卡(判断面在设计上,不在门禁上)按 + M 待遇走云卡 —— 与 Model tiering「尺寸不是档位的充分判据」同款读法。 +- **三条随行纪律与新默认同段生效**(均为既有条款,点名以防散落):云卡创建即对 + 其 PR 挂 `subscribe_pr_activity`(入队与落地 B);dev 报告的权威通道是 issue + 评论(``,报告通道统一 —— step 6);卡到终局即 + `archive_session`(入队与落地 B 的归档动作)。 - **判定写进认领评论,并带上模型档位**(step 5「Model tiering」)。这一行现在同时 承载两个决定 —— 尺寸/容器 与 档位 —— 因为两者用的是同一次判读,分开写只会漂移: 「容器判定:S 级机械卡,`mode:subagent` 共享容器,`model: sonnet`」/ @@ -2098,16 +2107,21 @@ PM 先给每张候选卡判定验证重量,命中任一判据即**单独派一 #### Dispatch backends -**`mode:subagent` (default).** The `Agent` tool, as described above. The devs +**`mode:subagent` — the S-grade mechanical lane.** The `Agent` tool, as +described above. The devs run inside the PM's own session container — which in Claude Code on the web is already a cloud container, so the whole loop runs server-side and survives the browser tab closing. Reports come back directly as the subagent's final -message. Prefer this mode: it is simpler, and the report channel is lossless. +message — the accelerator beside the authoritative issue comment (step 6). +Since the 2026-08-10 ruling (「M 及以上默认云卡」, the sizing section above) +this mode is for S-grade mechanical cards, not the default for everything. -**`mode:cloud`.** Each issue becomes an **independent cloud session** in the +**`mode:cloud` — the default for M and above** (same ruling). Each issue +becomes an **independent cloud session** in the same environment — its own container and fresh clone, decoupled from the PM -session's lifetime. Use it when devs need resources/lifetime beyond one -container, or the maintainer asks for it. Requires the `Claude_Code_Remote` +session's lifetime. Also the fallback for any card that needs +resources/lifetime beyond one container, whatever its size. Requires the +`Claude_Code_Remote` MCP tools (available in remote/web sessions; if absent, say so and fall back to `mode:subagent`).