Skip to content

Commit b9b7310

Browse files
os-zhuangclaude
andauthored
feat(agents): pm-dispatch 升级判据收紧 — 明显的问题直接修,维护者持否决权而非审批权 (#4576)
* feat(agents): pm-dispatch escalation bar — obvious fixes ship, the maintainer gets a veto window, not a permission gate Maintainer feedback after #3182 sat waiting for confirmation: obvious problems should just be fixed. Escalation now requires genuine product/ contract divergence with no repo-norm answer, or destructive/irreversible action. Named non-escalation classes act immediately: restore-invariant fixes (dual-version dependency graphs, inert tripwires, unwired gates), sequencing, verification strategy, and dev needs_decision reports that fall into those classes (the PM answers the dev directly). Also records the GitHub body-sanitizer generics trap in the os-dev definition. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012C2cd7tL8QDoZ2QKN3djJ5 * feat(agents): resource discipline for parallel dev agents in one container Maintainer-reported OOM under parallel dispatch. Memory peaks are the build/test phases, so heavy phases serialize on a container-wide flock (/tmp/os-heavy-verify.lock) while editing stays parallel; plus heap caps (NODE_OPTIONS max-old-space-size), scoped --filter builds/tests, capped vitest/turbo workers, and worktree cleanup after the PR is up. PM-side: batch:2 or mode:cloud for build-heavy tasks; OOM-killed agents redispatch alone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012C2cd7tL8QDoZ2QKN3djJ5 * feat(agents): pm-dispatch scaling — repo sharding for multi-PM, multi-account claims, batch:5 operating point - Multiple PM sessions shard by whole repo, never share one queue (batch independence is only checked within one PM's view; the merge queue is one lane regardless). Scaling order: bigger batch + mode:cloud first, repo shard second, same-queue multi-PM prohibited. - Multiple GitHub accounts (colleagues' Claude Code) simplify claiming: assignee alone disambiguates across accounts; the claim-comment ritual is for sessions within one account. Partition by repo/label with a recorded assignment table. - batch:5 recorded as the maintainer's operating point, riding on the container resource discipline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012C2cd7tL8QDoZ2QKN3djJ5 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 21d29cf commit b9b7310

3 files changed

Lines changed: 98 additions & 3 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
---
3+
4+
Internal agent tooling only (`.claude/` pm-dispatch escalation bar + API-body generics trap) — releases nothing.

.claude/agents/os-dev.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,23 @@ rules that most often get missed:
3333
consumer (`??` alias, tolerant parse), the bug is at the producer or in the
3434
spec — fix it there, or return `needs_decision`.
3535

36+
**Resource discipline — parallel agents share ONE container; unbounded
37+
build/test runs OOM it.** Binding rules:
38+
39+
1. **Serialize the heavy phase.** Wrap every build and test run in the shared
40+
verification lock, so editing parallelizes but memory peaks never stack:
41+
`flock -w 7200 /tmp/os-heavy-verify.lock -c '<build/test command>'`
42+
(one lock file per container; waiting on it is normal, not a hang).
43+
2. **Cap the heap.** Prefix heavy commands with
44+
`NODE_OPTIONS=--max-old-space-size=4096` (raise only with a reason).
45+
3. **Scope, don't sweep.** Build and test the affected packages
46+
(`pnpm --filter <pkg> build/test`), not the whole repo, unless the task
47+
explicitly requires a full pass. Cap test parallelism:
48+
vitest `--maxWorkers=2`, turbo `--concurrency=2`.
49+
4. **Clean up when done**: after the PR is up, remove your worktree
50+
(`git worktree remove <path> --force`) — leftover `node_modules` trees
51+
exhaust the container's disk, which fails as confusingly as OOM.
52+
3653
Definition of done, in order:
3754

3855
- Implementation matches the issue's acceptance criteria.
@@ -89,3 +106,10 @@ Final message — exactly this JSON, no prose around it:
89106

90107
Use `status: "rework"` for a partial result you know is incomplete (say why in
91108
`summary`); the PM will review and re-dispatch with feedback.
109+
110+
Practical trap when filing issues/PRs through the GitHub API: the body
111+
sanitizer strips `<` followed by a letter as an HTML tag **at rest**, which
112+
destroys TypeScript generics (`Assert<Equal<1, 2>>` is stored as `Assert>`).
113+
Write generics with a space after each `<``Assert< Equal< 1, 2 > >` is
114+
still valid TypeScript — and read the stored body back to verify when a
115+
snippet is load-bearing.

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

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,23 @@ 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. One board, no second tracker.** The pm labels above are the state
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:
110+
111+
1. One PM, bigger batch (`batch:5` is the maintainer's chosen operating
112+
point, riding on the resource discipline above), heavy tasks via
113+
`mode:cloud` — adds compute without adding schedulers.
114+
2. When one PM genuinely can't keep up: a second session takes a **whole
115+
repo** as its shard (`/pm-dispatch repo:objectstack-ai/objectui`) —
116+
file universes are disjoint by construction. A sharded PM states its
117+
shard in every claim comment and **never claims outside it**; cross-repo
118+
parent/sub-issue chains stay with the main-backlog PM.
119+
3. Multiple PMs on the SAME queue: prohibited — all cost, no throughput.
120+
121+
**5. One board, no second tracker.** The pm labels above are the state
106122
machine; an org-level GitHub Project pulling issues/PRs from all three repos
107123
gives the maintainer a single view (filter by `repo:*` and `pm:*`). The PM
108124
maintains no tracking state outside GitHub — that invariant is what keeps
@@ -201,6 +217,16 @@ execute atomically, in order:
201217
Dev agents push their branch early — a remote branch is the hardest evidence
202218
of work in flight, closing the gap between "claimed" and "PR exists".
203219

220+
**Multiple GitHub accounts (colleagues' Claude Code sessions) simplify
221+
this, not complicate it.** Across accounts the assignee alone already says
222+
*who*: `assignee isn't you → taken, never touch` is the entire cross-account
223+
protocol, and it's already the rule. The claim-comment ritual (branch name,
224+
round, race check) matters *within* one account's sessions. When several
225+
accounts work the backlog, partition it the same way as multi-PM sharding —
226+
by repo or by an agreed label per account — and record the assignment table
227+
once in a pinned issue or the round report so nobody triages another
228+
account's shard.
229+
204230
**Stale-claim reclaim**: a claim older than ~24 h whose promised branch does
205231
not exist on the remote and has no PR is presumed dead — comment asking, and
206232
after another window of silence, remove the assignee (note why) and return
@@ -242,6 +268,19 @@ Follow your operating procedure (you are the os-dev agent). Non-negotiables:
242268
Return ONLY the JSON report defined in your agent definition.
243269
```
244270

271+
#### Resource limits — parallel agents share ONE container
272+
273+
Memory peaks come from **build + test**, not editing, so the fix is not less
274+
parallelism but serialized heavy phases: the os-dev definition requires every
275+
build/test run to hold the container-wide verification lock
276+
(`flock /tmp/os-heavy-verify.lock`), a `NODE_OPTIONS=--max-old-space-size`
277+
heap cap, scoped `--filter` builds/tests, capped vitest/turbo workers, and
278+
worktree cleanup after the PR is up. PM-side: treat `batch:3` as assuming
279+
normal-sized tasks — for build-heavy ones (dependency-family upgrades, full
280+
regression passes) drop to `batch:2`, or dispatch that issue via
281+
`mode:cloud` so it gets its own container. If an agent dies with a
282+
heap/OOM signature, redispatch it alone rather than into a full batch.
283+
245284
#### Dispatch backends
246285

247286
**`mode:subagent` (default).** The `Agent` tool, as described above. The devs
@@ -313,8 +352,36 @@ Verdict per issue:
313352

314353
### 8. Escalate uncertainties to the maintainer
315354

316-
Whenever a dev returns `needs_decision`, an issue is too vague to dispatch, or
317-
rework has failed twice:
355+
**First, apply the escalation bar — most things that FEEL like decisions are
356+
not.** The maintainer's words: 「明显的问题直接修,不是事事都需要我确认」.
357+
Escalate ONLY when at least one of these holds:
358+
359+
- the options genuinely diverge on **product semantics or public contract
360+
shape** and neither the issue, AGENTS.md, ADRs, nor existing code norms
361+
determines the answer;
362+
- the fix requires a **destructive or hard-to-reverse action** (stored-data
363+
migration shape, deleting a shipped capability, force operations).
364+
365+
Everything else is the PM's call — decide, dispatch, and give the maintainer
366+
a **veto window instead of a permission gate**: state what you decided and
367+
why in the issue comment and the round report; they can stop it, but you do
368+
not wait for them. Named non-escalation classes (act immediately):
369+
370+
- **Restore-invariant fixes.** When the repo already states the invariant —
371+
one contract version across the family, declared = enforced, a gate must
372+
actually compile/run what it claims to check — a finding that the
373+
invariant is broken carries its own decision. A dual-version dependency
374+
graph, an inert tripwire, an unwired gate: queue it, dispatch it, report
375+
it. Asking "may I restore the invariant?" is the anti-pattern.
376+
- **Sequencing and dependency ordering** between technical tasks.
377+
- **Verification strategy** (what regression pass a risky-but-decided change
378+
needs) — that is scoping the work, not deciding it.
379+
- A dev's `needs_decision` that, on PM review, falls into the classes above:
380+
answer the dev yourself with the decision and rationale; do not relay it
381+
upward.
382+
383+
Whenever a dev returns `needs_decision` that passes the bar above, an issue
384+
is too vague to dispatch, or rework has failed twice:
318385

319386
1. **Default: the decision lives ON the issue it belongs to — never a new
320387
issue.** Post the analysis as a comment on that issue, add the

0 commit comments

Comments
 (0)