Repository navigation
feat(infinity-slack-bot): resume streaming threads across restarts - #150
Draft
akainth015 wants to merge 1 commit into
Draft
akainth015 wants to merge 1 commit into
akainth015 wants to merge 1 commit into
Conversation
Deploying infinity with
|
| Latest commit: |
5cf0a19
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://d4eb6281.infinity-dc7.pages.dev |
| Branch Preview URL: | https://pr-slack-resume.infinity-dc7.pages.dev |
akainth015
force-pushed
the
pr/slack-resume
branch
from
October 16, 2026 17:43
5d9ca59 to
9272147
Compare
akainth015
force-pushed
the
pr/slack-rewind-shortcut
branch
from
October 16, 2026 17:43
9b86ea8 to
4324e18
Compare
akainth015
force-pushed
the
pr/slack-resume
branch
from
October 16, 2026 19:17
9272147 to
d994e97
Compare
akainth015
force-pushed
the
pr/slack-rewind-shortcut
branch
from
October 19, 2026 20:31
4324e18 to
a3da634
Compare
akainth015
force-pushed
the
pr/slack-resume
branch
from
October 19, 2026 20:31
d994e97 to
705c2d3
Compare
akainth015
force-pushed
the
pr/slack-rewind-shortcut
branch
from
October 19, 2026 23:51
a3da634 to
50e6d6d
Compare
akainth015
force-pushed
the
pr/slack-resume
branch
from
October 19, 2026 23:51
705c2d3 to
1ea6f4b
Compare
akainth015
force-pushed
the
pr/slack-rewind-shortcut
branch
from
October 20, 2026 00:17
50e6d6d to
dffe1eb
Compare
akainth015
force-pushed
the
pr/slack-resume
branch
from
October 20, 2026 00:17
1ea6f4b to
59a91ad
Compare
akainth015
marked this pull request as draft
October 20, 2026 02:52
akainth015
force-pushed
the
pr/slack-resume
branch
from
October 20, 2026 17:42
59a91ad to
bfc70ea
Compare
akainth015
force-pushed
the
pr/slack-rewind-shortcut
branch
from
October 20, 2026 17:42
dffe1eb to
70474b2
Compare
Restarting the bot used to orphan in-flight responses: the daemon kept generating, nothing reconnected until the user's next message, and the output produced meanwhile never reached Slack (and dying mid-stream left Slack messages dangling in streaming state). ## Clean shutdown (SIGTERM / ctrl-c) * The inbound task closes the Socket Mode WebSocket with a proper close handshake (Slack sees the app offline immediately; envelopes already read were ACKed and forwarded, unread ones stay unACKed and are redelivered after the restart — a racing user message is delayed, never lost). * The outbound task drains: flushes buffered text, completes open task cards, finalizes every active stream with the disclaimer footer, and clears thread statuses. Queued-but-unprocessed actions are deliberately dropped — they were never counted as delivered, so the restart replays them. The whole drain is bounded by a 10s timeout. * The resume state is persisted to `~/.infinity/slack_resume.json` (atomic temp-file rename): per-stream delivery cursors, channel mappings, subagent thread routes, user-stop latches, and posted choice buttons. A periodic 30s snapshot bounds the damage of a crash (documented: a crash may re-post or drop a few seconds of output). ## Delivery cursors * `runtime.stream_progress` tracks, per (root thread, subagent), the cumulative text bytes and tool call/result cards *delivered to Slack*, counted by the I/O layer at delivery time (text when accepted into the flush buffer — flushed before shutdown completes — cards when posted). Suppressed (user-stopped) output is not counted and stays suppressed across restarts via the persisted latch. * `StreamStop` gains an `end_of_response` flag (set only by the ResponseDone-driven stop): a response-final stop drops the stream's cursor — cursor presence is exactly "this stream needs resuming". ## Startup resume * Each persisted stream gets its own daemon connection (`ConnectSession.target_thread`; replay — and an already-running subagent driver's live broadcast — are per thread, and a new root subscriber is not seeded into pre-existing child drivers). * The daemon's `Replay` is trimmed by the pure `resume::replay_delta`: skip the cursor-covered prefix (slicing inside replayed whole-message text chunks on char boundaries; clamped if history shrank via rewind or compaction), emit the remainder through the existing rendering arms, and append the synthetic `ResponseDone`s replay history never carries — counted to restore the exact end-state the live sequence would have produced (tool-executing: none; completion in flight: one to consume the round-continues flag; idle: one to close, plus one if the delta re-armed the flag). Replays for non-resumed streams are dropped (their content is already on screen). * Still-pending choices are re-posted only if their buttons weren't already on screen (restored `choice_messages`); pre-restart buttons keep working. Subagent output resumes into its existing dedicated thread (anchor map seeded from the persisted routes), and replies inside subagent threads keep routing to their subagent. ## Tests & docs * 10 unit tests for the resume store and `replay_delta` (trimming, mid-chunk slicing, multibyte boundaries, independent call/result cursors, synthetic-done counting, clamping, snapshot/restore round-trip). * 2 new sim tests: the ResponseDone stop is marked end-of-response, and the two-dones-close-a-replayed-tool-round contract `replay_delta` relies on. * "Restarts and Continuity" section in the Slack bot docs.
akainth015
force-pushed
the
pr/slack-rewind-shortcut
branch
from
October 22, 2026 01:18
70474b2 to
bc873ca
Compare
akainth015
force-pushed
the
pr/slack-resume
branch
from
October 22, 2026 01:18
bfc70ea to
5cf0a19
Compare
akainth015
added a commit
to hydro-project/infinity-slack
that referenced
this pull request
Oct 29, 2026
Restarting the bot used to orphan in-flight responses: the daemon kept generating, nothing reconnected until the user's next message, and the output produced meanwhile never reached Slack (and dying mid-stream left Slack messages dangling in streaming state). ## Clean shutdown (SIGTERM / ctrl-c) * The inbound task closes the Socket Mode WebSocket with a proper close handshake (Slack sees the app offline immediately; envelopes already read were ACKed and forwarded, unread ones stay unACKed and are redelivered after the restart — a racing user message is delayed, never lost). * The outbound task drains: flushes buffered text, completes open task cards, finalizes every active stream with the disclaimer footer, and clears thread statuses. Queued-but-unprocessed actions are deliberately dropped — they were never counted as delivered, so the restart replays them. The whole drain is bounded by a 10s timeout. * The resume state is persisted to `~/.infinity/slack_resume.json` (atomic temp-file rename): per-stream delivery cursors, channel mappings, subagent thread routes, and posted choice buttons. A periodic 30s snapshot bounds the damage of a crash (documented: a crash may re-post or drop a few seconds of output). ## Delivery cursors * `runtime.stream_progress` tracks, per (root thread, subagent), the cumulative text bytes and tool call/result cards *delivered to Slack*, counted by the I/O layer at delivery time (text when accepted into the flush buffer — flushed before shutdown completes — cards when posted). * `StreamStop` gains an `end_of_response` flag (set only by the ResponseDone-driven stop): a response-final stop drops the stream's cursor — cursor presence is exactly "this stream needs resuming". ## Startup resume * Each persisted stream gets its own daemon connection (`ConnectSession.target_thread`; replay — and an already-running subagent driver's live broadcast — are per thread, and a new root subscriber is not seeded into pre-existing child drivers). * The daemon's `Replay` is trimmed by the pure `resume::replay_delta`: skip the cursor-covered prefix (slicing inside replayed whole-message text chunks on char boundaries; clamped if history shrank, e.g. via compaction), emit the remainder through the existing rendering arms, and append the synthetic `ResponseDone`s replay history never carries — counted to restore the exact end-state the live sequence would have produced (tool-executing: none; completion in flight: one to consume the round-continues flag; idle: one to close, plus one if the delta re-armed the flag). Replays for non-resumed streams are dropped (their content is already on screen). * Still-pending choices are re-posted only if their buttons weren't already on screen (restored `choice_messages`); pre-restart buttons keep working. Subagent output resumes into its existing dedicated thread (anchor map seeded from the persisted routes), and replies inside subagent threads keep routing to their subagent. ## Tests & docs * 10 unit tests for the resume store and `replay_delta` (trimming, mid-chunk slicing, multibyte boundaries, independent call/result cursors, synthetic-done counting, clamping, snapshot/restore round-trip). * 2 new sim tests: the ResponseDone stop is marked end-of-response, and the two-dones-close-a-replayed-tool-round contract `replay_delta` relies on. * "Restarts and Continuity" section in the Slack bot docs. Ported from hydro-project/infinity#150.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Clean shutdown (SIGTERM/SIGINT) now: gracefully closes the Socket Mode WebSocket (close handshake; unread envelopes stay un-ACKed so Slack redelivers them — a user message racing shutdown is delayed, never lost), drains buffers and finalizes all active streams (no dangling task cards or streaming messages), and persists resume state (
~/.infinity/slack_resume.json, atomic write): per-(thread, subagent) delivery cursors, channel mappings, subagent anchor routes, halt latches, and the choice-button registry.On startup the bot reconnects to each previously-streaming session (per-thread
ConnectSession.target_thread), trims the daemon'sReplaywith the persisted cursors (UTF-8-safe slicing, clamped if history shrank via rewind/compaction), and posts exactly the missed output — resumed subagent output continues in its existing dedicated thread instead of forking a duplicate, pre-restart choice buttons keep working, and user-stop latches survive. A 30s dirty-checked snapshot bounds loss on non-clean crashes (documented).63 tests green (10 new resume-store/delta unit tests, 2 new sim tests).