skills(pm-dispatch): arm the patrol wake-up once at seating as a self-bound Routine; the round-end one-shot is an accelerator - #17855
Merged
Conversation
…-bound Routine; the round-end one-shot is an accelerator A chain of round-end one-shots has no memory: one skipped re-arm is silence until a human notices. The seat now creates ONE self-bound recurring Routine at seating as the default wake-up (id recorded in the seat post's notes), the round-end send_later becomes an optional accelerator (≤55 min while anything is in flight, never the only wake-up), the patrol's first action confirms the Routine is enabled via list_triggers, and sign-off item 6 deletes the seating Routine explicitly. core-rules mirrors the same three rules in the same PR. Also adds the pointer beside the release prohibition to the release-window criterion (已上膛) in references/dispatch-runbook.md, paid by folding the round/round-report pair in 〈入口与角色〉 into one line (core-rules already carries that fold). All three files stay at their line ceilings; every edited line is ≤120 bytes. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK
This was referenced Sep 12, 2026
…e two round/round-report lines The pointer line beside the release prohibition was funded by joining two existing lines, which the line ratchet refuses as re-wrap buying a line for new content. Restore SKILL.md :66-:67 to their main spelling and remove the pointer; the rider rides a later SKILL.md PR that carries a genuine deletion. The seating-time Routine rewrite (:71-:72, :570-:571) and the core-rules and seat-post-protocol mirrors are unchanged. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK
Contributor
Author
维护者速读事情:座位协议里巡检唤醒靠「每轮结束时挂一个一次性定时器」,链条没有记忆——漏挂一次,座位就静默到有人发现(9 月 11 日一个席位因此空转约 7 小时,起因是你在聊天里问的那句「这种应该怎么解决」)。本 PR 把默认唤醒改为坐席时建一次自绑定的 cron Routine,轮末的一次性定时器降为可选加速器(在飞时 ≤55 分钟),巡检首动作改为「确认 Routine 在、在飞再挂加速器」,收班清单明确删掉这个 Routine。三处规则在 SKILL.md、core-rules.md、seat-post-protocol.md 同 PR 同改,行数不增(812/151/91),节奏不变。 没做的:不加巡查行(Routine 是账号级对象,巡查跑不到);#17830 遗留的「发布窗口判据指针行」这次没塞进来——第一版用合并两行来付账被本席拒收,改随下一个有真实删减的 SKILL.md PR 走。 风险:低。协议文本,门禁 16/16 绿,四轴框架块未动。 只问一字:是否合并? Generated by Claude Code |
os-zhuang
approved these changes
Sep 12, 2026
os-zhuang
marked this pull request as ready for review
September 12, 2026 15:26
os-zhuang
enabled auto-merge
September 12, 2026 15:26
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 17, 2026
…the client; destroyed evidence reads NOT MEASURED (objectstack-ai#17860) Part of objectstack-ai#17374 — first half only (expectations 1, 4 and the record for 5). The second half (expectation 2, the write-throttling 口径; expectation 3, the F3 recovery steps; and the reconciliation of `platform-readings.md` :95–:125) is deferred until PR objectstack-ai#17803 and PR objectstack-ai#17855 land; objectstack-ai#17374 remains open after this merges. Governed surface (`.claude/**`): draft PR at the human terminal. No changeset — `dispatch-gates.mjs` derives 17 gate families for the diff and none is a publish family; nothing in any package `files[]` moves. ## What changed (head `58885bc0b`, base `7f625364b`) Two files, both at their ratchet ceilings, both paid in place by deletion — line counts unchanged (82/82, 403/403), every edited line ≤120 B, ⛔ no ceiling raise, ⛔ no re-wrap, ⛔ no cross-file move. ### 1. `references/rest-channel.md` 〈通道边界〉 — the identity-bound rate-limit rule (expectation 1) Added after the `/rate_limit` credential-shape line (:11), where a seat already reads 「先跑一条 repo-scoped 探针再选通道」: ``` - 限流拒绝绑定被拒身份(报文 user ID):同身份各写通道一并耗尽,⛔ 换通道续写与重试同罪。 - 他侧只在身份不同时是退路:凭据 `GET /user` ≠ 被拒 user ID 才换;席内 PM 与 dev 同一身份。 ``` This is the operative form of the card's F1 sentence 「一次限流信号绑定在身份上;⛔ 在同一身份的另一条通道上继续写,与重试同罪」, with the one legitimate fallback stated as a check the seat performs before switching: the refusal names a user ID; the other side is a different reading only if its credential answers a different user ID on `GET /user`. Within one seat the PM and its dev subagents are one identity, so a dev's refusal is the seat's refusal. Consistency with the standing facts in `platform-readings.md` (read-only here): :95–:97 (quota is per account, one identity holds only inside a seat, ⛔ no cross-seat pool inferred from a refusal's user ID) — the rule binds to the identity named in the refusal and nothing wider; :122 (a refusal is that side's reading) — the identity comparison is how a seat tells whether the other side is another reading or the same one. The 限流 clause of :123 「限流、403、传输失败都要试过另一侧才说得出我没手段」 is the reconciliation the claim assigns to the second half; it is not touched here. Paid by (deleted, with where the content survives): - :12 「按班矩阵与降级梯住 `platform-readings.md`,⛔ 不在本表复述。」 — a pointer restated at :3 (「见 `platform-readings.md` 配额段」) and :67 (「本表只指路,⛔ 不在两处各存一份」); the 降级梯 lives in the quota section :3 already points to. - :43 「GraphQL 池为 0 的同一分钟里开得出 draft PR ⇒ 交付不必等重置。」 — the capability survives at the ✓ row directly above it (:42, `POST .../pulls` 带 `draft=true`); the prescription half 「交付不必等重置」 is the unconditional form of the act the new rule conditions on identity (same identity ⇒ the same act as a retry), so it cannot stand beside the rule. Its conditional replacement is the new line 2. ### 2. `.claude/agents/os-dev.md` — destroyed evidence reads NOT MEASURED (expectation 4) Added directly after :174 「两类跑了却没测到,都读作 NOT MEASURED,不读作绿也不读作红。」, the home of the NOT MEASURED family (:96, :100, :174, :344 on main); the report-contract section defines `open_questions` as a field but carries no verdict-reading rules, so the family home is the tighter fit: ``` - 证据已销毁(评论、卡或 PR 答 404)的复核项记 NOT MEASURED 并写因,⛔ 不记通过或「无旗」。 ``` Paid by: :172 「引用门禁结果时点名它自己印的判定行,永不引裸 `$?`:判定行由门禁写,`$?` 由你的管道写。」 — restated at :166 「门禁结果的读法:退出码在任何管道之前捕获,报告里引门禁自己印的判定行。」 and at 资源纪律 1 「结论读它印的 `VERDICT command-exit` 行,⛔ 不读裸 `$?`」; the trailing clause was rationale. ### 3. Expectation 5 — already met on main, nothing built The half-state patrol's H40 「Dangling references」 row on anchor objectstack-ai#9857 (`scripts/pm/check-half-states.mjs`) lists every open card/PR whose `#` reference answers 404. Read on the anchor's current sweep body: 363 of 400 attempted resolutions answered over 6365 distinct references; 37 do not resolve; ⛔ only HTTP 404 is read as unresolvable; every count a lower bound. A destroyed card is therefore detected by patrol, not by someone noticing. ## Acceptance reading - Criterion 1 (「客户端 A 报限流、客户端 B 有额度」): the text now answers. Same identity (the refusal's user ID equals what B's credential answers on `GET /user`) ⇒ 「停」 — B's full quota is not a fallback. Different identity ⇒ verify the two user IDs differ, then switch. The seat does not infer; it compares two numbers. - Criterion 2 (normal-quota writes untouched): both new lines fire only on a refusal; nothing slows or batches a write under normal quota, and no transport is banned (REST, GraphQL and MCP all remain in the table). - Criterion 3 (rehearsed recovery): second half, with F3. - Criterion 4 (ablation, grep on the two files): at `origin/main` `git grep -c -E '身份|限流'` on rest-channel.md = 0 and `git grep -c -E '销毁|404'` on os-dev.md = 0 (lit controls: `探针`/`/rate_limit` hit :7/:10/:11/:31; `NOT MEASURED` hit :96/:100/:174/:344). At head `58885bc0b` the same greps read 2 and 1, and `NOT MEASURED` reads 5. With the new lines removed, criterion 1's question is again unanswerable by the text. - Criterion 5: item 3 above. The three 「⛔ 三条不要走的路」 hold: no transport is banned; nothing relies on 「下次注意」 (the rule is a check with two inputs); no new quota-exhaustion exit is introduced — the fallback that existed unconditionally is now narrower, not wider. ## A measured falsification of the dispatch word The dispatch cited a 2026-09-12 reading (MCP refusal naming `user ID 319429713`; seat `/rate_limit` 15000/15000; `GET /user` answering `os-sales`) as a switch between two different users. Measured on this container at PR time: `GET /user` answers `login: os-sales`, `id: 319429713` — the same user ID the MCP refusal named. Under the rule as written the two are one identity and that switch answers 「停」, consistent with the evidence comment on the card (5628795815: one client's bucket dry while another on the same user reads full — per-(user, app) buckets on one identity). The rule is unchanged by this; it is the comparison that catches it. Reported for the seat's reading, no state changed. ## Gates (run on head `58885bc0b`, exit codes captured by redirect before any pipe) `node scripts/pm/dispatch-gates.mjs --commands` derived 17 families; all 17 run, all exit 0; `--ran` reconciles 17/17 (0 UNRUN). Named: `check:pm-skill-ratchet` (rest-channel.md 82/82, os-dev.md 403/403, 「declared cross-file moves: 1, total ceilings down 9 lines」 unchanged), `check:pm-skill-id-lint` (27 files clean), `check:skill-frame-sync` (frame untouched), `check:nul-bytes`, `check:agent-model-declared`, `check:doc-authoring`, `check:agent-test-spelling`, `check:commit-card-trailers`, `check:watch-hint-literal`, `check:refd-timer-probe`, `check:driver-memory-census`, `check:pm-governed-merges`, closing-keyword-parity (+ self-test), comment-mask-corpus, governed-queue-guard self-test, and the lint-package `check:doc-formula-expressions` after building the `@objectstack/lint` closure under `os-verify-lock.sh` (`VERDICT command-exit 0`, held 176s). Repo-wide `pnpm lint` is CI's run, not run here. ## Acceptance notes - noted, not filed: `references/rest-channel.md` :53 carries a provenance date (「两条 2026-09-12 两席实调」) of the kind the rules-only rewrite removed elsewhere; a density candidate for any later net-reducing PR on this file. 承接者:无. ## 维护者速读(草稿) **改了什么**:两个文件各加一条纪律、各删一条已在别处写过的话,行数不变。① `rest-channel.md`〈通道边界〉:限流拒绝绑在被拒的身份上(报文里的 user ID),同一身份的所有写通道一起耗尽,换通道继续写等同重试;只有另一侧凭据 `GET /user` 答出的是不同的 user ID 才算退路;席内 PM 与 dev 是同一身份。② `os-dev.md`:复核项的证据被销毁(评论、卡、PR 答 404)时记 NOT MEASURED 并写原因,不记通过、不记「无旗」。期望 5 已由 H40 巡检行满足,不另建。 **为什么改**:2026-09-10 整队被停用的复盘卡指出,当时的规矩只禁「重试」,没说限流信号绑的是身份而不是手上的客户端,于是一个席位可以合规地换通道继续写。本 PR 只写纪律,不动舰队身份结构(objectstack-ai#17392 已关)。 **风险与代价(含回滚)**:纯文本纪律,不改代码、不改门禁、不发布任何包;正常额度下的写入不受影响,也不禁用任何传输方式。删掉的两条在同文件别处仍有原话或被新条款取代。回滚 = revert 本 PR 一个 commit。 **席位意见**:(留空) **你要做的**:确认 ① 「同一身份 ⇒ 停」这条线画在你要的位置(而非按传输方式画);② 期望 2、3 与 `platform-readings.md` :123 的对齐留给第二半;然后人工直合。 --- _Generated by [Claude Code](https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK)_ Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #17728
A chain of round-end one-shots has no memory: one skipped re-arm is silence until a human notices (the card's provenance is the maintainer's own question, quoted on the card verbatim and untranslated). This PR makes the seating-time self-bound Routine the DEFAULT wake-up and demotes the round-end
send_laterto an optional accelerator, inSKILL.mdand itscore-rules.mdsubset in the same PR (SKILL.md 〈优先级〉: 「一条规则在本文与核心条款一处改动,另一处同 PR 同改。」), and makes sign-off delete that Routine explicitly.Base
9c577c18a(origin/main at dispatch) → head9f2bfbfd2(two commits: the rewrite at049609264, then the rework below). Three files, +8/−8, every file at its ratchet ceiling before and after:SKILL.md812 → 812,references/core-rules.md151 → 151,references/seat-post-protocol.md91 → 91. No changeset:.claude/**only, nothing published moves (dispatch-gates: 14 changeset families apply only once a changeset exists; none written ⇒skip-changeset).Rework (second commit): the first head also carried a pointer line beside the release prohibition (SKILL.md :34) to the release-window 「已上膛」 criterion, paid by joining :66–:67 into one line. The seat's review refused that funding — a fold buys a line for new content, which the ratchet rule forbids (
.claude/agents/os-dev.md「付行数棘轮的唯一合法货币是删内容:⛔ 不拿 re-wrap(折行合并)当筹行,新增以删减付账。」) — so the second commit restores :66–:67 to theirorigin/mainspelling byte-for-byte and drops the pointer line. That rider is not in this PR; it rides a later SKILL.md PR that carries a genuine deletion (the seat re-records it on the seat post). Everything else is as on the first head.What changed, per file (before → after; untranslated)
.claude/skills/pm-dispatch/SKILL.md(812 → 812)A. 〈入口与角色〉 :71–:72 → :71–:72 (two lines become two lines carrying the new content)
create_trigger,own_followup)作默认唤醒;id 记座位贴说明段。」 (115 B)send_later是可选加速器:在飞 ≤55 分钟,⛔ 不作唯一唤醒;只定下次唤醒,不定轮长。」 (118 B)The fired text's discipline is not restated: 〈平台读数纪律〉 :167–:168 already binds every self-set timer (「自设定时器的文本以先重读状态开头,只写关键判据」). The self-bound spelling (自绑) is deliberate — it is what distinguishes this Routine from the fresh-session
create_triggerRoutine thatseat-post-protocol.md〈座位 Routine 创建〉 forbids (no GitHub tools), perplatform-readings.md「自绑唤醒的是已存在的会话、工具已加载」.B. 〈收集〉 :570 → :570 (same count)
list_triggers确认 Routine 启用,在飞重挂加速器;唤醒归 Routine,漏挂不断链。」 (118 B)Cadence-change clause moved, not dropped — the 「维护者明示可改」 half of old :72 now sits on the cadence line it governs, 〈收集〉 :571:
The two A lines could not also hold it under 120 B (the tightest one-line fold of A measured 136 B, so A stays two lines and this clause moves to the line that states the cadence). Cadence values are unchanged. No other SKILL.md line differs from
origin/main..claude/skills/pm-dispatch/references/core-rules.md(151 → 151) — mirrors of A and Bsend_later只是 ≤55 分钟加速器。」 (114 B)Byte budget forced two elisions in the subset, both still stated in SKILL.md (core-rules :3 「细节参数……仍以 SKILL.md……为准」): 「不定轮长」 is implied by 「只定下次唤醒」 on :17, and 「⛔ 不作唯一唤醒」 is implied by 「只是……加速器」 on :18.
.claude/skills/pm-dispatch/references/seat-post-protocol.md(91 → 91)list_triggers清点本会话全部自设定时器,逐个清理或随移交物转交。」list_triggers清点本会话定时器:删入座 Routine,一次性的逐个清理或随移交物转交。」 (110 B)Untouched by design:
platform-readings.md(its :440 orphan rule 「绑定会话一死:先删旧 Routine、再从新会话重建,⛔ 永不留孤儿。」 already covers the dead-session case),dispatch-runbook.md,os-dev.md, AGENTS.md. No patrol row or check script (Routines are account-scoped; the guard is the Routine itself). No card number of three or more digits enters the skill corpus.Acceptance criteria (from the card)
check:skill-frame-syncexit 0,check:pm-skill-ratchetexit 0,check:pm-skill-id-lintexit 0 (below).send_latertherefore removes only the accelerator; the next fire is the Routine's.Gates (all on head
9f2bfbfd2, exit codes captured before any pipe)node scripts/pm/dispatch-gates.mjs --commandsderived 16 commands from the three-path change set (merge base9c577c18a; the list is identical to the first head's); all 16 run on this head,--ranreconciles 16/16 with recorded exit codes, 0 NOT-MEASURED, 0 UNRUN.pnpm check:pm-skill-ratchetpnpm check:skill-frame-syncpnpm check:pm-skill-id-lintpnpm check:nul-bytespnpm check:pm-governed-prosepnpm check:pm-governed-mergespnpm check:agent-test-spellingpnpm check:doc-authoringpnpm check:driver-memory-censuspnpm check:refd-timer-probepnpm check:watch-hint-literalpnpm --filter @objectstack/lint run check:doc-formula-expressions@objectstack/formula+@objectstack/lintbuilt; built under the verify lock,VERDICT command-exit 0)node scripts/check-closing-keyword-parity.mjsand--self-testnode scripts/check-comment-mask-corpus.mjsnode scripts/pm/check-governed-queue-guard.mjs --self-testByte widths: every edited line ≤120 B (
LC_ALL=C awk); the only line over 120 B in the three files is SKILL.md :811 (172 B), pre-existing onorigin/main. Control-character scan of the three files: zero hits.Acceptance notes
seat-post-protocol.md:13 registers 「会话 ID 或 Routine ID」 for the seat's 当前 PM triple — that is the maintainer-created seat Routine, a different object from the self-bound wake-up Routine this PR records in the 说明段. Not a conflict; recorded so the next reader does not merge the two. 承接者:无..claude/**): this PR stays a draft at the human terminal; never flipped ready, never queued, no reviewers requested by this session.维护者速读(草稿)
改了什么:PM 座位的唤醒机制换了默认形态。以前每轮结束时挂一个「下一次」的一次性定时器,漏挂一次就整个停摆;现在入座时就建一个自绑到本会话的周期 Routine 作为默认唤醒,轮末的一次性定时器降为可选加速器,巡检第一动作改为确认 Routine 仍在,收班清单明确删掉这个 Routine。核心条款子集同 PR 同改。
为什么改:维护者本人的提问就是出处 —— 一个座位因最后一轮漏挂定时器空转了约 7 小时,GitHub 上什么都看不出来。一次性定时器链条没有记忆;周期 Routine 有。
风险与代价(含回滚):纯协议文本,三个文件都钉在行数上限、每行 ≤120 字节,没有脚本或门禁变化。代价是 core-rules 子集里两处措辞因字节预算略简(「不定轮长」「不作唯一唤醒」在 SKILL.md 里仍是原话)。座位死亡时 Routine 会成孤儿,已有 platform-readings 的「先删旧、再重建」规则兜底。回滚 = revert 这两个提交。
席位意见:(留空,由席位定稿)
你要做的:受管面,人工合并;看一眼三个文件的 8 行 diff 是否同意「Routine 是默认唤醒、一次性定时器只是加速器」这个方向即可。
Generated by Claude Code
Generated by Claude Code