Skip to content

docs(pm-dispatch): fold the 2026-08-08/09 shift's measured lessons in — 7 added/merged, 2 dropped as already covered (#6902) - #7046

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-6902-pm-dispatch-shift-lessons
Aug 9, 2026
Merged

docs(pm-dispatch): fold the 2026-08-08/09 shift's measured lessons in — 7 added/merged, 2 dropped as already covered (#6902)#7046
os-zhuang merged 2 commits into
mainfrom
claude/issue-6902-pm-dispatch-shift-lessons

Conversation

@os-project-manager

@os-project-manager os-project-manager commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

Fixes #6902

Folds the shift's measured lessons into .claude/skills/pm-dispatch/SKILL.md. +148 / −5 lines, one file (git diff --stat origin/main...HEAD).

The card carries eight numbered lessons plus a ninth in its own comments (the 03:35Z maintainer ruling that superseded the 03:31Z draft). The ninth is treated as in scope: it is a maintainer ruling recorded on this card, and this file's own step 1 says a ruling that lives only in a comment is a ruling the next reader skips.

Branch cut from main at 6dd3c251b, i.e. after all five of tonight's SKILL merges — #6871 (dispatch-model economics), #6886 (domain vocabulary), #6959, #6911 and #6974. The dispatch order named two; the log named five, so the whole file was re-read before writing rather than diffed against the two.

Disposition of every entry — added / merged / dropped

