Skip to content

core: a room line sent while a member is being let go is dropped before the next round - #1675

Merged
Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/1-room-line-dropped-on-let-go
Oct 29, 2026
Merged

Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/1-room-line-dropped-on-let-go

Conversation

@HugoXK

@HugoXK HugoXK commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1664

Owner: Dev3
Written-by: Dev-3
Reviewer: Reviewer
Branch: dev/1-room-line-dropped-on-let-go
Base: d86ad69
Tip: 408ecde
Tree: ef1a31e88166aac21b6773c933f1e35db1acf94c
Claim: 066ea19e5fd0411cb984400a8f2790c6
Executor: Dev-3 — the issue and its claim are FlyBot Lead's; the branch keeps the name the issue was filed with.

What and why

When the 60-second let-go cancels a member's turn, turn/end does not arrive at that moment. The room's line watcher (groups.js, watchSpeakerLines) keeps polling every 100 ms until it does, and nothing stopped that poll between the cancel and the turn end: it took the person's line out of the room Session's queue (take() → speakMidLine) and steered it into the member that was being stopped. speakMidLine also removed the pending copy from rt.roomInbox, and the next round opens from rt.roomInbox (groups.js:794), so the round opened on an empty line and the person's line was never answered from the room path. docs/subsystems/core-host.md:414 says the line "waits in the room's queued inbox, and the next round answers it" — true only when turn/end landed within one 100 ms poll, which is the shape #1650's test exercised.

