Skip to content

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
os-zhuang merged 2 commits into
mainfrom
claude/issue-17728-seating-time-routine
Sep 12, 2026
Merged

os-zhuang merged 2 commits into
mainfrom
claude/issue-17728-seating-time-routine

Conversation

@claude

@claude claude Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

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_later to an optional accelerator, in SKILL.md and its core-rules.md subset in the same PR (SKILL.md 〈优先级〉: 「一条规则在本文与核心条款一处改动,另一处同 PR 同改。」), and makes sign-off delete that Routine explicitly.

Base 9c577c18a (origin/main at dispatch) → head 9f2bfbfd2 (two commits: the rewrite at 049609264, then the rework below). Three files, +8/−8, every file at its ratchet ceiling before and after: SKILL.md 812 → 812, references/core-rules.md 151 → 151, references/seat-post-protocol.md 91 → 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 their origin/main spelling 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)

  • before :71 「- 自设定时器只定下一次唤醒,⛔ 不定本轮长度。」
  • before :72 「- 定时器默认在轮末挂、延时 ≤55 分钟;维护者明示可改。」
  • after :71 「- 入座即建唯一自绑 cron Routine(create_trigger,own_followup)作默认唤醒;id 记座位贴说明段。」 (115 B)
  • after :72 「- 轮末 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_trigger Routine that seat-post-protocol.md 〈座位 Routine 创建〉 forbids (no GitHub tools), per platform-readings.md 「自绑唤醒的是已存在的会话、工具已加载」.

B. 〈收集〉 :570 → :570 (same count)

  • before 「- ① 定时器重挂是每次巡检的第一动作,先挂后查,链条对中断免疫。」
  • after 「- ① 巡检首动作 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:

  • before 「- ② 在飞期间主巡检间隔 ≤45 分钟,待命期 60–70。」
  • after 「- ② 在飞期间主巡检间隔 ≤45 分钟,待命期 60–70;节奏维护者明示可改。」 (95 B)

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 B

  • :17 before 「- 自设定时器只定下次唤醒、不定轮长;新 fire 到时上一轮未清空即接续,⛔ 不重开不重排。」
  • :17 after 「- 入座自绑 cron Routine 作默认唤醒、只定下次唤醒;fire 到时未清空即接续,⛔ 不重开不重排。」 (120 B)
  • :18 before 「- 分诊席跑普通直连会话、默认判断档,裁决归总监席;自排下次唤醒不超过 55 分钟。」
  • :18 after 「- 分诊席跑普通直连会话、默认判断档,裁决归总监席;send_later 只是 ≤55 分钟加速器。」 (114 B)
  • :117 before 「- 收集先扫 GitHub 且报告缺席 ⛔ 永不读作成功;探活每轮先重挂定时器。」
  • :117 after 「- 收集先扫 GitHub 且报告缺席 ⛔ 永不读作成功;探活先确认 Routine 启用,在飞再重挂加速器。」 (119 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)

  • :47 before 「6. list_triggers 清点本会话全部自设定时器,逐个清理或随移交物转交。」
  • :47 after 「6. 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)

  1. SKILL.md states the seating-time self-bound Routine as the default wake-up and the round-end one-shot as optional; the sign-off list deletes it; frame-sync, ratchet and id-lint green. — :71 「作默认唤醒」, :72 「是可选加速器 … ⛔ 不作唯一唤醒」, seat-post-protocol :47 「删入座 Routine」; check:skill-frame-sync exit 0, check:pm-skill-ratchet exit 0, check:pm-skill-id-lint exit 0 (below).
  2. A seat following the text and skipping one round-end re-arm still wakes within one Routine period. — the wake-up is the cron Routine armed at seating (:71); the one-shot is 「⛔ 不作唯一唤醒」 (:72); the patrol's first action confirms the Routine is still enabled rather than re-arming a link (:570 「漏挂不断链」). A skipped send_later therefore 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 --commands derived 16 commands from the three-path change set (merge base 9c577c18a; the list is identical to the first head's); all 16 run on this head, --ran reconciles 16/16 with recorded exit codes, 0 NOT-MEASURED, 0 UNRUN.

command exit
pnpm check:pm-skill-ratchet 0 — SKILL.md 812/812, core-rules 151/151, seat-post-protocol 91/91
pnpm check:skill-frame-sync 0 — one declared frame copy, coherent; 74 files scanned for undeclared copies
pnpm check:pm-skill-id-lint 0 — 27 files clean
pnpm check:nul-bytes 0
pnpm check:pm-governed-prose 0
pnpm check:pm-governed-merges 0
pnpm check:agent-test-spelling 0
pnpm check:doc-authoring 0
pnpm check:driver-memory-census 0
pnpm check:refd-timer-probe 0
pnpm check:watch-hint-literal 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions 0 (needs @objectstack/formula + @objectstack/lint built; built under the verify lock, VERDICT command-exit 0)
node scripts/check-closing-keyword-parity.mjs and --self-test 0 / 0
node scripts/check-comment-mask-corpus.mjs 0 — 6654 files, 0 disagree
node scripts/pm/check-governed-queue-guard.mjs --self-test 0

Byte 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 on origin/main. Control-character scan of the three files: zero hits.

Acceptance notes

  • noted, not filed: 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. 承接者:无.
  • Governed surface (.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

…-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
…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
@claude

claude Bot commented Sep 12, 2026

Copy link
Copy Markdown
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
os-zhuang marked this pull request as ready for review September 12, 2026 15:26
@os-zhuang
os-zhuang enabled auto-merge September 12, 2026 15:26
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 12, 2026
Merged via the queue into main with commit 80ef826 Sep 12, 2026
39 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-17728-seating-time-routine branch September 12, 2026 15:50
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>
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 needs-user-decision size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants