Skip to content

Commit e504a68

Browse files
os-zhuangclaude
andauthored
docs(pm-dispatch,os-dev): 串行接力一夜沉淀的六条缺口补进 SKILL —— 接力模式、锚点措辞、裁决传播扫描、停摆纠偏、飞行中重叠、预期红停放 (#5441) (#5501)
* docs(pm-dispatch,os-dev): 串行接力一夜沉淀的六条缺口补进 SKILL —— 接力模式、锚点措辞、裁决传播扫描、停摆纠偏、飞行中重叠、预期红停放 (#5441) 2026-08-04/05 夜 spec 车道以串行接力连落 10 个 PR(#5304#5365),六个情形是 现行 SKILL 没有覆盖、靠现场即兴的,各有实付学费。按 issue 注明的落点章节逐条插入: - 第 7 步「入队与落地」新增平行小节「串行接力」:每棒一整圈(auto-merge 由 PM 挂、 dev 永不碰,ready 与 auto-merge 顺序不可反且每棒各走一次)、相邻棒同文件交接语义 而非文本(#5318/#5319 实例)、两棒散文互锁由 PM 指派分工(#5323#5365#5335)。 - 「入队与落地 A」新增锚点断言措辞:authenticity = baseRev 是 origin/main 祖先 且 keys 与该 commit 逐行一致;baseRev 允许滞后;⛔ 不得要求 baseRev == merge-base (那会教唆手改锚点,即 #4650 攻击自身)。四步序的第 3 步补上「先 commit merge」, 并引用 #5370 / #5371 两个新陷阱(仅引用,不实现)。 - 第 5 步派发词 + 第 7 步 review 新增「裁决传播 = 全仓 pin 扫描」:翻 pin 一轮翻完 且必须保留承重(#5322 裁决、#5365 的 REST 层漏翻)。 - 第 6 步 Collect 的 subagent 半边新增停摆纠偏:watcher 永不触发,中途状态即停摆 信号,第三次视为不可靠改走接手协议。生产端半边同步落到 os-dev.md 资源纪律第 6 条。 - 第 5 步 same-day churn 新增姊妹段「飞行中范围重叠」:每轮读 origin/main 时对每个 在飞 dispatch 做相交判断,相交即预警(#5322 × #5335 实例)。 - 「入队与落地 B」新增依赖 PR 的预期红停放:draft 停放 + 签名级预期红清单 + 解除 条件,新签名才是真问题(#5365 的 REST 红即由此识别)。 仅改 `.claude/` 内部 agent 协议文本,不发布任何包;六条落点之外未动任何段落。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GX3sL71LFq8m2usg6VqTSE * docs(pm-dispatch): 停摆纠偏第三条的交叉引用改指「step 5」—— 接手协议在第 5 步,不在第 6 步之下 自查发现的方向错误:「Handing off an interrupted dev」小节是第 5 步的子节 (SKILL.md:839),而新增段落写在第 6 步(:923),原文写「below」会把读者指向 第 6 步之后的 Cloud mode 段。同段里对 cloud-mode ~2h 阈值的「below」是对的,保留。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GX3sL71LFq8m2usg6VqTSE * docs(pm-dispatch): 飞行中重叠一段的时间戳改用可核验值 —— #5335 的 merged_at 是 23:49:44Z,不是 00:0xZ issue #5441 正文写「#5335 在 00:0xZ 合入」,核 GitHub API 的 merged_at 与 main 上 squash 提交的 committer date,两者一致给出 2026-08-04T23:49:44Z。改写成「起飞后 32 分钟合入(merged_at 2026-08-04T23:49:44Z)」,把不可核验的钟点换成可核验的 时间差 + 权威字段;起飞时刻 23:17Z 沿用 issue 的记述(无仓内产物可核,且非承重)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GX3sL71LFq8m2usg6VqTSE --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 168f60f commit e504a68

2 files changed

Lines changed: 147 additions & 1 deletion

File tree

.claude/agents/os-dev.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,17 @@ build/test runs OOM it.** Binding rules:
8080
applied to every process. Record the PID of what you start and operate
8181
on that PID only (`kill $PID`, liveness via `kill -0 $PID` — a
8282
`pgrep -f` pattern can match your own watcher and never terminate).
83+
6. **Run the whole pipeline in the FOREGROUND — never park verification on a
84+
background watcher and stop.** Build and test are steps of this task: run
85+
them blocking, read the real output, continue. ⛔ Never return mid-task
86+
reasoning that "a background watcher will wake me" — a completion
87+
notification is itself the statement that no live subtask remains, so that
88+
wake-up **never arrives** and the task sits stalled until the PM pulls it
89+
back by hand (four agents, 6 stalls, ~1.5–2 h lost in one night). A
90+
completion message reading "build still in progress" or "I'll resume
91+
when…" is not a report; it is the stall. The one long wait that IS
92+
legitimate is `flock` queueing on the shared lock in rule 1 — that one
93+
blocks by design, so waiting it out is the rule, not a stall.
8394

8495
**Toolchain traps — each of these cost at least one agent a false-red lap:**
8596

.claude/skills/pm-dispatch/SKILL.md

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,45 @@ the issue's files today, and tell the dev to verify against `origin/main`
779779
rather than any working tree (Operational notes 4) — the dev's worktree is cut
780780
from `origin/main` once and never refreshes itself.
781781

782+
**In-flight overlap needs intercepting too — same-day churn only covers the
783+
dispatch instant.** The paragraph above handles "main moved before takeoff";
784+
main lands ~18 merges a day, so it moves **after** takeoff just as often.
785+
#5322's agent launched at 23:17Z and #5335 merged 32 minutes into that flight
786+
(`merged_at` 2026-08-04T23:49:44Z) — the same two compilers, two of the same
787+
four cells. The PM's routine check read `git log
788+
origin/main`, spotted the overlap and sent an immediate SendMessage warning; the
789+
agent narrowed its scope twice and dropped its own design in favour of a minimal
790+
diff replayed inside the other PR's structure. Rule: **every round, when you
791+
read `git log origin/main`, intersect each newly-landed PR against every
792+
in-flight dispatch's declared file surface** — on any intersection warn at once,
793+
with four instructions:「合 main 后重跑测试矩阵、读对方 diff 重划边界、只补它没覆盖
794+
的部分、**被完全覆盖就停下回报,⛔ 不要硬造 diff**」. One round late is one rework.
795+
796+
This is Operational notes 8's sister paragraph: notes 8 is the PM re-checking
797+
main **by symptom before enqueuing its own** shared-infrastructure fix; this one
798+
is the PM re-checking it **on behalf of someone else's in-flight agent**. Same
799+
fact that main keeps moving, two different victims — and only the PM can see the
800+
second one, because the flying agent has no view of `origin/main` moving under it.
801+
802+
**A ruling that flips public semantics ships with a whole-repo pin sweep — say so
803+
in the dispatch prompt.** When a maintainer ruling changes a public semantic
804+
(#5322 reclassified the empty combinator from *refused* to the **boolean identity
805+
element**), pins of the old position live **outside** the package being changed:
806+
the consumer layers each hold a copy — REST envelope tests, objectql, runtime.
807+
#5365's first lap flipped only the service-analytics layer and the copy in
808+
`packages/rest/src/analytics-filter-refusal-envelope.test.ts` went red in CI
809+
(`expected 200 to be 400`); a second lap cleared it. Two lines belong in the
810+
prompt:
811+
812+
- **Flip them all in one pass**:「grep 错误码 / 错误消息(如 `INVALID_FILTER`)
813+
**全仓扫描**同语义 pin,一轮翻完,不要只改本包」;
814+
- **A flipped pin must keep bearing load**: assert the **substance** of the new
815+
semantics (the reduced row count, the translated message, the status code) —
816+
not "old assertion deleted". Refusal assertions for shapes that are *genuinely*
817+
invalid stay **verbatim**: the guarded surface may never shrink. The two lines
818+
are one rule — drop the second and "flip the whole repo" degrades into "delete
819+
the whole repo's assertions", which is green and worthless.
820+
782821
**Issue 正文是线索,不是规格 —— and the dispatch wording is what makes an
783822
honest "the premise is dead" cheap to return.** Step 1's stale-premise check
784823
is the PM's sample; the dev's verification is the real thing, so the prompt
@@ -867,6 +906,30 @@ to `mode:subagent`). Per issue:
867906
do not fabricate a pending agent's result. A dev that dies or returns
868907
malformed output counts as `status: "blocked"` with its raw output attached.
869908

909+
**A stalled subagent is this half's most common failure, and it never
910+
self-heals.** When a dev stops mid-task reasoning that "a background watcher will
911+
wake me", **that watcher never fires** — a completion notification is itself the
912+
statement that no live subtask remains. Four agents stalled 6 times across the
913+
2026-08-04/05 night, every one recovered by hand, ~1.5–2 h lost. Three rules:
914+
915+
- **State the execution posture in the dispatch/relay prompt** for any long
916+
verification pipeline:「**前台(阻塞)同步执行全部步骤,中途不停止、不把构建/
917+
测试挂到后台等唤醒**」.
918+
- **A completion notification carrying a MID-TASK state IS the stall signal**
919+
"build still in progress", "I'll resume when…". SendMessage it back
920+
immediately with that posture line attached; ⛔ do not wait out any silence
921+
threshold (the cloud-mode ~2 h below): a threshold is for *no* answer, not for
922+
an answer that says the agent stopped.
923+
- **A third stall means unreliable** — re-dispatch a fresh agent onto that
924+
branch under "Handing off an interrupted dev" in step 5 (worktree already exists,
925+
read every existing commit first, re-run the verification in full, claim and
926+
assignee untouched).
927+
928+
The **producer-side** half of this rule lives in `.claude/agents/os-dev.md`'s
929+
resource discipline — fixing it at the producer beats patching it at the PM
930+
(Prime Directive #12's instinct, applied to agent protocol); these three are the
931+
backstop, not the primary fix.
932+
870933
**Cloud mode:** there is no direct return channel — collect through GitHub.
871934
Arm a `send_later` check-in (~15 min); on each wake, sweep the dispatched
872935
issues for `<!-- os-dev-report -->` comments and linked PRs, then re-arm
@@ -887,6 +950,13 @@ against the report's own claims:
887950
- Test evidence in the report shows the actual commands and passing output,
888951
not a bare "tests pass".
889952
- The diff plausibly satisfies the issue's acceptance criteria.
953+
- **A ruling-implementation PR: were the old position's pins flipped repo-wide?**
954+
Fetch the changed-file list, then grep the consumer layers (REST envelope
955+
tests, objectql, runtime) for the ruling's error code / message and confirm no
956+
copy of the old position survives — **and** that refusal assertions for
957+
genuinely invalid shapes are still there (step 5's two lines). #5365 slipped
958+
through exactly this review layer and was caught by CI instead: CI does catch
959+
it, at the price of one extra lap.
890960
- **Did the dev verify the issue's premise?** The report's
891961
`premise_still_valid` field makes the answer explicit — a `false` there
892962
reopens triage rather than failing review. A report that falsifies the
@@ -960,20 +1030,85 @@ content/docs/references/**
9601030

9611031
1. `git merge origin/main`(⛔ 禁 rebase / force-push,AGENTS.md §3)
9621032
2. `git checkout origin/main -- <上述生成物>`
963-
3. **整体重新生成**(⛔ 绝不做文本合并)
1033+
3. **先 commit 掉这次 merge,再整体重新生成**(⛔ 绝不做文本合并;⛔ 也绝不在 MERGE
1034+
状态下跑 `gen:schema` —— 那会静默倒退锚点,见下一段与 #5370)
9641035
4. 断言**所有兄弟单的条目都还在** —— #4878 落地时是四条 step17 条目并存
9651036

9661037
一个比「条目还在」更硬的旁证:去查**上一单的实现体**是否完好(#4878 合并后核
9671038
`conversions/registry.ts`#4391 的 D2 conversion 仍有 16 处命中)。条目是索引,
9681039
实现体才是被吞的重灾区。驱动本身另有缺陷(把绝对路径写死进最后跑过 `pnpm install`
9691040
的那个 worktree),见 #4868
9701041

1042+
**锚点(`authorable-surface.base.json`)的断言措辞 —— 写错会教唆 dev 手改锚点。**
1043+
#5304#4650 删除闸门的基线固化成树内锚点之后,接力模板曾用**错误断言**
1044+
「baseRev == merge-base」派发,后棒实测证伪并纠正。正确措辞(2026-08-05 夜后八棒全部
1045+
沿用,零误报):
1046+
1047+
> 断言 `pnpm --filter @objectstack/spec check:authorable-surface` **绿**即可。锚点
1048+
> authenticity 的定义是两件事:`baseRev``origin/main`**祖先**,且它记录的 keys
1049+
> **该 commit**`authorable-surface.json` 逐行一致(`verifyCommittedSurfaceBase`
1050+
> 就查这两条)。`baseRev` **允许滞后** —— `gen:schema` 只在 keys 真的漂移时才重写它
1051+
> (在 `main` 上 merge base 就是 HEAD,该文件**必然**落后自己的 surface 一个 PR),
1052+
> 滞后只打一行 `ℹ️`,不是错误。⛔ 禁止为了凑「相等」手改锚点文件 —— 那正是 #4650
1053+
> 堵住的攻击本身;⛔ 不得要求 `baseRev == merge-base`,那个等式不是任何门的判据。
1054+
1055+
配套两个新陷阱(都已立单,派发词**引用**即可,⛔ 不要在接力单里顺手实现它们):
1056+
1057+
- **#5370:merge 未 commit 就跑 `gen:schema`,会把锚点静默倒退回旧 merge-base。**
1058+
MERGE 状态下 HEAD 仍是合并前的分支 tip,`resolveSurfaceBase()` 解出的是分支的****
1059+
分叉点;倒退后的锚点**依然 authentic**(旧 rev 也是 origin/main 的祖先、keys 也对得
1060+
上),于是**全部门放行**,一次已合并的锚点推进被静默撤销。这就是四步序里「先 commit
1061+
merge,再整体重生成」的由来。
1062+
- **#5371:`gen:schema``rmSync` 顺手抹掉 `gen:openapi` 的产物。** 之后跑
1063+
`@objectstack/rest` 会拿到 5 条 `expected 503 to be 200`**假红**(`openapi.json`
1064+
被清场,不是 rest 坏了)。派发词里直接给解法:`pnpm --filter @objectstack/spec
1065+
gen:openapi` 补回,或重跑一次完整 spec build。
1066+
9711067
**B. 跟到 MERGED 为止,不是跟到「已入队」为止。** 「auto-merge 已挂上」不是终点,
9721068
维护者对此有过明确纠正。每轮同时读**队列分支**`origin/main`(Operational notes
9731069
1);红了先分签名,再在「原样重投 / 推新提交 / 重新诊断」三者里选(notes 2 与 5)。
9741070
落地之后**再核一次落地判据本身** —— 队列的合并同样走 os-regen 驱动,A 里那个静默
9751071
吞并在队列合并这一步一样能发生。
9761072

1073+
**依赖前棒才能转绿的 PR:draft 停放 + 一份精确的预期红清单。** 串行链里后棒常常先行
1074+
实现(#5365 的四条进一致性表依赖 #5323 的 mongodb 归约才成立)。这种 PR **停在
1075+
draft**,PR body 写两样东西:**精确的预期红清单**(逐条列失败测试名 + 报错签名)与
1076+
**解除条件**(「依赖 PR #N 合入」)。每个 CI-failure webhook 到达时**与该清单比对**
1077+
—— 签名匹配则静默跳过,**出现新签名才是真问题**:#5365 的 REST 层红
1078+
(`expected 200 to be 400`)正是靠这个比对被一眼认成新问题,而不是被当成已知的等待态
1079+
忽略过去。依赖合入后走「最后一轮同步 → 红清零 → 转 ready → 入队」。
1080+
1081+
这是 Operational notes 2「先认签名,再决定重投」在**故意红**上的对偶:notes 2 管的是
1082+
flaky 的偶然红,这一条管的是自己设计出来的红 —— 两者的失效方式完全相同,**把一个没
1083+
预料到的签名当成预料之中的**。所以清单必须写到签名级别,「几条测试会红」不够用。
1084+
1085+
#### 串行接力 —— 同碰生成物的多个 PR,一次只放行一个
1086+
1087+
第 3 步的 batch 模型假设**同批 file-disjoint 并行**;当「多个已实现的 PR 全碰
1088+
`packages/spec` 生成物」时该假设不成立,唯一可行形态是**串行接力**:一次只放行一个,
1089+
每合并一个就向下一棒发接力指令。2026-08-04/05 夜 spec 车道以这个形态连落 10 个 PR
1090+
(#5304#5306#5308#5318#5319#5321#5314#5312#5323#5365),
1091+
下面三条是那一夜靠现场即兴、事后固化的纪律。
1092+
1093+
**1. 每一棒都是一整圈,`auto-merge` 由 PM 挂、dev 永不碰。** 单棒循环:merge main +
1094+
上面 A 的四步重建 + 全套验证 + 兄弟单断言复核 → **PM 复核回报** → 转 ready → 挂
1095+
auto-merge / 入队。ready 与 auto-merge 的顺序不可反 —— 转回 draft 会同时掉 auto-merge
1096+
与队列成员资格(Operational notes 1),而接力循环里**每一棒都要重走这一次**,不是整条
1097+
链只走一次。ACCEPT 那一条只写了单 PR 的流程;链上每一棒逐棒照它执行。
1098+
1099+
**2. 相邻两棒同碰一个文件时,交接的是语义,不是文本。** 前棒在回报里写明它对共享文件
1100+
改动的**性质**(改名 / 提取变量 / 增补断言,**而非纯追加**),PM **原样转告**下一棒,
1101+
并要求「两个 PR 的意图**叠加**,⛔ 禁止机械取一边」。实例:#5318#5319 同动
1102+
`packages/spec/src/system/metadata-form-zod-reconciliation.test.ts`,#5319 逐行号核过
1103+
#5318 新增的十项元素、并实测两者的交互 —— 没有 #5319 的 preprocess 修复,#5318 的新
1104+
断言在 view 上是空转的。取一边会「各自绿、合起来错」,即 AGENTS.md §10「clean merge
1105+
不等于 working merge」落在同一份测试文件上的形态。
1106+
1107+
**3. 两棒散文互锁,分工由 PM 指派。** 允许前棒给后棒留占位交接(「本段由 #N 在其同步轮
1108+
翻正 / 删除」):#5323 的族 1 段落 ↔ #5365#5335 的「#5322 is its own ruling」pin 块
1109+
都是这个形状。PM 必须在**两侧**的接力指令里写明谁动、谁不动 —— 否则要么两边都动
1110+
(冲突),要么两边都不动(留下一段过期散文),而这两种结果**在 CI 上都是绿的**
1111+
9771112
### 8. Escalate uncertainties to the maintainer
9781113

9791114
**First, apply the escalation bar — most things that FEEL like decisions are

0 commit comments

Comments
 (0)