From 9856a651ee4b0e368bd8e3ddb348e482912c3c7e Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 2 Aug 2026 08:30:17 +0000 Subject: [PATCH 1/3] =?UTF-8?q?feat(agents):=20pm-dispatch=20escalation=20?= =?UTF-8?q?bar=20=E2=80=94=20obvious=20fixes=20ship,=20the=20maintainer=20?= =?UTF-8?q?gets=20a=20veto=20window,=20not=20a=20permission=20gate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_012C2cd7tL8QDoZ2QKN3djJ5 --- .changeset/pm-dispatch-escalation-bar.md | 4 +++ .claude/agents/os-dev.md | 7 ++++++ .claude/skills/pm-dispatch/SKILL.md | 32 ++++++++++++++++++++++-- 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 .changeset/pm-dispatch-escalation-bar.md diff --git a/.changeset/pm-dispatch-escalation-bar.md b/.changeset/pm-dispatch-escalation-bar.md new file mode 100644 index 0000000000..83bc57f27e --- /dev/null +++ b/.changeset/pm-dispatch-escalation-bar.md @@ -0,0 +1,4 @@ +--- +--- + +Internal agent tooling only (`.claude/` pm-dispatch escalation bar + API-body generics trap) — releases nothing. diff --git a/.claude/agents/os-dev.md b/.claude/agents/os-dev.md index 3913ba4cb3..295ece5f24 100644 --- a/.claude/agents/os-dev.md +++ b/.claude/agents/os-dev.md @@ -89,3 +89,10 @@ Final message — exactly this JSON, no prose around it: Use `status: "rework"` for a partial result you know is incomplete (say why in `summary`); the PM will review and re-dispatch with feedback. + +Practical trap when filing issues/PRs through the GitHub API: the body +sanitizer strips `<` followed by a letter as an HTML tag **at rest**, which +destroys TypeScript generics (`Assert>` is stored as `Assert>`). +Write generics with a space after each `<` — `Assert< Equal< 1, 2 > >` is +still valid TypeScript — and read the stored body back to verify when a +snippet is load-bearing. diff --git a/.claude/skills/pm-dispatch/SKILL.md b/.claude/skills/pm-dispatch/SKILL.md index 61005e5729..fe0401a43c 100644 --- a/.claude/skills/pm-dispatch/SKILL.md +++ b/.claude/skills/pm-dispatch/SKILL.md @@ -313,8 +313,36 @@ Verdict per issue: ### 8. Escalate uncertainties to the maintainer -Whenever a dev returns `needs_decision`, an issue is too vague to dispatch, or -rework has failed twice: +**First, apply the escalation bar — most things that FEEL like decisions are +not.** The maintainer's words: 「明显的问题直接修,不是事事都需要我确认」. +Escalate ONLY when at least one of these holds: + +- the options genuinely diverge on **product semantics or public contract + shape** and neither the issue, AGENTS.md, ADRs, nor existing code norms + determines the answer; +- the fix requires a **destructive or hard-to-reverse action** (stored-data + migration shape, deleting a shipped capability, force operations). + +Everything else is the PM's call — decide, dispatch, and give the maintainer +a **veto window instead of a permission gate**: state what you decided and +why in the issue comment and the round report; they can stop it, but you do +not wait for them. Named non-escalation classes (act immediately): + +- **Restore-invariant fixes.** When the repo already states the invariant — + one contract version across the family, declared = enforced, a gate must + actually compile/run what it claims to check — a finding that the + invariant is broken carries its own decision. A dual-version dependency + graph, an inert tripwire, an unwired gate: queue it, dispatch it, report + it. Asking "may I restore the invariant?" is the anti-pattern. +- **Sequencing and dependency ordering** between technical tasks. +- **Verification strategy** (what regression pass a risky-but-decided change + needs) — that is scoping the work, not deciding it. +- A dev's `needs_decision` that, on PM review, falls into the classes above: + answer the dev yourself with the decision and rationale; do not relay it + upward. + +Whenever a dev returns `needs_decision` that passes the bar above, an issue +is too vague to dispatch, or rework has failed twice: 1. **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 From 04e02ccda0c8d66ef080b42960bfa01d998612e6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 2 Aug 2026 08:45:57 +0000 Subject: [PATCH 2/3] 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 Claude-Session: https://claude.ai/code/session_012C2cd7tL8QDoZ2QKN3djJ5 --- .claude/agents/os-dev.md | 17 +++++++++++++++++ .claude/skills/pm-dispatch/SKILL.md | 13 +++++++++++++ 2 files changed, 30 insertions(+) diff --git a/.claude/agents/os-dev.md b/.claude/agents/os-dev.md index 295ece5f24..6f67067ce3 100644 --- a/.claude/agents/os-dev.md +++ b/.claude/agents/os-dev.md @@ -33,6 +33,23 @@ rules that most often get missed: consumer (`??` alias, tolerant parse), the bug is at the producer or in the spec — fix it there, or return `needs_decision`. +**Resource discipline — parallel agents share ONE container; unbounded +build/test runs OOM it.** Binding rules: + +1. **Serialize the heavy phase.** Wrap every build and test run in the shared + verification lock, so editing parallelizes but memory peaks never stack: + `flock -w 7200 /tmp/os-heavy-verify.lock -c ''` + (one lock file per container; waiting on it is normal, not a hang). +2. **Cap the heap.** Prefix heavy commands with + `NODE_OPTIONS=--max-old-space-size=4096` (raise only with a reason). +3. **Scope, don't sweep.** Build and test the affected packages + (`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. + Definition of done, in order: - Implementation matches the issue's acceptance criteria. diff --git a/.claude/skills/pm-dispatch/SKILL.md b/.claude/skills/pm-dispatch/SKILL.md index fe0401a43c..21a4771bb6 100644 --- a/.claude/skills/pm-dispatch/SKILL.md +++ b/.claude/skills/pm-dispatch/SKILL.md @@ -242,6 +242,19 @@ Follow your operating procedure (you are the os-dev agent). Non-negotiables: Return ONLY the JSON report defined in your agent definition. ``` +#### Resource limits — parallel agents share ONE container + +Memory peaks come from **build + test**, not editing, so the fix is not less +parallelism but serialized heavy phases: the os-dev definition requires every +build/test run to hold the container-wide verification lock +(`flock /tmp/os-heavy-verify.lock`), a `NODE_OPTIONS=--max-old-space-size` +heap cap, scoped `--filter` builds/tests, capped vitest/turbo workers, and +worktree cleanup after the PR is up. PM-side: treat `batch:3` as assuming +normal-sized tasks — for build-heavy ones (dependency-family upgrades, full +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. + #### Dispatch backends **`mode:subagent` (default).** The `Agent` tool, as described above. The devs From f69127b70e7b41bef8c41202a5dadfc2cca2719f Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 2 Aug 2026 08:52:37 +0000 Subject: [PATCH 3/3] =?UTF-8?q?feat(agents):=20pm-dispatch=20scaling=20?= =?UTF-8?q?=E2=80=94=20repo=20sharding=20for=20multi-PM,=20multi-account?= =?UTF-8?q?=20claims,=20batch:5=20operating=20point?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 Claude-Session: https://claude.ai/code/session_012C2cd7tL8QDoZ2QKN3djJ5 --- .claude/skills/pm-dispatch/SKILL.md | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/.claude/skills/pm-dispatch/SKILL.md b/.claude/skills/pm-dispatch/SKILL.md index 21a4771bb6..ec94231039 100644 --- a/.claude/skills/pm-dispatch/SKILL.md +++ b/.claude/skills/pm-dispatch/SKILL.md @@ -102,7 +102,23 @@ known case: accepting a `repo:objectui` PR ⇒ file a `pm:queue` issue in `objectstack` — "run `pnpm objectui:refresh` and land the console bump", referencing the merged PR, blocked-by it until it actually merges. -**4. One board, no second tracker.** The pm labels above are the state +**4. Multiple PM sessions shard by repo — never share one queue.** The +claim protocol makes concurrent PMs *safe*, not *useful*: batch +independence (file-disjointness) is only checked within one PM's view, so +two PMs on the same queue can claim different issues that collide on +shared files, and the merge queue is one lane regardless. Scaling order: + +1. One PM, bigger batch (`batch:5` is the maintainer's chosen operating + point, riding on the resource discipline above), heavy tasks via + `mode:cloud` — adds compute without adding schedulers. +2. When one PM genuinely can't keep up: a second session takes a **whole + repo** as its shard (`/pm-dispatch repo:objectstack-ai/objectui`) — + file universes are disjoint by construction. A sharded PM states its + shard in every claim comment and **never claims outside it**; cross-repo + parent/sub-issue chains stay with the main-backlog PM. +3. Multiple PMs on the SAME queue: prohibited — all cost, no throughput. + +**5. One board, no second tracker.** The pm labels above are the state machine; an org-level GitHub Project pulling issues/PRs from all three repos gives the maintainer a single view (filter by `repo:*` and `pm:*`). The PM maintains no tracking state outside GitHub — that invariant is what keeps @@ -201,6 +217,16 @@ execute atomically, in order: Dev agents push their branch early — a remote branch is the hardest evidence of work in flight, closing the gap between "claimed" and "PR exists". +**Multiple GitHub accounts (colleagues' Claude Code sessions) simplify +this, not complicate it.** Across accounts the assignee alone already says +*who*: `assignee isn't you → taken, never touch` is the entire cross-account +protocol, and it's already the rule. The claim-comment ritual (branch name, +round, race check) matters *within* one account's sessions. When several +accounts work the backlog, partition it the same way as multi-PM sharding — +by repo or by an agreed label per account — and record the assignment table +once in a pinned issue or the round report so nobody triages another +account's shard. + **Stale-claim reclaim**: a claim older than ~24 h whose promised branch does not exist on the remote and has no PR is presumed dead — comment asking, and after another window of silence, remove the assignee (note why) and return