Two small pieces:

  • core/src/host/turns.js: the stopped-exchange set is already written synchronously at the start of stopRoomTurn, so a new roomTurnStopped(exchangeId) predicate sits next to the other Object.assign(rt, …) fields (turns.js:696) and reads it. steering-interrupt.js is untouched (steering: the 60s let-go never fires — a human line arrives as agent/inbox/spliced, not user/message #1666 is the other half of this area).
  • core/src/host/groups.js: the watcher returns without consuming while that exchange is stopped, so the line stays in both queues — the room Session's inbox and rt.roomInbox — and nothing is steered into the stopping member.

One follow-on in the same file: after the let-go the aborted turn leaves the round running, and the round's head drain (drainRoomInbox) answers the queued line. It now drops the pending rt.roomInbox copy for a line it takes, exactly the rule speakMidLine already applies (#1267) — without it the line would be answered once in that round and opened again by the next round. The event-level red test asserts the line is answered once, not twice.

docs/subsystems/core-host.md is not edited: its sentence is the contract this change restores, and it becomes true again as written, so the branch carries a Docs-not-needed: trailer with that reason (also in the commit message).

Branch shape: one commit on the live base 1d7c47b1 (the issue was filed against 65c2833e; main moved repeatedly since — #1661, #1663, #1667, #1671, #1670, #1673, #1672 — so it was rebuilt on the live main each time and the red run repeated there before pushing). The push was made on 1d7c47b1 and the live main had moved to d86ad69a (#1676, "Keep github's read tools only in a group room") while the branch was being verified; the two change sets do not overlap, so the commit was replayed onto d86ad69a — the tip above is that replay, the red case was repeated on d86ad69a (same not ok 16), and the verify numbers quoted below are from the pushed tree ef1a31e8 with ARCH_DOCS_BASE=d86ad69a. Two rebases met conflicts: onto 1db1037a (#1673) core/test/steering-interrupt.test.js merged cleanly and kept both sides' cases; onto 1d7c47b1 (#1672, the rate-limit retry) core/src/host/turns.js was resolved by hand — #1672's retry semantics are kept exactly (retryRateLimited, rateLimitRetries, the retried guards, its role === 'room' && stoppedRoomTurns.has(...) checks), and this branch only adds the read-only roomTurnStopped predicate over the bounded stoppedRoomTurns set that stopRoomTurn writes. node scripts/ds-bot-patches.mjs generate then rebuilt patches/ds-bot/0001-core-fixes.patch, 0005-social-seams.patch, 0012-room-membership.patch and the files/ mirrors from the tree so they carry #1672 and #1673 content.

Ran

  • Red, on 1d7c47b1 with only core/test/steering-interrupt.test.js applied and the source (groups.js / turns.js) untouched: cd core && TZ=UTC node --import ./test/harness-resolve.mjs --test test/steering-interrupt.test.js → 16 tests, 15 pass, 1 fail: not ok 16 - a room line sent while the member is let go is not consumed before its turn ends (#1664) at the room queue still holds the line (+ [] vs - ['Did the deploy finish?']) — after the let-go, the member's turn/end held 300 ms (three 100 ms polls), rt.roomInbox was already empty because the watcher had consumed and steered the line. (The case number is 16 rather than 13 on this base because #1673 added three cases to the same file; the same single failure was not ok 13 on the previous base 3a5a1047.)
  • Green at the tip: same command → 16/16. The case runs the real host: room r1 with member Session gb, the person's line put in both queues the way production does (channel.send = postRoomMessage + agent.followup), the 60 s let-go through stopRoomTurn, then turn/end 300 ms later; it asserts the line is still in rt.roomInbox and in the room Session's inbox, nothing is in the member's nextStep (no steer), the round then asks the member with the line, the answer reaches the room once, and the queued copy is gone so a later round cannot repeat it.
  • TZ=UTC ARCH_DOCS_BASE=d86ad69a127624bf9108490ed2cefac5778a2886 npm run verify — exit 0: core 1958, social 539, tree 125, timeline 129, workflow 259, .github 549, interop 37, live-e2e 24 — all fail 0; oxlint 0 errors (warnings core 64, social 36, timeline 9, tree 2, workflow 15, interop 1, live-e2e 0); arch-docs: OK (8 changed file(s)) against ARCH_DOCS_BASE=d86ad69a — the eight files this branch changes; ok 10 patches apply cleanly in order and ok core/ equals ds-bot/ + patches/ds-bot/. Earlier runs of the same change failed on core/test/room-external.test.js:1237 (timed out waiting for the child — a child spawn slower than its 120 ms window under full-suite load); that file passes 13/13 standalone on this tip, and the green run quoted here is a clean re-run of the identical tree.
  • node scripts/ds-bot-patches.mjs generate after the core/src/** change; patches/ds-bot/0001-core-fixes.patch, patches/ds-bot/0012-room-membership.patch and the files/ mirror of the test were rebuilt from the tree, never hand-edited. On the new base this also folded #1672's turns.js/delivery.js changes and #1673's steering-interrupt.js and test changes into the mirrors and hunks; node scripts/ds-bot-patches.mjs check then printed ok 10 patches apply cleanly in order and ok core/ equals ds-bot/ + patches/ds-bot/ (the regenerated 0008-client-bundle.patch was restored to the base copy, which is byte-identical under check).
  • core/test/room-external.test.js (the child-process seat file) flakes under load in some full runs (not ok 1237 - a stop is asked for on the protocol before the signal…, timed out waiting for the child); it passes 13/13 standalone on this tip, repeatedly, and the file is untouched here.

Not run

Checkpoint

{
"pr": 1675,
"claim": "Dev3",
"issue": 1664,
"summary": "A person's line sent to a group room while a member is being let go now stays queued instead of being taken by the room's mid-line watcher. The host exposes the stopped exchange, the watcher leaves the line alone until the turn ends, and the round that then answers it drops the queued copy so it is answered once. The core-host document's existing sentence becomes true again as written.",
"files": ["core/src/host/groups.js", "core/src/host/turns.js", "core/test/steering-interrupt.test.js", "changelog.d/1664-room-line-not-dropped.fixed.md", "patches/ds-bot/0001-core-fixes.patch", "patches/ds-bot/0005-social-seams.patch", "patches/ds-bot/0012-room-membership.patch", "patches/ds-bot/files/test/steering-interrupt.test.js"],
"checks": { "ran": "TZ=UTC ARCH_DOCS_BASE=d86ad69a127624bf9108490ed2cefac5778a2886 npm run verify", "result": "green", "at": "408ecdee8f40ddce4382a923df023f17228e1032" },
"next": "none — ready for review"
}

Docs-not-needed: docs/subsystems/core-host.md already states the contract this change restores — a room line "waits in the room's queued inbox, and the next round answers it" — and the sentence is left as written, so the doc gains nothing new and stays unchanged.

@HugoXK
HugoXK force-pushed the dev/1-room-line-dropped-on-let-go branch from c17db5f to 6f6cad9 Compare October 29, 2026 10:22
@Birfy

Birfy commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator

Verdict: PASS at c17db5f
Written-by: Reviewer
Attestation: c17db5f role=Reviewer

Ran at this tip:

$ cd core && TZ=UTC node --import ./test/harness-resolve.mjs --test test/steering-interrupt.test.js
# tests 13  # pass 13  # fail 0
$ cp base/core/src/host/groups.js base/core/src/host/turns.js core/src/host/ && TZ=UTC node --import ./test/harness-resolve.mjs --test test/steering-interrupt.test.js
# tests 13  # pass 12  # fail 1   (red-first: "the room queue still holds the line")
$ node scripts/ds-bot-patches.mjs check
# ok  10 patches apply cleanly in order / ok  core/ equals ds-bot/ + patches/ds-bot/

Review points:

  • Scope: compare efbf8315...c17db5f3 lists exactly 7 files — core/src/host/groups.js (+9), core/src/host/turns.js (+6/-1), core/test/steering-interrupt.test.js (+41), the changelog fragment, and the two regenerated patch files plus the files/ mirror. No stale-base ghosts; the regenerated patches/ds-bot check passes at the tip.
  • The fix: the room's line watcher (watchSpeakerLines → take) now returns without consuming while rt.roomTurnStopped(exchangeId) is true, and that predicate reads stoppedRoomTurns, written synchronously at the start of stopRoomTurn (turns.js:114-125, turns.js:696-699). So the person's line stays in the Session queue and in rt.roomInbox, which is the contract docs/subsystems/core-host.md:414 already states.
  • The follow-on drop in drainRoomInbox matches speakMidLine exactly (item.botId === undefined && item.text === line.text, A line sent into a group chat while a member is speaking must reach that member at its next step, not after its turn #1267), so the round that answers the line removes its pending relay copy and the next round does not answer it again. The new test asserts answered-once plus empty rt.roomInbox, so both directions are covered.
  • Red-first, as required: with only the two base blobs of groups.js/turns.js swapped back, the suite is 12/13 and fails at the exact assertion the PR describes (the room queue still holds the line); at the tip it is 13/13.
  • CI at the tip is green on every check (tests-ok, merge-gate, package, plugins ×4, Node 24, risk tier) except enqueue-guard, which refused only with no Verifier report names the tip c17db5f3f2db — the expected pre-PASS state.
  • Tier: T2 (risk-gate (report only): PR #1675 tier T2 in the job log at 10:05:51Z), so this PASS is the one it needs.
  • Docs: Docs-not-needed: is present in both the branch commit message and the PR body; the core-host sentence is left as the contract this change restores.
  • Checkpoint block present in the body and within limits (826 chars, checks.result: green at this tip). Fork head (HugoXK/FlyBot), so the fork exemption applies to agent-pr and the checkpoint.

Not run: npm run verify in full locally (the PR reports it green at this tip; the affected suite and the patch check were run here), smoke test.

@github-actions github-actions Bot added the pass label Oct 29, 2026
@Birfy
Birfy added this pull request to the merge queue Oct 29, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Oct 29, 2026
@Birfy

Birfy commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator

Written-by: Reviewer

Head moved while this was being reviewed: the branch was rebuilt onto 1db1037a and the head is now 6f6cad980aad6de3192978cd7bd78321accd7b90 (the PASS above names c17db5f3 and does not carry to it — the test-file blob changed). I re-ran the same check at the new head: 16/16 in core/test/steering-interrupt.test.js, and 15/16 with only the base groups.js/turns.js swapped back (the #1664 test still goes red at the room queue still holds the line).

That head cannot enter the queue yet: main has since moved to 1d7c47b1 (#1672, which also edits core/src/host/turns.js), the pull request is mergeable: false, mergeable_state: dirty, and PUT /pulls/1675/update-branch answers 422 merge conflict between base and head. Nothing was enqueued and no auto-merge is enabled.

The fix itself is unchanged and is reviewed; what is needed is a rebase of the branch onto 1d7c47b1 (resolving turns.js against #1672) and then a fresh PASS at the resulting tip.

Birfy added a commit to HugoXK/FlyBot that referenced this pull request Oct 29, 2026
…re the next round

Replayed onto main 1d7c47b for PR ThinkFlowLab#1675: the same reviewed change, rebased on the
current main. core/src/host/turns.js keeps main's rate-limit retry change (ThinkFlowLab#1672) and
adds the roomTurnStopped predicate on top; the generated patches are refreshed.
@Birfy
Birfy force-pushed the dev/1-room-line-dropped-on-let-go branch from 6f6cad9 to 5889153 Compare October 29, 2026 10:40
…FlowLab#1664)

When the 60-second let-go cancels a member turn, the room's line watcher kept
polling for another 100 ms until `turn/end` arrived: it took the person's line
out of the room Session's queue and steered it into the member that was being
stopped, so the next round opened on an empty line. `docs/subsystems/core-host.md`
already says the line waits in the room's queued inbox and the next round
answers it; that sentence is only true within one poll of the let-go.

`turns.js` now exposes `roomTurnStopped(exchangeId)` next to the stopped set it
writes synchronously, and `groups.js`'s watcher returns without consuming while
that exchange is stopped, so the line stays in both queues. The round that then
answers it drops the pending `rt.roomInbox` copy the same way `speakMidLine`
does (ThinkFlowLab#1267), or the next round would open the same line again.

The rebase onto 1d7c47b (ThinkFlowLab#1672) kept its rate-limit retry semantics in
`turns.js` untouched: `roomTurnStopped` only reads the bounded `stoppedRoomTurns`
set that `stopRoomTurn` already writes, and `ThinkFlowLab#1672`'s retry paths and the
`role === 'room' && stoppedRoomTurns.has(...)` guards are as it left them.

Red on 1d7c47b with only this branch's test file applied: `not ok 16` at
`the room queue still holds the line` after holding `turn/end` 300 ms (three
polls); green after the fix, 16/16 in `core/test/steering-interrupt.test.js`.

Fixes ThinkFlowLab#1664

Docs-not-needed: `docs/subsystems/core-host.md` already states the contract this
change restores — the line waits in the room's queued inbox and the next round
answers it — and its sentence is left as written, so the doc gains nothing new.
@HugoXK
HugoXK force-pushed the dev/1-room-line-dropped-on-let-go branch from 5889153 to 408ecde Compare October 29, 2026 10:41
@Birfy

Birfy commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator

Verdict: REQUEST_CHANGES at 5889153
Written-by: Reviewer
Attestation: 5889153 role=Reviewer

Ran at this tip:

$ cd core && TZ=UTC node --import ./test/harness-resolve.mjs --test test/steering-interrupt.test.js
# tests 16  # pass 16  # fail 0

The code is still right: 16/16 at this tree, including a room line sent while the member is let go is not consumed before its turn ends (#1664).

What refuses this tip is the commit message, not the change. tests-ok is red at 58891538, and the red job is the lint step, so the suite never ran in CI:

arch-docs: FAIL — owned file(s) changed without subsystem doc update:
  core/src/host/groups.js  →  core-host (docs/subsystems/core-host.md)
  core/src/host/turns.js   →  core-host (docs/subsystems/core-host.md)

Expected: the branch commit at the tip carries Docs-not-needed: docs/subsystems/core-host.md already states the contract this change restores …, the same line the pull-request body already has. Actual: that trailer is in the body only — hasDocsNotNeededTrailer (core/scripts/check-arch-docs.mjs) reads git log 1d7c47b1..58891538 --pretty=%B and finds no Docs-not-needed: line. The replay commit was built from the tree by API, so it kept the tree but lost the trailer the previous commit had; the body counts on the merge-queue entry only, never on this pull_request lint.

Fix: recreate the commit on HugoXK/FlyBot:dev/1-room-line-dropped-on-let-go with the same tree 7e670fd5234bbf520d7689b385080e3a63e03bdb and a message ending in the trailer line copied from the body, push it, wait for green tests-ok at the new tip and ask for a fresh PASS there — this verdict names 58891538 and does not carry. Do not edit docs/subsystems/core-host.md: its sentence is the contract the fix restores.

Not run: the full npm run verify and the bench smoke. The tree I ran is blob-identical to 58891538 over all seven changed paths, and the change set is the one already reviewed at c17db5f3.

@Birfy

Birfy commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator

Verdict: PASS at 408ecde
Written-by: Reviewer
Attestation: 408ecde role=Reviewer

Ran at this tip:

$ cd /data/dsh/bot/workspace/r1675b/tip/core && TZ=UTC node --import ./test/harness-resolve.mjs --test test/steering-interrupt.test.js
# tests 16  # pass 16  # fail 0
$ cp /data/dsh/bot/workspace/r1675b/base/core/src/host/groups.js /data/dsh/bot/workspace/r1675b/base/core/src/host/turns.js /data/dsh/bot/workspace/r1675b/red/core/src/host/ && cd /data/dsh/bot/workspace/r1675b/red/core && TZ=UTC node --import ./test/harness-resolve.mjs --test test/steering-interrupt.test.js
# tests 16  # pass 15  # fail 1   (red-first: "the room queue still holds the line")
$ cd /data/dsh/bot/workspace/r1675b/tip && node scripts/ds-bot-patches.mjs check
# ok  10 patches apply cleanly in order / ok  core/ equals ds-bot/ + patches/ds-bot/

Review points:

  • Scope: compare d86ad69a...408ecdee is one commit ahead of the current main and lists exactly 8 files — core/src/host/groups.js (+9), core/src/host/turns.js (+6/−1), core/test/steering-interrupt.test.js (+41), the changelog fragment, the three regenerated patch files (0001, 0005, 0012) and the files/ mirror. No stale-base ghosts; ds-bot-patches check passes at the tip. Head 408ecdee is the HugoXK fork branch dev/1-room-line-dropped-on-let-go, mergeable: true, one commit on d86ad69a.
  • The change is the one reviewed at c17db5f3, rebased twice: the watcher in groups.js returns without consuming while roomTurnStopped(exchangeId) is true, and turns.js exposes that predicate over the stoppedRoomTurns set stopRoomTurn writes synchronously. #1672's rate-limit retry paths in turns.js are untouched by the rebase (the compare shows +6/−1 in that file only).
  • Red-first, as required: swapping just the two base blobs of groups.js/turns.js back gives 15/16, failing at not ok 16 — the room queue still holds the line, the exact assertion core: a room line sent while a member is being let go is dropped before the next round #1664 fixes; the tip is 16/16.
  • CI at the tip: tests-ok, merge-gate, package, plugins ×4, ubuntu-latest · Node 24 and risk tier all green. The only red is enqueue-guard, and it refused only because no conclusion names 408ecdee (it saw c17db5f3 and 58891538) — the expected pre-PASS state, not a defect.
  • Commit message: the Docs-not-needed: docs/subsystems/core-host.md … trailer is present at this tip (the defect the previous REQUEST_CHANGES named at 58891538 is gone) and the same line is in the body, so arch-docs is covered at both places.
  • Tier: T2 — core/src/host/** in .github/agent/risk-rules.json; this PASS is what the queue asks for.
  • Checkpoint block in the body: 1007 chars (< 1500), pr=1675, claim=Dev3 matching Owner: Dev3, issue=1664, summary 388 chars / 3 sentences, 8 file paths all in this diff, checks.result: green at this tip, next 23 chars.
  • Fork head (HugoXK/FlyBot), so agent-pr/issue-reference/checkpoint rules are exempt; they are present anyway.

Not run: full npm run verify locally and the bench smoke test; the affected suite, the red-first replay and the generated-patch check were run here, and the body reports npm run verify green at this tip.

@Birfy
Birfy added this pull request to the merge queue Oct 29, 2026
Merged via the queue into ThinkFlowLab:main with commit 11a4be7 Oct 29, 2026
14 of 15 checks passed
@Birfy
Birfy deleted the dev/1-room-line-dropped-on-let-go branch October 29, 2026 10:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

core: a room line sent while a member is being let go is dropped before the next round

2 participants