Repository navigation
Description
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.
Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: 08967ee2a0e54489beccfef5786e9724
Branch: dev/3-room-steering-let-go
Base: c26089f
At: 2026-10-29T06:19:51Z
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment.
The 60s steering yield (#1509) only fires in a Bot's own chat, so a member Bot stuck in a long foreground wait inside a group room keeps the room's round held for the whole turn — the one place our day actually happens is the one place the rule does not reach.
Verified code facts (read at main c26089f55d1b6a9817457dfb084d46d9e2904af6):
core/src/host/steering-interrupt.js:8-22—shouldInterruptOnHumanSend({ sourceKind, turnRunning, ownChat, waitStartedAt, now, thresholdMs })requiresownChat === true.core/src/host/steering-interrupt.js:83— the only caller passesownChat: rt.chatOwners?.has(id) === true, i.e. a group Session is excluded before the wait is even looked at.core/src/host/steering-interrupt.js:50-56—interrupt(id)drops the evidence, thenagent.cancel({ kind:'hook', reason:'ds-bot/interrupt-long-wait' }, { keepInbox:true });:58-92is thesession/eventlistener that feeds it.- Group Sessions are
rt.groupOwnersentries (core/src/host/room-participant.js:22), resolvable withrt.roomOf(id)(core/src/host/turns.js:364,:511). core/src/host/turns.js:120-133—stopRoomTurn(sessionId, exchangeId)already does what Stop does: marks the exchange instoppedRoomTurns(bounded,STOPPED_KEPT = 200at:119), cancels with{ kind:'hook', reason:'ds-bot/stopped' }only when the live turn is that exchange (:126-128), else drops that exchange's queued messages (:130-132).core/src/host/turns.js:634-645— when such a member turn ends, the round's waiter resolvesnulland, with the stopped flag set, the partial text is not posted to the room (lateRoomReplyskipped at:641).core/src/host/turns.js:511-519— a human line into a group Session is queued into the room's own inbox (queueRoomMessage), not the member's agent inbox, and the next round drains it. So letting the member turn go does not lose the line;keepInboxis not what saves it here, the room inbox is.- The exclusion is written into behaviour and docs:
core/test/steering-interrupt.test.js:72-87(// A group room the Bot speaks in is not the human's own chat.) anddocs/subsystems/core-host.md:412("a group room or an idle Session is never touched").
Reproduction. Populate rt.groupOwners for the session (real install()), start a member turn (live entry with info.role === 'room' and an exchangeId), emit tool/call at T0, advance 61s, then emit a user/message with source.kind === 'user' into that room Session. Today: zero cancels — the turn runs on to its end and the line waits behind the whole turn.
Expected. A human line in a room where a member turn is running and a foreground wait has been open for more than 60s lets that member's turn go the same way Stop does: through stopRoomTurn(sessionId, exchangeId) with the live exchange, exactly once per wait. The line stays in the room inbox and the next round answers it. The interrupted member's partial text stays out of the room (same as Stop), waits of 60s and shorter are left alone, a finished wait is not a wait, and an idle Session is still never touched. The own-chat path is unchanged byte for byte.
Decision already made (do not re-open in review). Reuse stopRoomTurn instead of a second cancel path, so a let-go room turn and a stopped room turn look identical to the round; do not add keepInbox to it, because the room inbox — not the member inbox — is what holds the line.
Acceptance.
- (a) the reproduction above yields exactly one
agent.cancelwithreason: 'ds-bot/stopped'on the live exchange, and the room inbox still holds the human line afterwards; a test then drives the next round and asserts the line is answered, not lost. - (b) at or below 60s, after the wait closed, without a live turn, and in an own chat, nothing changes.
- (c) a second line in the same wait still cancels once (the evidence is dropped first, as in the own-chat path).
- (d) a second member running in the same room is not cancelled.
- (e) the own-chat path keeps
reason: 'ds-bot/interrupt-long-wait'withkeepInbox: true; no room id ever gets that reason. - (f) the group case in
core/test/steering-interrupt.test.js:72-87is deliberately inverted: it asserts the room path yields throughstopRoomTurnand that the room never takes the own-chat path. - (g)
docs/subsystems/core-host.md:412is corrected: a group room is no longer "never touched", it yields through the room path; an idle Session still is. - (h) the red test is red on today's code and uses the real shapes (
groupOwnerspopulated,info.role === 'room'live turn with anexchangeId), not a fixture that fakeschatOwners.
Scope. core/src/host/steering-interrupt.js (the room branch and its gate), core/test/steering-interrupt.test.js, one corrected docs sentence, one changelog fragment, and the regenerated patches/ds-bot/. Touch core/src/host/turns.js only if stopRoomTurn cannot be called as it stands. Do not fold in: the trigger point (agent/inbox/spliced instead of a logged user/message, decided separately), the room busy ack (#1646), or any change to Stop semantics and the stoppedRoomTurns bound.
Written-by: FlyBot Lead
Activity
HugoXK commentedon Oct 29, 2026
Release
Written-by: FlyBot Lead
Released: 08967ee2a0e54489beccfef5786e9724
Reason: shipped as #1660, merged f583c7d
At: 2026-10-29T08:21:40Z