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: 24ac833d04ba4161b97443586fd01b17
Branch: dev/4-group-session-pause
Base: 46327e8
At: 2026-10-30T08:46:21Z
What happens
Retiring a Bot disarms only the Sessions its own chat lives in. The Sessions it speaks in as a group member stay armed, so a retired Bot keeps waking up in those rooms and runs a turn there every day.
Live main 46327e82, core/src/host/schedules.js:494 — pauseBotSchedules(botId) builds its set as
const parts = new Set([current, ...(rt.botOf(botId)?.segments ?? []).map(segment => segment.sessionId)])— the current part plus the earlier parts of bot.segments. Missing from it: the Bot's seat in a group (room.sessions[botId]) and the earlier parts of that seat (room.segments[botId]). A row armed on either is never paused.
This is the shape #1799 was filed to remove, back under another face: #1806 (on #1799) closed it for the Bot's own chat and its parts, and the doc sentence it wrote — "Archiving also disarms the target's own chat, every part of it" (docs/subsystems/core-host.md) — describes exactly the limit that leaves this hole open.
Why the group seats are the Bot's, not someone else's
Not an inference, it is the product's own wording:
bot_schedules' description (schedules.js:604) counts "its own chat, the earlier parts of it, the group chats it speaks in" as this Bot's Sessions, andplaceOf(schedules.js:585) answers a task on a room Session withgroup chat "<name>"for that Bot.- Deletion already treats them as the Bot's:
roster.deleteBot(roster.js:568-574) records everyroom.sessions[id]androom.segments[id]part instate.retiredSessionsso the seats keep their owner (#1453). Retirement writes a subset of what deletion writes, and the group seats are the part it leaves out.
Reproduction
Reproduced twice, independently — once as a probe over a real host on an earlier base, once as the failing case on live main 46327e82, which is the case that will land with the fix:
- Fixture: a room
Launchholding BotCoder(b3) as a member,r1.sessions.b3 = 's-b3'; one daily armed ons-b3and one armed on the Bot's own chatb3; a second room's own task (r1) and another member's seat (s-b2) armed as the guard. archive_bot { bot: 'Coder' }→Archived Coder. … Paused 1 task(s) in its own chat.- The group Session is untouched: the row on
s-b3still reads{"id":"schedule-1","title":"Group standup","status":"active"},pausedis["b3"], andbot_schedulesread from insides-b3still lists it —"Group check-in" (schedule-1) — next 2030-01-01T01:00:00.000Z, active, group chat "Launch", session s-b3.
Why it actually runs: the row is still in DSH's active catalog, so it fires into s-b3. Nothing on the host refuses a turn that a scheduled task starts — hooks.js's gate covers an inbound send (request.to), resume.js's covers a restart, and a group round is drawn from roomBots, which a scheduled task does not go through.
Expected
Retiring a Bot disarms every Session it can be woken in: the current part, bot.segments, every room.sessions[botId], and every part in room.segments[botId]. The count and the answer cover all of them (Paused 2 task(s) across its chats. in the fixture above).
A reminder the group set for itself is not the Bot's: a row whose Session is room.id (placeOf's rt.roomOf branch) stays active. That is the whole room's task, not this Bot's, and disarming it because one member retired would be a worse regression than the one this fixes — the red test carries that assertion from the start (in the fixture, r1 keeps active after the archive, and so does another member's s-b2).
Scope
core/src/host/schedules.js— collect the group seats into the same setpauseBotSchedulesalready builds: for each room whereroom.sessions?.[botId]is set, that Session and eachroom.segments?.[botId]part.room.iditself is never collected.core/src/host/tools.js:412— the answer says "in its own chat", which stops being true once the count covers the seats; reworded. The two existing assertions that read it match/Paused 2 task\(s\)/(retired-bot-paths.test.js,#1709 (5d)and#1799), so none of them changes.core/test/retired-bot-paths.test.js— one case beside#1799's, red first on the base of the PR: before the fix the group row is stillactiveandpausedis["b3"]; after it the row is gone andpausedis["b3","s-b3"], with the guard rows stillactive.docs/subsystems/core-host.md— the retirement paragraph ("Archiving also disarms the target's own chat, every part of it") gains the group seats and names the one Session it does not touch.- regenerate
patches/ds-bot/0001-core-fixes.patch.
Duplicate check, run before filing: grep on live main for pauseBotSchedules / room.sessions / retiredSessions / groupOwners, plus closed-issue search for group session schedule retire, pause schedules archive_bot, retiredSessions, disarm schedules, bot_schedules group → #1799, #1709, #1445, #628. Every one of them is about the current part or the Bot's own chat; no issue covers the group seats.
Activity
Retiring a Bot disarms the group Sessions it speaks through