Skip to content

Commit 9c040f1

Browse files
os-zhuangclaude
andauthored
docs(pm-dispatch): domain 车道 —— 按修复落点划域,支持同仓多 PM 并发 (#4819) (#4824)
新增「Domain lanes(同仓多 PM 并发)」一节(置于 Multi-repo coordination 之后): 前提(每 PM 独立会话+独立容器)、锚定规则(每个包恰好属一个 domain;`domain:*` 取修复落点所在包的域,分诊时读代码后打,不从标题词汇猜)、#4775 反例、六域分类表、 标签纪律(打标 ≠ 认领;未打标不得认领)、认领范围(#4604 登记 domain 集合)、 跨域单与借单规则、选批时的全局在飞检查,以及合并队列仍是共享串行资源的提醒(#4796)。 认领注释模板(round loop 第 4 步)加「域」「文件面」两行,跨域与借单必填。 Multi-repo coordination 规则 4 由「同队列多 PM 一律禁止」改为「仅在 domain 车道 协议生效时允许」,repo 分片阶梯保留,domain 车道作为第三级。 仅改 .claude/skills/pm-dispatch/SKILL.md,现有章节标题与编号未动。 Claude-Session: https://claude.ai/code/session_015Br2xsJsczFsTR9bvbh2Ny Co-authored-by: Claude <noreply@anthropic.com>
1 parent 023c00b commit 9c040f1

2 files changed

Lines changed: 111 additions & 6 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
---
3+
4+
docs(pm-dispatch): domain 车道协议 —— 按「修复落点的包」划域,支持同仓多 PM 并发 (#4819)
5+
6+
`.claude/skills/pm-dispatch/SKILL.md` 新增「Domain lanes(同仓多 PM 并发)」一节:
7+
锚定规则(每个包恰好属于一个 domain,`domain:*` 标签取**修复落点所在包**的域,分诊时
8+
读代码后打,不从标题词汇猜 —— #4775 的 hook condition 概念属 automation,落点却是
9+
`packages/objectql/src/hook-wrappers.ts`,故归 `domain:engine`)、六域分类表、标签纪律
10+
(打标 ≠ 认领;未打标不得认领)、认领范围(在 #4604 登记 domain 集合)、跨域单与借单
11+
规则、选批时的全局在飞检查,以及合并队列仍是全体共享串行资源的提醒(flaky 税,#4796)。
12+
认领注释模板加「域」「文件面」两行(跨域与借单必填);Multi-repo coordination 规则 4 的
13+
「同队列多 PM 一律禁止」改为「仅在 domain 车道协议生效时允许」,repo 分片阶梯保留,
14+
domain 车道作为第三级。
15+
16+
仅改内部 agent 协议文本,不发布任何包。

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

Lines changed: 95 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,13 @@ known case: accepting a `repo:objectui` PR ⇒ file a `pm:queue` issue in
102102
`objectstack` — "run `pnpm objectui:refresh` and land the console bump",
103103
referencing the merged PR, blocked-by it until it actually merges.
104104

105-
**4. Multiple PM sessions shard by repo — never share one queue.** The
106-
claim protocol makes concurrent PMs *safe*, not *useful*: batch
107-
independence (file-disjointness) is only checked within one PM's view, so
108-
two PMs on the same queue can claim different issues that collide on
109-
shared files, and the merge queue is one lane regardless. Scaling order:
105+
**4. Multiple PM sessions shard by repo; one shared queue only under
106+
domain lanes.** The claim protocol makes concurrent PMs *safe*, not
107+
*useful* on its own: batch independence (file-disjointness) is only checked
108+
within one PM's view, so two PMs on the same queue can claim different
109+
issues that collide on shared files, and the merge queue is one lane
110+
regardless. Making that check **global** is exactly what the next section
111+
does. Scaling order:
110112

111113
1. One PM, bigger batch (`batch:5` is the maintainer's chosen operating
112114
point, riding on the resource discipline above), heavy tasks via
@@ -115,7 +117,13 @@ shared files, and the merge queue is one lane regardless. Scaling order:
115117
repo** as its shard (`/pm-dispatch repo:objectstack-ai/objectui`) —
116118
file universes are disjoint by construction. A sharded PM states its
117119
shard in every claim comment and **never claims outside it**.
118-
3. Multiple PMs on the SAME queue: prohibited — all cost, no throughput.
120+
3. Multiple PMs on the SAME queue: **prohibited unless the Domain-lanes
121+
protocol (next section) is active** — every PM in its own session and
122+
its own container, domain sets registered in the registry issue, label
123+
discipline observed, and the global in-flight check run at every batch
124+
selection. Without that protocol the ban stands as written: all cost,
125+
no throughput, and the collision stays invisible to both PMs until the
126+
merge.
119127

120128
**Shard ownership is registered, never assumed.** A registry issue in the
121129
main backlog (`[PM] 分片分工登记表`) records which session owns which
@@ -159,6 +167,81 @@ gives the maintainer a single view (filter by `repo:*` and `pm:*`). The PM
159167
maintains no tracking state outside GitHub — that invariant is what keeps
160168
the loop resumable and the board honest.
161169

170+
## Domain lanes(同仓多 PM 并发)
171+
172+
Rule 4's ladder ran out at one PM per repo because file-disjointness is only
173+
ever checked inside one PM's own view. Domain lanes are the **third rung**:
174+
one PM's triage verdict is cached as a `domain:*` label every other PM can
175+
read, so batch selection filters at the label layer instead of at the merge.
176+
Premise: each PM is its **own session in its own container** — adding a PM
177+
adds compute, not contention — and collisions are prevented by the
178+
domain→package mapping, not by hoping two PMs pick different work.
179+
180+
**Anchoring rule.** The whole scheme rests on this one sentence:
181+
182+
> Every package belongs to exactly **one** domain; an issue's `domain:*`
183+
> label is the domain of **the package the fix lands in**, decided at triage
184+
> by reading the code — **never guessed from the issue's title vocabulary**.
185+
186+
The counter-example that makes it a rule: #4775 is a hook `condition`, which
187+
reads as automation, but the fix lands in
188+
`packages/objectql/src/hook-wrappers.ts``domain:engine`. Labeling by topic
189+
would have routed it to a different PM than the one already inside that
190+
package — the exact collision lanes exist to prevent. If you cannot say which
191+
file the fix touches, you have not triaged it yet, and it is not labelable.
192+
193+
| 标签 | 包家族 |
194+
|:--|:--|
195+
| `domain:engine` | `packages/objectql``packages/metadata*``packages/platform-objects``packages/core``packages/plugins/driver-*` |
196+
| `domain:services` | `packages/services/*``packages/plugins/plugin-approvals``plugin-webhooks``packages/connectors/*` |
197+
| `domain:identity` | `packages/plugins/plugin-auth``plugin-security``plugin-sharing``plugin-audit` |
198+
| `domain:devx` | `packages/lint``skills/**``content/docs/**``scripts/`(门禁类) |
199+
| `domain:spec` | `packages/spec` 及其生成物(现 spec 车道不变) |
200+
| `domain:cli` | `packages/cli``runtime``verify``qa``types` |
201+
202+
`examples/**` belongs to the subsystem it exercises; anything that fits
203+
nowhere is judged at triage by its principal landing site. A package missing
204+
from the table is classified the first time it is triaged and the table
205+
updated **by PR** — the taxonomy evolves deliberately, never per-claim.
206+
207+
**Label discipline.** `domain:*` is applied during the backlog sweep (round
208+
loop step 0) by whichever PM triages the issue. **Labeling ≠ claiming**: any
209+
PM may label any issue, including ones it will never claim — the label is
210+
shared routing, not a reservation. An **unlabeled issue may not be claimed by
211+
anyone**: triage and label it first, or selection has silently gone back to
212+
happening inside one PM's private view.
213+
214+
**Claim scope.** Each PM session registers its **domain set** in the registry
215+
issue (`[PM] 分片分工登记表`, #4604 — the same registry that records repo
216+
shards) and claims only issues whose label falls inside that set. A set, not
217+
a single domain: lanes are a routing table, not a job title.
218+
219+
**Cross-domain issues.** Prefer the contract-first split of rule 2 — one
220+
sub-issue per domain, each carrying its own `domain:*` label, ordered with
221+
`Blocked-by:`. When a split costs more than it buys, a single PM claims the
222+
whole issue and **declares the full file surface** in its claim comment, so
223+
every other PM's in-flight check can see all of it.
224+
225+
**Borrowing.** An idle PM may claim outside its registered set when all three
226+
hold: (a) that domain's PM has not claimed the issue, (b) the claim comment
227+
declares the file surface, (c) the global in-flight check below passes.
228+
Borrowing is a one-issue exception, not a lane transfer — the registry entry
229+
does not change, so nobody has to guess who owns the domain afterwards.
230+
231+
**Global in-flight check — run it at batch selection (step 3).** List every
232+
`pm:dispatched` issue across the repo, read the file-surface declaration on
233+
each one's latest claim comment, and require your candidates to be disjoint
234+
from all of them. This is step 3's independence test raised from your batch
235+
to the whole repo; skip it and two individually-independent batches are
236+
jointly dependent, which is precisely the failure the same-queue ban was
237+
protecting against.
238+
239+
**The merge queue is still one shared serial resource.** Lanes buy parallel
240+
authorship, not parallel landing: the flaky-test tax (#4796) scales linearly
241+
with the number of PMs, and a red queue blocks every lane at once. Queue
242+
health is therefore a shared duty — a PM that notices a flake fixes or files
243+
it rather than re-queuing past it, whichever lane it came from.
244+
162245
## The round loop
163246

164247
### 0. Backlog sweep — classification is a standing duty, not a request
@@ -280,6 +363,12 @@ execute atomically, in order:
280363
> 会话:`session_<id>`
281364
> 分支:`claude/issue-<n>-<slug>`
282365
> Worktree:`<repo>-issue-<n>`
366+
> 域:`domain:<x>`
367+
> 文件面:`<预计触碰的目录列表>`(越界即停,报告说明)
368+
369+
「文件面」is **required** for cross-domain and borrowed claims and
370+
**recommended** for ordinary same-domain ones — it is the only input
371+
another PM's global in-flight check has to read.
283372
3. **Race check**: assignment is idempotent, so two agents can both
284373
"succeed". Re-read the comments; if an earlier claim comment with a
285374
*different* session ID or branch name exists, you lost — touch nothing of

0 commit comments

Comments
 (0)