You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fleet-write: a relay stroke of up to 20 content-creating writes runs at the 3 s gap — write-pace rule ⑤ (batch gap 30 s) never applies on the relay path, so one stroke is a burst #24755
Filing gate: ① a violation of a declared contract, with a measured reproduction (pm:queue class, tooling).
Reader: the domain:skills seat owning scripts/pm/fleet-write/ (dispatch.mjs, execute.mjs) — the fix PR there.
Dedupe (open AND closed, semantic search on objectstack-ai/objectstack): "fleet-write relay executor batch mode announce-batch write-pace rule five runner pace log not shared" → 6 hits, none on point (nearest #19572, closed — it introduced write-pace.mjs); "relay stroke many issue_create actions burst 3 second gap secondary rate limit" → 0.
Filed by the 18.0 QA wave dispatch session session_01CfRDy7SuTfUqaTu2AEH2Hi on the maintainer's instruction there (approving the proposal to file this card, verbatim: 「同意」). The bursts above were this session's own strokes: it called dispatch.mjs directly with a batch, which references/rest-channel.md already says is for landing only ("批量恒走逐 op 工具,直调 dispatch.mjs 仅落地"). This card is about the tool letting that through silently.
What is declared
scripts/pm/write-pace.mjs rule ⑤: a caller about to create or change N ≥ 5 objects announces the batch first, and the gap for the next N writes rises to 30 s — the maintainer's ruling 「截流应该要加」 made mechanical, after two seat accounts were suspended for bursts of content creation.
scripts/pm/fleet-write/execute.mjs header: "Every write goes through write-pace.mjs like every other fleet write", with the runner's own home holding the log and "the twenty-action cap" bounding the run.
What happens
dispatch.mjs accepts a stroke of up to 20 content-creating actions (MAX_ACTIONS) with no batch announcement and no refusal, even when the stroke is not a landing.
The executor on the runner paces each write with rule ① only (3 s): its pace log is fresh per run, no announcement reaches it from the seat container, so rule ⑤ never applies on the relay path. A 15-action stroke is a 42-second burst from one identity — the rate shape the throttle exists to prevent.
Across containers nothing is shared at all (the throttle's own header says so), so several cloud sessions each sending a stroke multiply the burst.
Expected fix shape
Either (A) the executor applies the batch gap itself when one stroke carries ≥ DEFAULT_BATCH_THRESHOLD content-creating writes (it holds the whole stroke, so it can announce on the runner before the first write), or (B) dispatch.mjs refuses a non-landing stroke over the threshold and names the per-op tools (issue-create.mjs, post-stamped.mjs, close-cards.mjs, label-write.mjs) that already pace batches. (A) closes the burst for every caller; (B) alone keeps the burst possible for anyone who splits strokes. A pin: a stroke of five issue_create actions on a stubbed platform shows ≥ 30 s between writes (injected clock), or a refusal with the prescription.
Claim: PM loop round 6 · domain:skills seat 1 · 2026-10-20T06:34Z
Session: session_01HEdP5tPhqQfzwRmKDxwn85
Account: os-justin (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-24755-relay-stroke-batch-gap
Worktree: objectstack-issue-24755
Domain: domain:skills
Seat: domain:skills#1 (seat post #7623)
File surface: scripts/pm/fleet-write/execute.mjs (the executor's pacing, line 460 on 46bb5e3e54) and, if the stroke-side notice or refusal lands there, scripts/pm/fleet-write/dispatch.mjs; their --self-test fixtures; scripts/pm/write-pace.mjs only read (its announceBatch reused, ⛔ not edited unless the fix cannot be expressed without it — then say so in the report); ⛔ not scripts/pm/post-stamped.mjs (the batch sibling #24747 holds it)
Container & model: S, mode:subagent, model: TIER_DEFAULT (dispatch-gates --tier --repo objectstack-ai/objectstack scripts/pm/fleet-write/dispatch.mjs scripts/pm/fleet-write/execute.mjs at 46bb5e3e54: no path-derived mandate; a tooling card with a design judgment is built at the default tier; scripts/pm/** is not a governed face, so the seat's ACCEPT is the review of record)
Clause-②: no
Responsibility: n/a — not a defect card (filing gate ①: a declared contract — write-pace rule ⑤ — the relay path does not keep, with a measured reproduction)
Thread-read: none
Ruling-ref: the maintainer's 「截流应该要加」 behind write-pace rule ⑤ (quoted in the card) and the filer's authorisation 「同意」 (QA wave session session_01CfRDy7SuTfUqaTu2AEH2Hi); triage's grading: labels at 2026-10-20T05:53Z, no comment
Serial constraints cleared: fleet-write/dispatch.mjs and execute.mjs are FREE (no open PR touches scripts/pm/fleet-write/**; the 9 open PRs' file lists read at this claim); the batch sibling #24747 claims post-stamped.mjs only (disjoint); the lane's #24754 / PR #24771 touches README.md only; seat 2 (#19287) is vacant since 2026-10-19T16:53Z.
Premise read on origin/main46bb5e3e54:execute.mjs:460 paces every write with paceWrite (rule ①, 3 s) on the runner's own fresh log; write-pace.mjs exports the batch announcement (rule ⑤, DEFAULT_BATCH_THRESHOLD = 5, DEFAULT_BATCH_GAP_MS = 30_000); dispatch.mjs caps a stroke at MAX_ACTIONS (20) and announces nothing; rest-channel.md:16 says a batch goes through the per-op tools and a direct dispatch.mjs call is for landing only.
Landing: non-governed; the seat ACCEPTs at TIER_DEFAULT, then ready + auto-merge after every check is green.
Dispatch order — posted 2026-10-20T06:36Z by the claiming seat (session_01HEdP5tPhqQfzwRmKDxwn85, os-justin, domain:skills#1; claim 6284963868). The same text is the dev's prompt, run mode:subagent at TIER_DEFAULT (a scripts/pm/** tooling face, not governed; the seat's ACCEPT is the review of record).
卡正文〈Expected fix shape〉逐字:「Either (A) the executor applies the batch gap itself when one stroke carries ≥ DEFAULT_BATCH_THRESHOLD content-creating writes (it holds the whole stroke, so it can announce on the runner before the first write), or (B) dispatch.mjs refuses a non-landing stroke over the threshold and names the per-op tools (issue-create.mjs, post-stamped.mjs, close-cards.mjs, label-write.mjs) that already pace batches. (A) closes the burst for every caller; (B) alone keeps the burst possible for anyone who splits strokes. A pin: a stroke of five issue_create actions on a stubbed platform shows ≥ 30 s between writes (injected clock), or a refusal with the prescription.」⇒ 本令取 (A) 为必做;(B) 可选,见三。
声明的契约(卡正文〈What is declared〉):write-pace 规则 ⑤(≥5 个对象的创建/改动先宣告批次,随后 N 次写的间隔升到 30 s;维护者裁「截流应该要加」);execute.mjs 头部「Every write goes through write-pace.mjs like every other fleet write」。
os-dev-report
{
"issue": 24755,
"status": "done",
"branch": "claude/issue-24755-relay-stroke-batch-gap",
"pr": "#24810",
"session": "session_01HEdP5tPhqQfzwRmKDxwn85 (subagent: the dispatching seat's id, the container's CLAUDE_CODE_REMOTE_SESSION_ID, which the relay envelope also carried)",
"premise_still_valid": true,
"summary": "Option (A) is built. execute.mjs announces ONE write-pace rule-5 batch on the runner, after the sender gate and before the first write, when a stroke carries DEFAULT_BATCH_THRESHOLD or more content-creating actions. The writes from the first through the last content-creating one then leave DEFAULT_BATCH_GAP_MS apart. dispatch.mjs holds the shared definitions. CONTENT_CREATING_OPS is derived from the op table by createsContent (today: comment, issue_create, pr_create, transfer). strokePacing gives the batch, its pacing and the budget; the budget is RELAY_JOB_TIMEOUT_MS (10 min, pinned to fleet-write.yml) less DEFAULT_CEILING_MS (5 min), so at most 11 batched writes per stroke. packRequest refuses an over-budget stroke for every sender, naming the per-op doors; the executor refuses it again with zero requests. sendFleetWrite extends its wait ceiling by the stroke's pacing and logs one line saying so. An announcement the pace log cannot record is refused, exit 10. Premise measured on 46bb5e3: the executor ran paceWrite with no batch. Reach run 39369675259 created objectui#13122 at 04:27:43Z and #13123 at 04:27:46Z, a 3 s gap.",
"tests": "Self-tests run on HEAD 4a9257f (before the change: execute 128/16, dispatch 222/20): node scripts/pm/fleet-write/execute.mjs --self-test exit 0, 138 cases / 17 batteries, new battery 'the batch gap on the runner' with 10 cases on an injected clock. The pin: 5 issue_create writes with gaps [30000,30000,30000,30000], summing to strokePacing's pacingMs. node scripts/pm/fleet-write/dispatch.mjs --self-test exit 0, 235 cases / 21 batteries, new battery 'the batch gap at the seat' with 13 cases. node scripts/pm/fleet-write/validate.mjs --self-test exit 0, 158/11 unchanged. pnpm check:pm-with-fleet exit 0, 32 cases. Reverse mutations through scripts/ablation-replace.mjs; each landed (anchor 1 to 0, blob changed) and each was restored (blob == HEAD, git diff HEAD empty). All turned red: M1, executor announceBatch call removed: execute 5/138 red, gaps [3000,3000,3000,3000], re-run on 4a9257f. M2, seat ceiling extension removed: dispatch 1/235 red, timeout at 300000. M3, packer budget refusal removed: dispatch 2/235 red. M4, executor budget refusal removed: execute 2/138 red, exit 0 with 13 requests. M5, ANNOTATED_OPS arm of createsContent removed: dispatch 2/235 red. No build or dist is involved (scripts/pm, no package). git grep for test files naming either script: no match.",
"gates": "Local union on HEAD 4a9257f (git rev-parse --short HEAD), derived by node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 40 commands, the same list as the dispatch order minus its one empty entry. Exit codes were captured before any pipe: node scripts/check-ci-filter-parity.mjs = exit 0; node scripts/check-closing-keyword-parity.mjs = exit 0; node scripts/check-closing-keyword-parity.mjs --self-test = exit 0; node scripts/check-comment-mask-corpus.mjs = exit 0; node scripts/check-declaration-mirrors.mjs = exit 0; node scripts/check-declaration-mirrors.mjs --self-test = exit 0; node scripts/check-position-name-fold-loaders.mjs = exit 0; node scripts/check-position-name-fold-loaders.mjs --self-test = exit 0; node scripts/check-scripts-symbol-anchors.mjs = exit 0; node scripts/check-scripts-symbol-anchors.mjs --self-test = exit 0; node scripts/check-self-test-wired.mjs = exit 0; node scripts/check-self-test-wired.mjs --self-test = exit 0; node scripts/check-self-test-workflow-commands.mjs = exit 0; node scripts/check-self-test-workflow-commands.mjs --self-test = exit 0; node scripts/check-whole-set-label-write.mjs = exit 0; node scripts/check-whole-set-label-write.mjs --self-test = exit 0; node scripts/pm/bare-root-worklist.mjs --self-test = exit 0; pnpm check:agent-test-spelling = exit 0; pnpm check:bash32-floor = exit 0; pnpm check:cli-command-ids = exit 0; pnpm check:cross-package-test-inputs = exit 0; pnpm check:driver-memory-census = exit 0; pnpm check:entry-guard = exit 0; pnpm check:gitlink-declared = exit 0; pnpm check:nul-bytes = exit 0; pnpm check:parse-guard = exit 0; pnpm check:pm-close-cards = exit 0; pnpm check:pm-fleet-write-dispatch = exit 0; pnpm check:pm-fleet-write-execute = exit 0; pnpm check:pm-fleet-write-validate = exit 0; pnpm check:pm-issue-create = exit 0; pnpm check:pm-issue-transfer = exit 0; pnpm check:pm-label-write = exit 0; pnpm check:pm-post-stamped = exit 0; pnpm check:pm-write-pace = exit 0; pnpm check:pnpm-filter-targets = exit 0; pnpm check:pnpm-flag-spellings = exit 0; pnpm check:refd-timer-probe = exit 0; pnpm check:watch-hint-literal = exit 0; pnpm check:pm-dispatch-gates = exit 0 (fast tier, 1913 cases, nohup + tail --pid). --ran with ':: exit N' recorded: 'Run reconciliation — 40 derived, 40 run, 0 NOT-MEASURED, 0 UNRUN' and '40 derived famil(ies) accounted for ... a DERIVED zero', exit 0. Extra: pnpm check:pm-with-fleet exit 0; control-byte grep on both files: 0 hits. PR check-runs, one read at report time on 4a9257f: Check Changeset success, Check PR Size success, Check Documentation Links success, filter success, 'The card this PR closes must claim this branch' success, 'No other open PR may claim the same issue' success. Still in_progress: Test Core 1/6 to 6/6, Type Check workspace / source gates / consumer gates / debt ledger, every Lint lane (PM dispatch-gates, docs & code guards, tooling self-tests, repo & tooling gates, CI release & engine gates, ESLint, scoped lanes), Governed Surface Queue Guard, Dogfood Regression Gate. Build Core skipped. CI convergence is the PM's to read.",
"mcp_calls": "0",
"api_writes": "2 REST writes, each one POST /repos/objectstack-ai/objectstack/dispatches through the relay, sent by the origin/main tools in the shared checkout (a43405d, which does not carry this change). 1) pr_create via scripts/pm/fleet-write/dispatch.mjs: run 39386076441, PR #24810, draft, assignee os-justin, label skip-changeset; body read back identical, 9573 of 9573 bytes. 2) this os-dev-report comment via scripts/pm/post-stamped.mjs. git push is not counted here: 4 pushes of the branch.",
"open_questions": [
{
"question": "The budget for runner pacing reserves DEFAULT_CEILING_MS (5 min) for job overhead, which caps one stroke at 11 batched writes. The measured overhead on the reach run was 11 s before the first write and 56 s in total. Keep the conservative reserve, or derive a tighter one?",
"options": [
"A: keep it. The cap is 11; bigger batches go through the per-op doors behind --announce-batch, as rest-channel already prescribes. No change.",
"B: reserve a measured overhead constant instead, which would allow about 18 batched writes, at the cost of a new hand-tuned number and less margin under the 10-min timeout."
],
"recommendation": "A. A direct dispatch.mjs stroke is for landing, and the per-op doors already pace batches. A larger cap would serve no real caller, and it would add a tuned number."
}
],
"out_of_scope_findings": [],
"deviations": [
"Route change, measured: the PM read only the job's timeout-minutes 10. sendFleetWrite's completion ceiling, DEFAULT_CEILING_MS 5 min, binds first. So dispatch.mjs now extends that ceiling by the stroke's pacing, and the budget is the job timeout less that ordinary allowance. As a result, the suggested maintainer line 'a 15-card stroke takes 7 minutes' would be false: 15 in one stroke is refused (7 min of pacing is over the 5 min budget). The 维护者速读 uses 5 cards about 2 min and 11 about 5 min instead.",
"(B) is mechanical rather than a hint only. packRequest refuses an over-budget stroke for every sender, and the executor refuses it again. A batched stroke also gets one hint line from sendFleetWrite. 'Landing' needed no definition: a landing stroke carries 0 content-creating actions.",
"The budget refusal lives in the packer and the executor, not in validate.mjs strokeErrors. validate.mjs is outside the declared file surface, and it cannot import the seat's ceiling without an import cycle. On the runner, an over-budget stroke is therefore refused by the execute step, not the validate step.",
"origin/main was not merged into the branch before the PR. The one new commit, a43405d, is a single-line .claude/skills/pm-dispatch/references/filing-gate.md edit with no overlap. Merging would have forced a re-run of the 40-gate union for no reading.",
"Commit trailers use AGENTS.md's model-free 'Co-authored-by: Claude' pair, not the harness reminder's model-named trailer. AGENTS.md's rule takes precedence and the pre-push hook refuses a model identifier.",
"Files: only execute.mjs and dispatch.mjs changed. write-pace.mjs was only read; post-stamped.mjs (sibling card's file) was not touched; ops.mjs, validate.mjs and the workflow are untouched."
],
"files_changed": [
"scripts/pm/fleet-write/execute.mjs",
"scripts/pm/fleet-write/dispatch.mjs"
]
}
Non-governed (scripts/pm/**): the seat's ACCEPT at TIER_DEFAULT is the review of record; the dev's open question (widen the runner budget from the conservative 5-minute reserve) is answered A — keep it. Readied and armed (pr_ready + automerge_enable) through the relay once CI converged green; the queue merges it, Fixes #24755 closes this card and the seat then removes pm:dispatched.
yes, by the queue at 2026-10-20T08:26Z, merge commit 638779305
638779305 is an ancestor of origin/main (ddb3360a49)
yes
queue branch gh-readonly-queue/main/pr-24810-… still present
0
remote branch claude/issue-24755-relay-stroke-batch-gap still present
0
State change: the card closed completed by the PR's Fixes; pm:dispatched removed. The relay executor on main now announces write-pace rule ⑤ for a stroke of five or more content-creating actions, and the sender refuses a stroke the job budget cannot hold.
Activity
objectstack-fleet commentedon Oct 20, 2026
Claim: PM loop round 6 ·
domain:skillsseat 1 · 2026-10-20T06:34ZSession:
session_01HEdP5tPhqQfzwRmKDxwn85Account:
os-justin(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-24755-relay-stroke-batch-gapWorktree:
objectstack-issue-24755Domain:
domain:skillsSeat:
domain:skills#1(seat post #7623)File surface:
scripts/pm/fleet-write/execute.mjs(the executor's pacing, line 460 on46bb5e3e54) and, if the stroke-side notice or refusal lands there,scripts/pm/fleet-write/dispatch.mjs; their--self-testfixtures;scripts/pm/write-pace.mjsonly read (itsannounceBatchreused, ⛔ not edited unless the fix cannot be expressed without it — then say so in the report); ⛔ notscripts/pm/post-stamped.mjs(the batch sibling #24747 holds it)Container & model:
S,mode:subagent,model: TIER_DEFAULT(dispatch-gates --tier --repo objectstack-ai/objectstack scripts/pm/fleet-write/dispatch.mjs scripts/pm/fleet-write/execute.mjsat46bb5e3e54: no path-derived mandate; a tooling card with a design judgment is built at the default tier;scripts/pm/**is not a governed face, so the seat's ACCEPT is the review of record)Clause-②: no
Responsibility:
n/a — not a defect card(filing gate ①: a declared contract — write-pace rule ⑤ — the relay path does not keep, with a measured reproduction)Thread-read: none
Ruling-ref: the maintainer's 「截流应该要加」 behind write-pace rule ⑤ (quoted in the card) and the filer's authorisation 「同意」 (QA wave session
session_01CfRDy7SuTfUqaTu2AEH2Hi); triage's grading: labels at 2026-10-20T05:53Z, no commentSerial constraints cleared:
fleet-write/dispatch.mjsandexecute.mjsare FREE (no open PR touchesscripts/pm/fleet-write/**; the 9 open PRs' file lists read at this claim); the batch sibling #24747 claimspost-stamped.mjsonly (disjoint); the lane's #24754 / PR #24771 touches README.md only; seat 2 (#19287) is vacant since 2026-10-19T16:53Z.origin/main46bb5e3e54:execute.mjs:460paces every write withpaceWrite(rule ①, 3 s) on the runner's own fresh log;write-pace.mjsexports the batch announcement (rule ⑤,DEFAULT_BATCH_THRESHOLD = 5,DEFAULT_BATCH_GAP_MS = 30_000);dispatch.mjscaps a stroke atMAX_ACTIONS(20) and announces nothing;rest-channel.md:16says a batch goes through the per-op tools and a directdispatch.mjscall is for landing only.TIER_DEFAULT, then ready + auto-merge after every check is green.objectstack-fleet commentedon Oct 20, 2026
Dispatch order — posted 2026-10-20T06:36Z by the claiming seat (
session_01HEdP5tPhqQfzwRmKDxwn85, os-justin,domain:skills#1; claim 6284963868). The same text is the dev's prompt, runmode:subagentatTIER_DEFAULT(ascripts/pm/**tooling face, not governed; the seat's ACCEPT is the review of record).你是 os-dev,本令派你恰好一张卡:#24755(p2,
domain:skills,tooling;非受管面)。6284963868。claude/issue-24755-relay-stroke-batch-gap,worktree../objectstack-issue-24755,基点origin/main(派发时46bb5e3e54)。scripts/pm/fleet-write/execute.mjs(执行器节流,第 460 行paceWrite)为主;scripts/pm/fleet-write/dispatch.mjs仅当提示/拒收落在发送侧;两者的--self-test。scripts/pm/write-pace.mjs只读复用(announceBatch/ 规则 ⑤ 常量),⛔ 不改它除非修法无法表达 —— 那就在报告写明。⛔ 不碰scripts/pm/post-stamped.mjs(批次兄弟卡 [finding] seat-side read-back judge (post-stampedclassifyReadBack/ fleet-write) answers NOT STORED on a correctly stored body while the proxy-side#Nread expansion is active, and its remedy sends the seat to re-send the expanded text #24747 的文件面)。一、裁决(不可重裁)
DEFAULT_BATCH_THRESHOLDcontent-creating writes (it holds the whole stroke, so it can announce on the runner before the first write), or (B)dispatch.mjsrefuses a non-landing stroke over the threshold and names the per-op tools (issue-create.mjs,post-stamped.mjs,close-cards.mjs,label-write.mjs) that already pace batches. (A) closes the burst for every caller; (B) alone keeps the burst possible for anyone who splits strokes. A pin: a stroke of fiveissue_createactions on a stubbed platform shows ≥ 30 s between writes (injected clock), or a refusal with the prescription.」⇒ 本令取 (A) 为必做;(B) 可选,见三。execute.mjs头部「Every write goes throughwrite-pace.mjslike every other fleet write」。issue_create以 3 s 间隔 42 s 内落完(objectui#13122…[finding]rest-server.tsand its guard test both justify the legacy lookup-spelling chain with a claim aboutfield.zod.tsthat is FALSE — the spec refuses those aliases, it does not fold them #13137,2026-10-20T04:27Z–2026-10-20T04:28Z);同日另一会话 16 个同形。分诊:p2tooling(标签 05:53Z,无评论)。⛔ 无新门禁、无新标签。二、PM 机制假设(须实测,鼓励证伪)
execute.mjs:460对每次写调paceWrite({ token, kind }, paceDeps),runner 上的 pace 日志每 run 新建,所以只走规则 ①(3 s);write-pace.mjs导出批次宣告(头部第 88 行「announceBatch/--announce-batch N」,常量DEFAULT_BATCH_THRESHOLD = 5、DEFAULT_BATCH_GAP_MS = 30_000,第 250–251 行)。执行器持有整笔动作,可在第一次写之前按「内容创建型动作数」宣告批次,之后paceWrite自然按规则 ⑤ 的 30 s 走 —— 核announceBatch的导出名与签名、它写的日志记录能否被同进程随后的paceWrite读到(第 346–356 行的 active batch 读法)。ops.mjs的 op 表机械推导(POST 创建对象:issue_create、comment、pr_create、issue_transfer…),⛔ 不凭记忆列;标签/ready/automerge/patch 不算。写进自测,⛔ 不写死在两处。.github/workflows/fleet-write.yml的 jobtimeout-minutes(派发时 PM grep:见本令末附读数)与dispatch.mjs发送侧等 run 的超时(sendFleetWrite等待上限),两者都要容得下阈值以上的一笔;容不下就在报告里给数,⛔ 不悄悄缩短间隔。落地笔(pr_ready+automerge_enable,2 个动作)不受影响。PM 派发时已读:jobtimeout-minutes: 10(fleet-write.yml:64)⇒ (A) 单独撑不住 20 个动作的一笔(≈9.5 分钟 + 开销);合理形态是 (A) 对预算内的笔生效,预算外的笔在发送侧按常量算出来拒收并点名逐 op 工具((B) 的机械版:由DEFAULT_BATCH_GAP_MS、阈值与 job 超时推出上限,⛔ 不写死数字);⛔ 不改 workflow(受管面,不在本卡)。paceWrite的paceDeps是否已支持注入now/sleep(第 460 行传入的对象);支持则自测用注入时钟断言「5 个issue_create的笔,相邻写之间 ≥ 30 s」;不支持则说明并给最小可行的注入点(在 execute.mjs 内,⛔ 不改 write-pace.mjs 的公开签名)。dispatch.mjs的MAX_ACTIONS(20)不变;「landing」笔无机械定义 —— 本席不建议拒收(会误伤折叠/多卡落地笔),建议只打印一行提示:「this stroke carries N content-creating actions ≥ threshold: the runner paces it at the batch gap (~N×30 s)」。若你能从 ops 表机械定义 landing(ready/automerge/label 类),(B) 可做成「仅对非 landing 且超阈值的笔拒收」—— 实测可定义再做,否则只做提示。origin/main上的dispatch.mjs/issue-create/post-stamped发,⛔ 不用分支版本。三、PM 建议路线(可选,实测优先)
DEFAULT_BATCH_THRESHOLD⇒ 调 write-pace 的批次宣告(kind 形如fleet-write stroke),再进循环;头部注释补一句规则 ⑤ 在 runner 上如何生效。自测:stub 平台 + 注入时钟,断言间隔 ≥ 30 s;反向突变(去掉宣告)驱红;n < 阈值的笔仍 3 s。## 维护者速读(草稿)(业务话:两个席位账号曾因内容创建突发被封;中继路径漏了这道闸;修后一笔 15 张卡要 7 分钟是设计,不是变慢)。本令门禁清单(PM 于
46bb5e3e54以dispatch-gates --commands对两文件取得,41 条)node scripts/check-ci-filter-parity.mjsnode scripts/check-closing-keyword-parity.mjsnode scripts/check-closing-keyword-parity.mjs --self-testnode scripts/check-comment-mask-corpus.mjsnode scripts/check-declaration-mirrors.mjsnode scripts/check-declaration-mirrors.mjs --self-testnode scripts/check-position-name-fold-loaders.mjsnode scripts/check-position-name-fold-loaders.mjs --self-testnode scripts/check-scripts-symbol-anchors.mjsnode scripts/check-scripts-symbol-anchors.mjs --self-testnode scripts/check-self-test-wired.mjsnode scripts/check-self-test-wired.mjs --self-testnode scripts/check-self-test-workflow-commands.mjsnode scripts/check-self-test-workflow-commands.mjs --self-testnode scripts/check-whole-set-label-write.mjsnode scripts/check-whole-set-label-write.mjs --self-testnode scripts/pm/bare-root-worklist.mjs --self-testpnpm check:agent-test-spellingpnpm check:bash32-floorpnpm check:cli-command-idspnpm check:cross-package-test-inputspnpm check:driver-memory-censuspnpm check:entry-guardpnpm check:gitlink-declaredpnpm check:nul-bytespnpm check:parse-guardpnpm check:pm-close-cardspnpm check:pm-dispatch-gatespnpm check:pm-fleet-write-dispatchpnpm check:pm-fleet-write-executepnpm check:pm-fleet-write-validatepnpm check:pm-issue-createpnpm check:pm-issue-transferpnpm check:pm-label-writepnpm check:pm-post-stampedpnpm check:pm-write-pacepnpm check:pnpm-filter-targetspnpm check:pnpm-flag-spellingspnpm check:refd-timer-probepnpm check:watch-hint-literal通用
objectstack-ai/objectstack,共享检出/home/user/objectstack(⛔ 不编辑;它游离钉在origin/main);PM 会话session_01HEdP5tPhqQfzwRmKDxwn85,席位domain:skills#1,卡 assigneeos-justin(⛔ 你不写卡 assignee)。Claim:点名的Branch:就是本令的分支,不符即停手回报。TIER_DEFAULT施工(dispatch-gates --tier在46bb5e3e54读「no path-derived mandate」)。scripts/pm/**非受管面:席位 ACCEPT 即复核记录,ACCEPT 后席位翻 ready 入队。PR 正文带## 维护者速读(草稿)(中文、业务角度,席位意见留空)。deviations写明需要什么、为何,终报照常,由席位串行安排。scripts/pm/**不进任何包的files[])⇒ 不写 changeset,skip-changeset随pr_create的labels同笔申报;若分类器拒该标签,原样记入deviations,⛔ 不自行改道。Clause-②: no;首行Fixes #N。PR 正文一次写成(os-dev 定义:正文后续改动归席位)。node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack <本单文件>取得(见本令专属段),逐条跑,退出码先落盘再看,--ran对账;对实际改动重取一次补跑。本单文件自带--self-test:改动后必跑,新增用例须能被反向突变驱红(写进报告)。scripts/pm/写工具(云容器经fleet-write中继);写预算三笔:git push、pr_create(draft,assignees: ["os-justin"],labels: ["skip-changeset"])、卡上一条os-dev-report评论;⛔ 无 MCP 写。终报mcp_calls与api_writes两数都报。⚠ 本令两张卡改的正是你自己要用的写工具:用origin/main上的工具(共享检出)发 PR 与报告,⛔ 不用你分支上改过的版本。gates(逐条命令 + 退出码)、tests(自测用例名 + 反向突变结果)、deviations、files_changed。blocked结束。每个方案必须沿四条固定评估轴分析,这是决策分析的核心原则,不是可选项:
还是投机性能力面?判据要求实测(谁在写这个键、谁在读、
示例应用与真实部署的用法),「读起来像有用」不作数。这条轴会改变结论,不是陪衬。
contract-first),临时补丁式选项要明说长期代价。
契约收紧(严格 schema、publish 时响亮拒绝)优于消费端宽容(
??回退、静默容错)——宽容恰是 AI 批量犯错被掩盖的温床;声明即强制,绝不让 AI 声明一个运行时不兑现的能力。
从紧,无拉动的声明面按 implementation-first 处置,已发布零消费能力不因沉没成本豁免。
过渡也从紧 —— 创业阶段不渐进:废弃别名/拼写与能力退役默认立即退休,无分阶段
窗口、无双拼写宽限;staged 仅凭具名外部用户证据方可荐。新增门禁默认否:门禁是只减
不增的零件,例外只有维护者点名;荐新增门禁须引其点名原话,否则荐「不加」。
基本裁决原则:spec 声明 > 实现 > 文档面;声明而未兑现是实现缺口,补实现或退役,
⛔ 不在消费端收窄;未声明键随实现,文档随之。「声明即强制」只及 spec 与元数据契约、
运行时能力,⛔ 不及 AGENTS.md 这类给 agent 读的约定 —— 那些的读者是 agent,不是门禁。
推荐意见必须基于这四条轴给出理由;四轴冲突时如实呈现权衡,
交维护者拍板。
附:PM 派发时读数
.github/workflows/fleet-write.yml的timeout-minutes行:64: timeout-minutes: 10 (空即默认 360 分钟)。Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
os-dev-report
{
"issue": 24755,
"status": "done",
"branch": "claude/issue-24755-relay-stroke-batch-gap",
"pr": "#24810",
"session": "session_01HEdP5tPhqQfzwRmKDxwn85 (subagent: the dispatching seat's id, the container's CLAUDE_CODE_REMOTE_SESSION_ID, which the relay envelope also carried)",
"premise_still_valid": true,
"summary": "Option (A) is built. execute.mjs announces ONE write-pace rule-5 batch on the runner, after the sender gate and before the first write, when a stroke carries DEFAULT_BATCH_THRESHOLD or more content-creating actions. The writes from the first through the last content-creating one then leave DEFAULT_BATCH_GAP_MS apart. dispatch.mjs holds the shared definitions. CONTENT_CREATING_OPS is derived from the op table by createsContent (today: comment, issue_create, pr_create, transfer). strokePacing gives the batch, its pacing and the budget; the budget is RELAY_JOB_TIMEOUT_MS (10 min, pinned to fleet-write.yml) less DEFAULT_CEILING_MS (5 min), so at most 11 batched writes per stroke. packRequest refuses an over-budget stroke for every sender, naming the per-op doors; the executor refuses it again with zero requests. sendFleetWrite extends its wait ceiling by the stroke's pacing and logs one line saying so. An announcement the pace log cannot record is refused, exit 10. Premise measured on 46bb5e3: the executor ran paceWrite with no batch. Reach run 39369675259 created objectui#13122 at 04:27:43Z and #13123 at 04:27:46Z, a 3 s gap.",
"tests": "Self-tests run on HEAD 4a9257f (before the change: execute 128/16, dispatch 222/20): node scripts/pm/fleet-write/execute.mjs --self-test exit 0, 138 cases / 17 batteries, new battery 'the batch gap on the runner' with 10 cases on an injected clock. The pin: 5 issue_create writes with gaps [30000,30000,30000,30000], summing to strokePacing's pacingMs. node scripts/pm/fleet-write/dispatch.mjs --self-test exit 0, 235 cases / 21 batteries, new battery 'the batch gap at the seat' with 13 cases. node scripts/pm/fleet-write/validate.mjs --self-test exit 0, 158/11 unchanged. pnpm check:pm-with-fleet exit 0, 32 cases. Reverse mutations through scripts/ablation-replace.mjs; each landed (anchor 1 to 0, blob changed) and each was restored (blob == HEAD, git diff HEAD empty). All turned red: M1, executor announceBatch call removed: execute 5/138 red, gaps [3000,3000,3000,3000], re-run on 4a9257f. M2, seat ceiling extension removed: dispatch 1/235 red, timeout at 300000. M3, packer budget refusal removed: dispatch 2/235 red. M4, executor budget refusal removed: execute 2/138 red, exit 0 with 13 requests. M5, ANNOTATED_OPS arm of createsContent removed: dispatch 2/235 red. No build or dist is involved (scripts/pm, no package). git grep for test files naming either script: no match.",
"gates": "Local union on HEAD 4a9257f (git rev-parse --short HEAD), derived by node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 40 commands, the same list as the dispatch order minus its one empty entry. Exit codes were captured before any pipe: node scripts/check-ci-filter-parity.mjs = exit 0; node scripts/check-closing-keyword-parity.mjs = exit 0; node scripts/check-closing-keyword-parity.mjs --self-test = exit 0; node scripts/check-comment-mask-corpus.mjs = exit 0; node scripts/check-declaration-mirrors.mjs = exit 0; node scripts/check-declaration-mirrors.mjs --self-test = exit 0; node scripts/check-position-name-fold-loaders.mjs = exit 0; node scripts/check-position-name-fold-loaders.mjs --self-test = exit 0; node scripts/check-scripts-symbol-anchors.mjs = exit 0; node scripts/check-scripts-symbol-anchors.mjs --self-test = exit 0; node scripts/check-self-test-wired.mjs = exit 0; node scripts/check-self-test-wired.mjs --self-test = exit 0; node scripts/check-self-test-workflow-commands.mjs = exit 0; node scripts/check-self-test-workflow-commands.mjs --self-test = exit 0; node scripts/check-whole-set-label-write.mjs = exit 0; node scripts/check-whole-set-label-write.mjs --self-test = exit 0; node scripts/pm/bare-root-worklist.mjs --self-test = exit 0; pnpm check:agent-test-spelling = exit 0; pnpm check:bash32-floor = exit 0; pnpm check:cli-command-ids = exit 0; pnpm check:cross-package-test-inputs = exit 0; pnpm check:driver-memory-census = exit 0; pnpm check:entry-guard = exit 0; pnpm check:gitlink-declared = exit 0; pnpm check:nul-bytes = exit 0; pnpm check:parse-guard = exit 0; pnpm check:pm-close-cards = exit 0; pnpm check:pm-fleet-write-dispatch = exit 0; pnpm check:pm-fleet-write-execute = exit 0; pnpm check:pm-fleet-write-validate = exit 0; pnpm check:pm-issue-create = exit 0; pnpm check:pm-issue-transfer = exit 0; pnpm check:pm-label-write = exit 0; pnpm check:pm-post-stamped = exit 0; pnpm check:pm-write-pace = exit 0; pnpm check:pnpm-filter-targets = exit 0; pnpm check:pnpm-flag-spellings = exit 0; pnpm check:refd-timer-probe = exit 0; pnpm check:watch-hint-literal = exit 0; pnpm check:pm-dispatch-gates = exit 0 (fast tier, 1913 cases, nohup + tail --pid). --ran with ':: exit N' recorded: 'Run reconciliation — 40 derived, 40 run, 0 NOT-MEASURED, 0 UNRUN' and '40 derived famil(ies) accounted for ... a DERIVED zero', exit 0. Extra: pnpm check:pm-with-fleet exit 0; control-byte grep on both files: 0 hits. PR check-runs, one read at report time on 4a9257f: Check Changeset success, Check PR Size success, Check Documentation Links success, filter success, 'The card this PR closes must claim this branch' success, 'No other open PR may claim the same issue' success. Still in_progress: Test Core 1/6 to 6/6, Type Check workspace / source gates / consumer gates / debt ledger, every Lint lane (PM dispatch-gates, docs & code guards, tooling self-tests, repo & tooling gates, CI release & engine gates, ESLint, scoped lanes), Governed Surface Queue Guard, Dogfood Regression Gate. Build Core skipped. CI convergence is the PM's to read.",
"mcp_calls": "0",
"api_writes": "2 REST writes, each one POST /repos/objectstack-ai/objectstack/dispatches through the relay, sent by the origin/main tools in the shared checkout (a43405d, which does not carry this change). 1) pr_create via scripts/pm/fleet-write/dispatch.mjs: run 39386076441, PR #24810, draft, assignee os-justin, label skip-changeset; body read back identical, 9573 of 9573 bytes. 2) this os-dev-report comment via scripts/pm/post-stamped.mjs. git push is not counted here: 4 pushes of the branch.",
"open_questions": [
{
"question": "The budget for runner pacing reserves DEFAULT_CEILING_MS (5 min) for job overhead, which caps one stroke at 11 batched writes. The measured overhead on the reach run was 11 s before the first write and 56 s in total. Keep the conservative reserve, or derive a tighter one?",
"options": [
"A: keep it. The cap is 11; bigger batches go through the per-op doors behind --announce-batch, as rest-channel already prescribes. No change.",
"B: reserve a measured overhead constant instead, which would allow about 18 batched writes, at the cost of a new hand-tuned number and less margin under the 10-min timeout."
],
"recommendation": "A. A direct dispatch.mjs stroke is for landing, and the per-op doors already pace batches. A larger cap would serve no real caller, and it would add a tuned number."
}
],
"out_of_scope_findings": [],
"deviations": [
"Route change, measured: the PM read only the job's timeout-minutes 10. sendFleetWrite's completion ceiling, DEFAULT_CEILING_MS 5 min, binds first. So dispatch.mjs now extends that ceiling by the stroke's pacing, and the budget is the job timeout less that ordinary allowance. As a result, the suggested maintainer line 'a 15-card stroke takes 7 minutes' would be false: 15 in one stroke is refused (7 min of pacing is over the 5 min budget). The 维护者速读 uses 5 cards about 2 min and 11 about 5 min instead.",
"(B) is mechanical rather than a hint only. packRequest refuses an over-budget stroke for every sender, and the executor refuses it again. A batched stroke also gets one hint line from sendFleetWrite. 'Landing' needed no definition: a landing stroke carries 0 content-creating actions.",
"The budget refusal lives in the packer and the executor, not in validate.mjs strokeErrors. validate.mjs is outside the declared file surface, and it cannot import the seat's ceiling without an import cycle. On the runner, an over-budget stroke is therefore refused by the execute step, not the validate step.",
"origin/main was not merged into the branch before the PR. The one new commit, a43405d, is a single-line .claude/skills/pm-dispatch/references/filing-gate.md edit with no overlap. Merging would have forced a re-run of the 40-gate union for no reading.",
"Commit trailers use AGENTS.md's model-free 'Co-authored-by: Claude' pair, not the harness reminder's model-named trailer. AGENTS.md's rule takes precedence and the pre-push hook refuses a model identifier.",
"Files: only execute.mjs and dispatch.mjs changed. write-pace.mjs was only read; post-stamped.mjs (sibling card's file) was not touched; ops.mjs, validate.mjs and the workflow are untouched."
],
"files_changed": [
"scripts/pm/fleet-write/execute.mjs",
"scripts/pm/fleet-write/dispatch.mjs"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
ACCEPT → PR #24810 comment 6285910091 ·
domain:skillsseat 1 ·os-justin·session_01HEdP5tPhqQfzwRmKDxwn85· 2026-10-20T07:38Zscripts/pm/**): the seat's ACCEPT atTIER_DEFAULTis the review of record; the dev's open question (widen the runner budget from the conservative 5-minute reserve) is answered A — keep it. Readied and armed (pr_ready+automerge_enable) through the relay once CI converged green; the queue merges it,Fixes #24755closes this card and the seat then removespm:dispatched.Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
Landing ·
domain:skillsseat 1 ·os-justin·session_01HEdP5tPhqQfzwRmKDxwn85· 2026-10-20T08:27Z638779305638779305is an ancestor oforigin/main(ddb3360a49)gh-readonly-queue/main/pr-24810-…still presentclaude/issue-24755-relay-stroke-batch-gapstill presentState change: the card closed
completedby the PR'sFixes;pm:dispatchedremoved. The relay executor onmainnow announces write-pace rule ⑤ for a stroke of five or more content-creating actions, and the sender refuses a stroke the job budget cannot hold.Generated by Claude Code