Skip to content

Commit d2d61ba

Browse files
committed
docs(pm-dispatch): dispatch token economics — sonnet tier for S-class cards, scoped local gates, slimmed prompts (#6863)
Three coupled changes to the dispatch machinery, maintainer-approved 2026-08-09 (「全部同意,先开任务」). 1. Model tiering. Step 5's absolute "pass model: opus on every dev dispatch" is replaced by a tiered policy: S-class mechanical cards dispatch on sonnet, everything with design judgment stays on opus. The old absolute is named and retired in place so a reader cannot come away with both. The claim comment's container-assessment line now carries the tier too. 2. Local gate scoping. The "enumerate every check:* from lint.yml and run all 55+ locally" rule is replaced by build closure + affected-package suites + the gate families touching the card's surface. The CI-convergence wait is restated as the load-bearing safety half, in os-dev.md and again in the PM's step-7 reconciliation. 3. Slimmed dispatch prompts. The template no longer pastes the issue body; the dev reads it on GitHub and must verify completeness (notes-12 truncation risk moves with the read). The unconditional non-negotiables sink into .claude/agents/os-dev.md once; prompts carry only deltas. Conditional standard clauses (those with 适用判据) stay in the SKILL, pasted per card. Every line PR #6799 added is present verbatim; zero hunk overlap. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F8q5J1MQyocgtNspb15fSn
1 parent f22da70 commit d2d61ba

2 files changed

Lines changed: 234 additions & 50 deletions

File tree

.claude/agents/os-dev.md

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,34 @@ reason to ask what model the batch will run on.
2626
If a dispatch genuinely needs a different model, pass `model` on the Agent call:
2727
that override takes precedence over this line, so pinning here costs nothing and
2828
removes the silent-inheritance failure mode. Removing this line puts it back.
29+
30+
THE PIN IS A FLOOR FOR THE UNSPECIFIED CASE, NOT A CEILING ON THE DISPATCHER —
31+
read this before concluding that a tiering policy cannot take effect. Claude Code
32+
resolves a subagent's model in FOUR steps, in this order (verified 2026-08-09
33+
against the subagent documentation, "Claude Code resolves the subagent's model in
34+
this order"):
35+
36+
1. the `CLAUDE_CODE_SUBAGENT_MODEL` environment variable, when set
37+
2. the per-invocation `model` parameter on the Agent call
38+
3. this definition's `model:` frontmatter
39+
4. the main conversation's model
40+
41+
So a dispatch passing `model: "sonnet"` DOES run on sonnet: step 2 outranks step
42+
3. This line decides only what happens when the dispatcher says nothing — which is
43+
the incident above, and the only case it was ever meant to decide. The PM's
44+
S-class tiering policy (`.claude/skills/pm-dispatch/SKILL.md` §5, "Model tiering")
45+
therefore takes effect as written; it is not defeated by this pin.
46+
47+
Two adjacent traps worth knowing, both from the same table:
48+
49+
• `CLAUDE_CODE_SUBAGENT_MODEL` outranks BOTH the argument and this line. It is
50+
not set in the dispatch container today (checked 2026-08-09) — but if it ever
51+
is, it silently overrides every per-dispatch tier choice the PM makes, and
52+
nothing in this repo would show it. Check the environment before concluding a
53+
dispatched tier is what actually ran.
54+
• A value your organization's `availableModels` allowlist blocks does not fall
55+
back to THIS line — it falls back to the INHERITED model, i.e. straight back
56+
into the silent-inheritance failure this pin exists to stop.
2957
-->
3058

3159

@@ -170,15 +198,89 @@ build/test runs OOM it.** Binding rules:
170198
of the pinned fakes the gate lists on a green run instead
171199
(`service-automation/src/builtin/crud-bulk-intent.test.ts` is the fullest).
172200

201+
**Local verification scope — targeted gates locally, the full farm is CI's job.**
202+
Do **not** enumerate every `check:*` step out of `.github/workflows/lint.yml` and
203+
run all 55+ locally. That rule (#5738 era) was written before "wait for CI
204+
convergence" existed, and stacking the two makes every dispatch pay for the same
205+
farm twice: once on a shared container, once on the runners that were always going
206+
to run it anyway. Your local pass is:
207+
208+
1. **Build closure first** — see toolchain trap 2; this is the first command in a
209+
fresh worktree, before typecheck or test.
210+
2. **The affected packages' own suites**`pnpm test` / `pnpm typecheck`, scoped
211+
by `--filter` per resource rule 3.
212+
3. **The gate families that touch your card's surface, and those only** — the
213+
dispatch prompt names them; add any you can see are implicated (a new fake
214+
engine ⇒ `check:engine-double-contract`; a new error code ⇒
215+
`check:error-code-casing`; `.claude/agents/**``check:agent-model-declared`;
216+
any edit at all ⇒ `check:nul-bytes`). Naming one is cheap; running all of them
217+
is what was expensive.
218+
219+
⚠️ **The accepted cost is a lap, and the safety half is NOT optional.** Scoping the
220+
local farm means a non-obvious gate can go red in CI that you would previously have
221+
caught on your own machine — an occasional extra push-fix lap, deliberately traded
222+
for not paying the full farm on every dispatch. That trade is only sound because
223+
you still **wait for CI to converge before reporting** (see the CI-convergence item
224+
in Definition of done, which this rule makes load-bearing rather than redundant).
225+
⛔ This is not licence to report before CI converges — it is the opposite: the
226+
local farm was the thing that could be dropped precisely because the CI wait
227+
cannot be. A red gate in CI is still yours to fix in this task.
228+
229+
**Standard clauses live HERE, not in your dispatch prompt.** The prompt used to
230+
repeat ~1.5k tokens of these verbatim on every dispatch; it now carries only the
231+
*deltas* for your card (ruling quotes, the 裁决 / PM-机制假设 partition,
232+
card-specific clauses, same-day churn). So the clauses below are binding on you
233+
whether or not your prompt mentions them — a prompt's silence about any of them is
234+
the expected shape, never permission:
235+
236+
- **Build before you judge anything (#6371).** In a fresh worktree the first
237+
verification command is `pnpm --filter '@objectstack/<your-pkg>^...' build`
238+
(suffix `^...` = the packages it depends on). Skip it and tsc reads whatever
239+
stale `dist/*.d.ts` someone left behind — and it lies in **both** directions:
240+
false red burns laps chasing a non-existent problem, false green lets a narrowed
241+
export type read as "consumers are clean" when the consumer never saw the new
242+
`.d.ts` at all.
243+
- **The consumer sweep's filter direction is a PREFIX (#6218).**
244+
`pnpm --filter '...@objectstack/<pkg>'` is the **downstream consumers**;
245+
`'@objectstack/<pkg>...'` (suffix) is the upstream dependencies — the opposite
246+
direction. Signature narrowing, exported-type changes and contract tightening
247+
always land downstream. When your report says "N packages green", it **must also
248+
say which direction you used**, or the sentence cannot be reviewed: #6210's "25
249+
packages green" was a suffix sweep, and CI went red on `@objectstack/dogfood`
250+
immediately.
251+
- **A cross-package type change needs a reverse verification, not just a green.**
252+
Paste a key the new type rejects, confirm it goes red, restore it — that is what
253+
proves you actually read the rebuilt `.d.ts` rather than a cached one.
254+
- **`packages/spec`: the anchor rewrite is a product, and MERGE state is a trap.**
255+
A spec build (`gen:schema`) **rewrites** `authorable-surface.base.json` — that is
256+
the expected artifact; ⛔ never revert it, never hand-edit it to make some
257+
equality hold (hand-editing it is exactly the attack #4650 closed). The assertion
258+
that counts is `pnpm --filter @objectstack/spec check:authorable-surface` being
259+
green; `baseRev` is **allowed to lag** and a lag prints one informational line,
260+
not an error. ⛔ **Never run `gen:schema` while the tree is in MERGE state**
261+
(#5370): HEAD is still the pre-merge branch tip, so the anchor is silently rolled
262+
back to the old fork point — and the rolled-back anchor is still *authentic*, so
263+
every gate passes while a landed advance is quietly undone. Commit the merge
264+
first, then regenerate. Sister trap: `gen:schema`'s `rmSync` also wipes
265+
`gen:openapi`'s output, which shows up as ~5 bogus `expected 503 to be 200`
266+
failures in `@objectstack/rest`; restore with
267+
`pnpm --filter @objectstack/spec gen:openapi`.
268+
173269
Definition of done, in order:
174270

175271
- Implementation matches the issue's acceptance criteria.
176272
- Tests: new/updated tests covering the change; run the affected packages'
177-
`pnpm test` and `pnpm typecheck` and capture real output for the report.
273+
`pnpm test` and `pnpm typecheck` and capture real output for the report —
274+
scoped per "Local verification scope" above, not as a whole-repo sweep.
178275
- Changeset added when the change is user-visible.
179276
- Pushed with `git push -u origin claude/issue-<n>-<slug>` (retry on network
180277
failure with backoff).
181-
- **Draft** PR to `main`, body starting `Fixes #<n>`, **title and explanatory
278+
- **Draft** PR to `main`, body starting `Fixes #<n>`**but `Part of #<n>` when
279+
merging it would not close the card.** If you implemented only half of it (the
280+
other half is `needs_decision` awaiting a ruling, or was deliberately excluded by
281+
scope), the first line reads `Part of #<n>` and the body says which half you
282+
left; ⛔ never use `Fixes` to close a card that is still in the decision box.
283+
Also **title and explanatory
182284
prose in English** — GitHub artifacts (issue and PR titles, bodies, comments)
183285
are English per the maintainer ruling of 2026-08-08 quoted in AGENTS.md
184286
§Communication; Chinese is for talking to the maintainer in Claude Code, not

0 commit comments

Comments
 (0)