# Entry Disposition Where it landed, and why there
1 Push branch, open draft PR, then wait for CI — as a fixed dispatch clause Merged (one paragraph), and the card's prescription deliberately NOT followed Both halves already existed: the producer-side ordering is os-dev.md's Definition of done (push → draft PR → CI convergence → report), and the PM-side "report lost ≠ acceptance stalled" path is step 6 with its three conditions and the #5550/#5556 precedent. Writing it into the dispatch order as the card asks would contradict #6871, which sank unconditional clauses into os-dev.md precisely so prompts carry deltas only. What was genuinely new is the reason, so that is what landed: a paragraph in step 6 recording that the death cause can be fleet-wide (two account-wide token exhaustions, four in-flight devs each time), that in that cell the probe half is unavailable too so only conditions (a) and (c) are readable, and that the four cards lost zero information because the PR was already up. It says explicitly that this does not license copying the ordering into prompts.
2 File surface must say "expected landing site + what to do when the producer is in another package" Added — step 5, immediately under the ruling/assumption partition A dispatch order naming one path asks the dev to choose between keeping its word and fixing the right thing — and os-dev.md rule 5 (contract-first) already binds it to the second. Both paths verified present on main: consumer packages/core/src/utils/filter-tokens.ts, producer packages/spec/src/data/context-tokens.zod.ts. Added the mechanical fact the card leaves implicit: cross-package usually means cross-lane here (#5586 is labeled domain:engine-core; packages/spec is permanently the spec seat's under "shared contract surfaces have one owner"), so the route is rule 4's existing cross-seat machinery plus the after-the-fact declaration (#6532 precedent, #6017) — no new mechanism minted.
3 Closing/voiding on someone else's instruction needs a provenance comment Added — State model, label discipline The neighbouring bullets bind a state change to its paired record; none binds it to its reason, and a closure's reason cannot be read back out of the state machine. Correction relayed: #6668 is a PR, not an issue (draft ADR-0123, closed unmerged 2026-08-08 13:54Z) — issue_read get_labels cannot even resolve it. The new bullet points at the Guardrails ADR clause that already cites the same PR, so a reader does not think there are two #6668 stories: that one records the decision, this one the execution record.
4 Enumerate the gate list on the spot, never from memory Merged — step 5, next to the template's local-gate line Both trigger events verified in today's tree: check:kernel-hook-pairs at lint.yml:551 (#6672) and check:app-nav-i18n at lint.yml:1219 (#6661). The card's prescription needed correcting before relaying: "enumerate from .github/workflows/lint.yml" is itself memory-shaped — measured today, 61 pnpm check:* invocations are in lint.yml and 7 are not (ci.yml, spec-liveness-check.yml, validate-deps.yml, release.yml, showcase-smoke.yml). So the paragraph gives a command across all workflows instead of a filename, and folds in the dispatching seat's matching finding (#6865: four of six relayed required-context names live in ci.yml) and #6673 (a relayed line number that landed in a different function). It also states the boundary #6871 created: the PM takes the reading, not the dev — devs no longer run the farm locally, so the accuracy of naming is now the PM's alone.
5 Alternative discipline when the maintainer exempts same-file serialization Added — step 3, directly under the "no exception" paragraph Placed there because that paragraph currently reads as absolute; an exemption written anywhere else would read as a contradiction. Discipline ① (region-level declaration) is cross-referenced to the seat post's hot-file serial queue rather than restated, with the division of labour named: that section records the standing cross-round order, this one the current round's disjointness proof. Guardrails' "file-disjoint by construction" bullet gained a one-clause pointer for the same reason.
6 PM mechanism assumptions must be marked "measure this, falsification encouraged" Dropped as already covered — but its gap was fixed The rule exists three times over: step 5's ruling/assumption partition with three same-day falsifications, step 3's unlock paragraph, step 7's "acknowledge it in the open". Restating it would be the fourth. What measurement showed was a third category neither block covers: an option the PM merely suggests. Both of tonight's overturns were that shape — #6865's asked-for "no if:" assertion would have reddened four correct jobs, and #6893's dispatch offered excluding content/docs/releases/** as defensible when that is #4920's rejected option A. Verified rather than relayed: scripts/docs-audit/check-audit-scope.mjs calls process.exit(1) when no release page remains in scope, and its comments name #4920 and #6893 by number. So the partition became three blocks — in the prose, in the prompt template, and in the bullet above the template, all in one pass, since a template disagreeing with its own explanation is the drift this file keeps paying for.
7 Cloud cards must be archived after merge Merged — landing section B, into the existing unsubscribe rule archive_session appeared nowhere in the file before this. It fires on the same event as the unsubscribe that was already written there, so it belongs in that sentence rather than in a new paragraph or in the backends section: the trigger is the card's terminal state (PR MERGED or card voided), not "the dev reported". Cost kept: 11 finished-but-unarchived idle containers, which do not error, do not occupy the queue, and show on no board.
8 Split cards inherit the parent's release label Added — release board, right under the binary criterion Placed under the criterion because the honest rule is "re-run the criterion on each half", not "copy the label". #6806 was verified in both directions and it is a specimen of both: its body argues "why pm:queue without target:v17" using the four blocking classes, and that reasoning was later re-judged — today #6806 and #5495 both carry target:v17. The rule therefore reads: default is inheritance (the obligation follows the work), and a half judged not to inherit must argue it on the card in the four-class terms, falsifiably. Producer discipline unchanged.
9 Wave rhythm: finish a wave, archive, compact, continue (03:35Z ruling, superseding the 03:31Z rotation draft) Added — step 9, after the round-report metrics The wave boundary is where the round loop already ends, so a reader looking for "what happens between waves" looks there. Records the cost reading that drove the ruling (cache_read 10.18M against 6.9K uncached input on a ~2h cloud card), the four ordered steps, and the two boundaries the ruling itself draws: seat rotation demoted to an end-of-shift action because compaction preserves session bindings (PR subscriptions, self-bound send_later timers, the seat post's session ID, cloud-card parentage), and Routine seats are exempt since each fire rebuilds from GitHub. Step 2 of the four is explicitly framed as settling an existing debt ("state changes do not sleep over"), not a new obligation.

Two entries from the dispatching seat's own shift notes rode along, both as merges into rules they sharpen, not as new rules:

Verification — stated plainly, because the honest answer is unusual here

Skill prose is not asserted by any test in this repo. There is no before-red/after-green artifact to produce for a documentation sentence, and manufacturing one would be worse than saying so. What is verifiable was run, and what the card claimed was measured rather than transcribed.

Gates, all green in the worktree (every gate whose script reads .claude/**, found by grep -rln "\.claude" scripts/*.mjs):

Gate Result
check:nul-bytes 6500 tracked text files, no raw ASCII control bytes
check:skill-frame-sync 12 self-test cases pass; 4 isomorphic frame copies across 3 files, 4 count mentions agree, 42 markdown files scanned for undeclared copies
check:skill-frame-freshness 12 self-test cases pass; frame current with origin/main (fetched at run time)
check:skill-compatibility 18 self-test cases pass; 11 SKILL.md files reconciled against 77 workspace packages
check:doc-authoring self-test passes (scope wiring confirms .claude is in scope); 374 files clean
check:agent-model-declared 18 self-test cases pass (run because the diff cites os-dev.md; that file is untouched)

Also run: grep -naP over the changed file for the full control-byte class beyond NUL — clean. check:skill-examples is not applicable: this file carries no os:check blocks.

Contradiction sweep — the whole file was re-read against the additions, and three collisions were resolved in the edit rather than left for a reader:

  1. The card asks for a fixed push/PR/CI clause in the dispatch order; docs(pm-dispatch): dispatch token economics — sonnet tier for S-class cards, scoped local gates, slimmed prompts (#6863) #6871 sank unconditional clauses out of prompts. Resolved by landing the reading, not the clause, and saying so in the text.
  2. The card asks the dispatch order to say "enumerate from lint.yml"; docs(pm-dispatch): dispatch token economics — sonnet tier for S-class cards, scoped local gates, slimmed prompts (#6863) #6871 forbids the dev enumerating the farm locally. Resolved by making the PM the party who takes the reading.
  3. Entry 5's exemption path vs. Guardrails' "file-disjoint by construction". Resolved with a one-clause pointer in Guardrails so the two rules agree instead of merely coexisting.

Numbering discipline observed: no Operational note was added or renumbered. Every entry landed inside an existing section next to the rule it modifies, which is what keeps the numbered ledger stable.

Not merged by an AI seat

This PR governs the PM seat's own behaviour, so self-review-and-merge is prohibited — it is held for the maintainer, and no auto-merge is armed. docs/adr/** and content/docs/releases/** are untouched. .claude/-only ⇒ no changeset; skip-changeset applied by the author with the label set read back after the bots settled.


Generated by Claude Code

… — 7 added/merged, 2 dropped as already covered (#6902)

Seven of the card's nine entries land as additions or merges into existing
rules; two are dropped because #6871's clause-sinking and the existing
falsification partition already say them. Two of the card's own claims were
re-verified and corrected before relaying (the gate list is not confined to
lint.yml; #6668 is a PR, not an issue).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F8q5J1MQyocgtNspb15fSn
@vercel

vercel Bot commented Aug 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 9, 2026 11:43am

Request Review

@os-project-manager os-project-manager added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 9, 2026 — with Claude
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation labels Aug 9, 2026
… so that number was never issued

check:adr-anchors (inside the ESLint job) went red on it: the number is a squat
on the next free ADR id, taken from the title of a closed, unmerged draft. It
carried no load in the sentence — the load-bearing facts are that #6668 was a
draft ADR, all green, and closed by a sibling seat without provenance — so the
number is replaced by the fact that makes it wrong, which is strictly more
informative than the citation was.

⛔ Not fixed by allowlisting: that list is SHRINK-ONLY and its header says
adding an entry is never the fix for a red build. ⛔ Not fixed by writing
docs/adr/0123-*.md either: an ADR-touching PR cannot be merged by this seat
(#6741).

ADR-0107 / ADR-0001 also appear in that gate's output; both are pre-existing
allowlisted history citations on origin/main, in files this PR does not touch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F8q5J1MQyocgtNspb15fSn

Copy link
Copy Markdown
Collaborator Author

Held for the maintainer — this seat will not merge it. From the dispatching domain:devx PM seat (session_01F8q5J1MQyocgtNspb15fSn). This PR edits .claude/skills/pm-dispatch/SKILL.md, which governs this seat's own behaviour, so self-review-and-merge is prohibited. Marked ready for review; auto-merge deliberately not enabled. CI is green (ESLint and TypeScript Type Check both read as job conclusions; mergeable_state clean).

Ruling on the open question: option A — keep the ninth entry.

It is a maintainer ruling (the 03:35Z wave/compaction decision, superseding the 03:31Z rotation draft) that currently lives only in a comment thread on a card about to close. This file's own step 1 says a ruling that lives only in a comment is a ruling the next reader skips — and entry 3 of this very PR exists to stop exactly that. Dropping it to honour the dispatch order's word "eight" would preserve a number at the cost of losing the ruling. If you disagree, the dev notes it is one contiguous block in step 9 and lifts out cleanly.

Three things in this PR corrected upstream errors rather than inheriting them — worth knowing before review, since two of the corrections are to instructions this seat gave:

  1. The dispatch order was wrong about the file's recent history. It said SKILL.md changed twice tonight (docs(pm-dispatch): dispatch token economics — sonnet tier for S-class cards, scoped local gates, slimmed prompts (#6863) #6871, docs(pm-dispatch): write down the domain:* label vocabulary and the seat roster (#5469) #6886). The log shows five (docs(pm-dispatch): dispatch token economics — sonnet tier for S-class cards, scoped local gates, slimmed prompts (#6863) #6871, docs(pm-dispatch): write down the domain:* label vocabulary and the seat roster (#5469) #6886, docs(pm-dispatch): give domain:spec-tooling its table row — contested paths split by the "orbits the spec contract" criterion (#5469) #6959, docs(pm-dispatch): give the objectui local backlog its own target:v17 producer (#6903) #6911, docs(pm-dispatch): resolve the self-contradiction left by #6959 and #6886 landing one merge apart (#5469) #6974). The dev re-read the whole file instead of diffing against the two it was told about.
  2. Two of the card's own claims were re-verified and corrected before being written down — the gate-list prescription ("enumerate from lint.yml") is wrong today, because 61 pnpm check:* invocations are in lint.yml and 7 are not (ci.yml, spec-liveness-check.yml, validate-deps.yml, release.yml, showcase-smoke.yml), so the landed text gives a command across all workflows rather than a filename; and docs(adr): ADR-0123 — layered master data, template/override linkage and the resolution organization (#4585) #6668 is a PR, not an issue.
  3. The ADR-0123 CI red was traced to its source rather than silenced. The number came from the title of PR docs(adr): ADR-0123 — layered master data, template/override linkage and the resolution organization (#4585) #6668, which closed unmerged, so it was never issued. Fixed by dropping the citation and stating the fact that makes it wrong — explicitly not by adding an allowlist entry (that list is shrink-only and its own header says adding to it is never the fix for a red build), and not by writing docs/adr/0123-*.md, which would have made this PR unmergeable by this seat under ⛔ Discipline: ADRs are confirmed and merged by the maintainer only — no AI seat may merge, queue, or auto-merge a docs/adr/** PR #6741.

⚠️ One finding from this task deserves your attention independently of this PR: #7055. .claude/agents/os-dev.md:150 prescribes git worktree remove <path> --force. That is the root cause of a safety warning another dev tripped earlier tonight — it was following its own agent definition, not improvising. --force suppresses the refusal that protects a parallel agent's uncommitted work and the branch's own unpushed commits. The flag is not gratuitous (a worktree that ran pnpm install carries untracked node_modules, and the unforced form refuses), so the finding names the direction that keeps the guard armed: drop node_modules first, then remove unforced. This dev verified that sequence exits 0 in this very task.


Generated by Claude Code

@os-zhuang
os-zhuang added this pull request to the merge queue Aug 9, 2026
Merged via the queue into main with commit b3fe0f9 Aug 9, 2026
21 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-6902-pm-dispatch-shift-lessons branch August 9, 2026 13:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SKILL(pm-dispatch): 八条单班实测教训 —— 派发令模板、跨包落点、云卡归档、拆分卡标签继承

3 participants