Repository navigation
Send hosted Slack replies, files, and notifications through the gateway - #466
Open
alex-clickhouse wants to merge 3 commits into
Open
alex-clickhouse wants to merge 3 commits into
alex-clickhouse wants to merge 3 commits into
Conversation
This was referenced Sep 27, 2026
alex-clickhouse
force-pushed
the
alex/hosted-channel-outbound
branch
3 times, most recently
from
September 30, 2026 15:46
e4ace30 to
ed4e2b4
Compare
alex-clickhouse
marked this pull request as ready for review
October 1, 2026 13:54
alex-clickhouse
added this pull request to stack #407
October 2, 2026 18:12
In hosted mode, Nerve now sends its outbound work to Slack as channel contract operations on the gateway streams. `OperationRunner` sends each operation on a stream that advertised it for the connection, within the stream and connection in-flight limits. The gateway gets the connection's deadline as its budget. When a result is lost, a retry-safe operation is sent again, but a side-effecting operation is reported as ambiguous and is never sent again. A rate-limited operation is sent again after the advised delay. Failure text names only the operation and the outcome; the gateway's reason code goes to the log. - Replies are split at the connection's text limit. Streamed replies use a placeholder, paced edits, and a delete. Typing is an `eyes` reaction on the message that started the turn. A text whose frame is too large is shortened before it is sent. - `send_channel_message` keeps the `slack.allow_outbound` switch. When `allow_channels` or `deny_channels` is set, the conversation ID must also pass them. The gateway decides in every case, and a refused message is a coarse refusal for the agent. - `send_file` uploads through `file_send` and a transfer: chunks after the operation, sized to the gateway's frame limit, within the connection's file limit and the gateway's memory limit. An early result stops the chunks. - Attachments of a message that starts a turn are read through `file_read` before the turn and use the Socket Mode rules for text, images, PDF, and ZIP files, with a per-file and a per-message limit. A read is sent only while it fits Nerve's stream memory limit, and its chunks must continue the transfer that its result declared. - A notification is a card with one button for each answer, in the same conversation as in Socket Mode. A press arrives as an `interaction` event, answers the notification in the conversation where it was delivered, and replaces the card without buttons. A press that answers nothing gets a short notice. An expired card is edited without buttons. - A cancelled sender lets its frame finish, so the other requests of the stream are not lost. - The gateway closes a stream on any frame that fails its checks. So Nerve checks the values that the agent chooses before it sends them: target and message IDs, reaction names, upload file names, button labels and answers, and empty text. A bad value fails one operation, not the stream. The contract adds the operation, result, and transfer records that Nerve uses. It checks that a result answers its operation kind, has a known outcome, and carries the target or transfer that Nerve reads, that only a successful file read carries a transfer, that a chunk stays inside its transfer, and that capabilities give usable limits. - `nerve doctor` reports the hosted send tool settings and the notification conversation, and says that `/nerve` commands, including `doctor` and `restart`, are not available in hosted mode. The fake gateway serves operations from scripted replies, serves attachment reads, and collects uploads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The hosted channel advertises STREAMING when `stream_mode` is `partial`, and reports `supports_message_edit=False` when a connection that can send lacks `edit` or `delete`. The stream adapter then made no placeholder and buffered the tokens, but at `done` neither branch matched, so a finished turn sent nothing. Connections that advertise only `send`, or `send` and `edit`, lost every reply. At `done`, the adapter sends the buffered response as one message whenever no placeholder exists, for any mix of capabilities. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The hosted runtime runs also while `slack.enabled` is false, and pauses intake. `is_available` and `authorize_outbound` did not read the switch, so notifications and `send_channel_message` still went to Slack while it was switched off. In Socket Mode, a switched-off Slack sends nothing. Both read the switch for each call, with intake. A turn that is already running still sends its reply. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
alex-clickhouse
force-pushed
the
alex/hosted-channel-outbound
branch
from
October 5, 2026 09:31
ed4e2b4 to
91d3f76
Compare
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.
Stacked on #465 (
alex/hosted-channel-intake); #467 is stacked on this one. The gateway side of these operations is in review in ClickHouse/nerve-private#71, in the stack ClickHouse/nerve-private#63 to ClickHouse/nerve-private#77.In hosted mode, Nerve now sends its outbound work to Slack through the gateway. Each action is a channel contract operation on a gateway stream. Self-hosted Slack is unchanged.
Why
After #465, a hosted agent can read Slack but cannot answer. This PR gives it the same outbound features as Socket Mode: replies, streaming edits, typing,
send_channel_message,send_file, attachments, and notification cards.What it does
OperationRunnersends each operation on a stream that advertised it for the connection, within the stream and connection in-flight limits. The gateway gets the connection's deadline as its budget.editordelete, there is no placeholder, and the reply is sent once when the turn ends. That path is in the sharedStreamAdapter, which sends the buffered reply whenever no placeholder exists. Typing is aneyesreaction on the message that started the turn.slack.enabledis false, the hosted channel sends no notifications and refusessend_channel_message. A turn that is already running still sends its reply.send_channel_messagekeeps theslack.allow_outboundswitch. Whenallow_channelsordeny_channelsis set, the conversation ID must also pass them. The gateway decides in every case, and the agent gets a refusal without the gateway's reason.send_fileuploads in chunks after its operation, within the connection's file limit and the gateway's memory limit.nerve doctorreports the hosted send tool settings and the notification conversation, and says that/nervecommands are not available in hosted mode.How it was tested
main: run 36739445939. Backend and frontend jobs passed.Known gaps
send_interactionis not implemented. The engine offers interactive tool prompts to the web channel only.file_sendis reported as ambiguous, even when the last chunk was not sent./nerve reply.allow_channelsordeny_channels, Nerve does not limit where the send tool may post, direct messages included. Socket Mode refuses unsolicited direct messages. The gateway decides.nerve doctorreports settings only, not live streams.🤖 Generated with Claude Code