Repository navigation
feat(infinity-slack-bot): dedicated Slack threads per subagent, stream fixes, native stop button - #141
Closed
akainth015 wants to merge 4 commits into
Closed
feat(infinity-slack-bot): dedicated Slack threads per subagent, stream fixes, native stop button#141akainth015 wants to merge 4 commits into
akainth015 wants to merge 4 commits into
Conversation
Deploying infinity with
|
| Latest commit: |
790a43c
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://e8d2f8a6.infinity-dc7.pages.dev |
| Branch Preview URL: | https://pr-slack-subagent-threads.infinity-dc7.pages.dev |
This was referenced Oct 16, 2026
akainth015
force-pushed
the
pr/slack-subagent-threads
branch
from
October 16, 2026 17:02
f05c004 to
b95c6e5
Compare
Member
Member
Author
|
Done — the stop-button logic now ships in #143 alongside the daemon-interrupt wiring (where it actually works end-to-end, per your point about #142). This PR is now just the subagent threads + stream-correctness fixes (~380 lines lighter). #142's own diff is unchanged (interdiff-verified), and the trees from #143 up are byte-identical to before, so #144/#145/#150 diffs didn't churn. |
akainth015
force-pushed
the
pr/slack-subagent-threads
branch
2 times, most recently
from
October 19, 2026 20:31
72afe98 to
4973257
Compare
akainth015
force-pushed
the
pr/slack-subagent-threads
branch
from
October 19, 2026 23:51
4973257 to
19a0968
Compare
akainth015
force-pushed
the
pr/daemon-rewind
branch
2 times, most recently
from
October 20, 2026 00:16
1508d93 to
39a3427
Compare
akainth015
force-pushed
the
pr/slack-subagent-threads
branch
from
October 20, 2026 00:17
19a0968 to
2a2b13c
Compare
akainth015
marked this pull request as draft
October 20, 2026 02:52
akainth015
force-pushed
the
pr/daemon-rewind
branch
from
October 20, 2026 17:42
39a3427 to
a8da36b
Compare
akainth015
force-pushed
the
pr/slack-subagent-threads
branch
from
October 20, 2026 17:42
2a2b13c to
2001008
Compare
…nstead of prefixing user text
When user text interrupted an in-flight completion, the driver rewrote the
text to `<interrupt>…` before folding it into history, so the token the
model sees was baked into the stored user message. Likewise, an interrupted
tool call was settled by storing a synthetic "interrupted" tool result.
Interrupts are now first-class history entries:
* `TaggedSyntheticKind::Interrupt` is an input marker; `prepare_input`
folds it into `InfinityMessage::Interrupt` (settling a trailing
unanswered tool call like any other non-result entry). The driver
enqueues a marker ahead of the interrupting text instead of mutating it.
* The model-facing `<interrupt>` token is derived at render time by
`message::render_history` (used by `HistoryManager::get_history`): it is
prefixed onto the following user text (byte-for-byte what the model saw
before) or rendered standalone if no user text follows. Consecutive
interrupts collapse into one token.
* An unanswered tool call is settled by an `Interrupt` entry instead of a
stored synthetic "Tool call interrupted by user" result. `render_history`
renders an interrupt right after a tool call as that result, so the model
sees the same request (when text interrupts an in-flight synchronous
tool, the result alone now conveys it; no `<interrupt>` prefix follows).
History thus ends on an interrupt when the thread
was stopped, and the late real result is still dropped as stale.
* Stored user text is no longer rewritten. Rows written by older versions
(literal `<interrupt>` prefix, synthetic interrupted results) load and
render unchanged; replay keeps stripping the legacy prefix and hides
interrupt entries (as the live stream does).
Note: an older daemon cannot parse histories containing the new
`{"kind":"interrupt"}` entry (forward-incompatible on rollback).
…ation
Clients can stop a thread's in-flight work without queuing input and
without shutting the session down. Builds on interrupts being recorded as
history entries (`InfinityMessage::Interrupt`, previous commit).
* New `ClientMessage::Interrupt { thread_id }`: interrupts the thread and
its descendant subtree. Old daemons skip unparsable frames, so sending it
to one is a no-op.
* User text and interrupt markers are a batch's *decisive* inputs. One
backwards scan (`last_decisive_input`) decides whether the batch
interrupts the in-flight completion (any decisive input) and what happens
next (the last one):
| batch | in-flight work | new completion? |
|-------------------------|----------------|-----------------------|
| `[interrupt]` | cancelled | no |
| `[text, interrupt]` | cancelled | no |
| `[interrupt, text]` | cancelled | yes, answers the text |
| `[text]` | cancelled | yes (implicit marker) |
* A batch ending on an interrupt is recorded by `Thread::record_interrupt()`
without a completion: inputs are folded in order, a trailing unanswered
tool call is settled as interrupted (RAP cancellation notified, pending
choice dismissed; this settlement is factored out of `step_no_defer`), and
history is persisted. Exactly one `CompletionFinished` (→ `ResponseDone`)
is emitted per interrupted round.
* `HistoryManager::promote_harness_settlements()` persists the leading
interrupt entries that no completion will validate. History then ends on
the interrupt, so the driver idles out and a late tool result is dropped
instead of restarting generation. User text stays unvalidated and
unpersisted, like any input cancelled before model output.
* `SessionManager::interrupt_thread()` delivers interrupt markers to the
target and its active descendants; idle descendants are skipped.
* `ClientMessage::Interrupt` is handled in the client handler, with thread
refs stripped on the remote path like `UserInput`.
…ve rewind)
Adds `ClientMessage::ForkSession { thread_id, user_turn, keeps_session_alive }`:
create a NEW session whose history is the original's conversation just
before its `user_turn`-th user-text turn. The original session is only
read, so "rewinding" is an edit-and-fork that keeps it intact.
* `SessionManager::fork_session` resolves the turn (0-based over the
session's persisted user-text rows, matching the `UserInputEcho` entries
in its `Replay`; rows behind a compaction summary still count) and seeds
a new root thread with existing `ConversationStore` primitives: the rows
before the cut are copied verbatim and the latest compaction summary at
or before the cut is carried over. The fork inherits cwd and model and is
registered last, so a half-seeded fork never lists.
* Like `CreateSession`, the requesting connection is attached to the fork
and receives `Connected`; clients then send the (edited) turn as
`UserInput`. Non-sessions and out-of-range turns are reported via
`DaemonMessage::Error`. `ThreadRef`s are stripped on the
remote-forwarding path.
…ream-correctness fixes
## Subagent threads
* Subagent output (daemon events whose `thread_id` differs from the session
root) routes into a dedicated Slack thread, created lazily via an anchor
message ("🤖 Subagent …") with a permalink cross-link in the root thread.
* Replies/button clicks inside a subagent thread route back to that subagent
(`SendInput.target_thread`); per-(thread, subagent) stream state.
## Stream correctness
* Streams are finalized before any discrete block (buttons, errors,
cross-links) is posted into their thread, fixing out-of-order edits above
interposed blocks.
* Task cards are tracked per stream message (`open_tasks`) and completed on
every stop/split/recovery path so spinners can never freeze; late
completions are dropped instead of resurrecting streams.
* `SlackAction` serialization fixes for the embedded binary format
(no `skip_serializing_if`, string-adapted `serde_json::Value` fields).
Docs updated; new sim/unit tests cover routing, finalization, and task
cards. (Native stop-button support lands with the interrupt wiring in a
later commit, where it works end to end.)
akainth015
force-pushed
the
pr/slack-subagent-threads
branch
from
October 22, 2026 01:18
2001008 to
790a43c
Compare
akainth015
force-pushed
the
pr/daemon-rewind
branch
from
October 22, 2026 01:18
a8da36b to
3c24168
Compare
akainth015
force-pushed
the
pr/daemon-rewind
branch
from
October 29, 2026 22:57
3c24168 to
c426040
Compare
akainth015
added a commit
to hydro-project/infinity-slack
that referenced
this pull request
Oct 29, 2026
…ream-correctness fixes
## Subagent threads
* Subagent output (daemon events whose `thread_id` differs from the session
root) routes into a dedicated Slack thread, created lazily via an anchor
message ("🤖 Subagent …") with a permalink cross-link in the root thread.
* Replies/button clicks inside a subagent thread route back to that subagent
(`SendInput.target_thread`); per-(thread, subagent) stream state.
## Stream correctness
* Streams are finalized before any discrete block (buttons, errors,
cross-links) is posted into their thread, fixing out-of-order edits above
interposed blocks.
* Task cards are tracked per stream message (`open_tasks`) and completed on
every stop/split/recovery path so spinners can never freeze; late
completions are dropped instead of resurrecting streams.
* `SlackAction` serialization fixes for the embedded binary format
(no `skip_serializing_if`, string-adapted `serde_json::Value` fields).
Docs updated; new sim/unit tests cover routing, finalization, and task
cards.
Ported from hydro-project/infinity#141.
Member
Author
|
Moved to hydro-project/infinity-slack@2b5d03bc0ed2. The native stop button / interrupt parts were dropped. |
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.
Each subagent's output now streams into its own dedicated Slack thread (anchor message + permalink cross-link; replies/buttons inside a subagent thread route back to that subagent). Also fixes stream discontinuity around interposed blocks, frozen 'in progress' task cards, embedded-serialization bugs, and adds support for Slack's native stop button (
agent_session_stopped).Stacked on #140; part 2 of 6 (followed by #142 #143 #144 #145). See commit message for full details.