Skip to content

feat(slack): use native chat stream replies - #48066

Closed
leoatflora wants to merge 1 commit into
NousResearch:mainfrom
leoatflora:feat/slack-native-chat-stream
Closed

feat(slack): use native chat stream replies#48066
leoatflora wants to merge 1 commit into
NousResearch:mainfrom
leoatflora:feat/slack-native-chat-stream

Conversation

@leoatflora

Copy link
Copy Markdown

Summary

  • Add Slack native chat.startStream / chat.appendStream / chat.stopStream support through the existing gateway draft-stream transport
  • Pass Slack user/team routing metadata into streaming replies so channel streams can be scoped to the requesting user
  • Stop the active native stream on final send instead of posting a duplicate final message
  • Document Slack native streaming vs edit-based streaming and update the messaging capability definition

Testing

  • /tmp/hermes-slack-stream-venv/bin/python -m py_compile gateway/platforms/slack.py gateway/run.py
  • SLACK_ALLOWED_CHANNELS= PYTHONPATH=. /tmp/hermes-slack-stream-venv/bin/python -m pytest tests/gateway/test_slack.py -q -o 'addopts=' — 200 passed

Notes

Slack native streams are used only when streaming.enabled is on, transport is auto/draft, the SDK exposes the stream methods, and the incoming Slack event provides the user/team metadata Slack requires. Otherwise the existing edit-based streaming path remains the fallback.

@alt-glitch alt-glitch added type/feature New feature or request comp/gateway Gateway runner, session dispatch, delivery platform/slack Slack app adapter P2 Medium — degraded but workaround exists labels Jun 17, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for implementing this against the existing draft-stream transport. The native Slack capability is still absent on current main, so the idea remains useful.

Problems

  • The patch targets gateway/platforms/slack.py, but current main migrated the active adapter to plugins/platforms/slack/adapter.py in 560010547; tests/gateway/test_slack.py:67-71 now imports that plugin adapter. This needs a port rather than a direct cherry-pick.
  • In the PR, gateway/platforms/slack.py:1281 removes native-stream state when chat_appendStream fails. The consumer then disables drafts and falls back to normal sends (gateway/stream_consumer.py:1302-1337), so send() no longer has the state needed to call chat.stopStream. A started native stream can remain open.

Suggested changes

  • Port the implementation/tests to plugins/platforms/slack/adapter.py.
  • Stop an already-started stream before clearing state on append failure, and add a start → append failure → stop/fallback regression test.

Automated hermes-sweeper review.

return SendResult(success=True, message_id=state.ts)
except Exception as e: # pragma: no cover - defensive logging
logger.error("[Slack] chat.*Stream append error: %s", e, exc_info=True)
self._native_streams.pop(key, None)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If chat_appendStream fails after chat_startStream succeeded, dropping this state prevents the subsequent edit-path fallback from finding and stopping the live native stream. Best-effort chat_stopStream before clearing the entry, and add a regression test for an append failure after a successful start.

@teknium1 teknium1 added sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 14, 2026
menhguin added a commit to menhguin/hermes-agent that referenced this pull request Jul 16, 2026
…ress (opt-in)

Renders tool progress on Slack as native collapsible task cards
(chat.startStream/appendStream/stopStream with task_update chunks — the
UI Slack's own AI features use) instead of markdown progress bubbles.
Opt-in via display.platforms.slack.tool_progress_native (default false):
flag off is byte-identical to current behavior on every platform.

Card features: one grouped plan-mode card per turn; per-tool entries
with friendly labels, descriptive completion titles and short output
previews; source-link chips on web tools; live numbered cards for
delegate_task children; interleaved 💭 cards carrying the model's full
reasoning (first-sentence TLDR title, complete text in the collapsible
body). Failed calls render 'complete + ✗' rather than Slack's error
status (red triangle reserved for genuine stream breakage); MCP
text-form errors are sniffed so a 403 doesn't render a checkmark.

Empirically measured Slack API behavior this encodes (docs are wrong or
silent on each): recipient_team_id AND recipient_user_id are required
for bot-token streams despite being documented optional;
task_update.title/status REPLACE per update while details APPENDS
across updates (all details sends are therefore deltas); a streamed
message has an absolute ~306s lifetime regardless of append activity —
handled by proactive rollover onto a fresh streamed message with open
tasks replayed ('⤵ continued below'); msg_too_long is per-chunk, not
cumulative (62k cumulative verified clean); leading whitespace can be
stripped at chunk joins while trailing survives.

Delivery correctness: all sends serialized through a FIFO asyncio.Lock;
tool.started drains the reasoning throttle buffer before the tool card
is scheduled (the model completes reasoning before emitting a tool
call, so the buffered tail belongs before the tool entry); reasoning
flushes cut at sentence boundaries with sub-40-char fragments carried
forward; any API failure self-disables and falls back silently to the
markdown path for the rest of the turn.

Files: gateway/slack_task_stream.py (new, self-contained);
gateway/run.py (flag-gated wiring only); gateway/display_config.py
(toggle + 5 tuning knobs via standard display-setting resolution).

Related: NousResearch#17321 NousResearch#29496 (earlier takes on this surface), NousResearch#54522 (same
feature via GatewayEventDispatcher — happy to migrate onto that seam as
a follow-up once it lands and grows reasoning/subagent event types),
NousResearch#48066 (final-answer draft streaming; complementary, different
transport). Depends on the reasoning-delivery fixes in the sibling
fix/gateway-reasoning-delivery PR.

Co-authored-by: Minh Nguyen <menhguin@users.noreply.github.com>
@teknium1 teknium1 added the area/streaming Streaming responses: gateway delivery, provider wire label Jul 19, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Closing as superseded: native chat.startStream reply streaming landed in PR #85476 via the same draft-streaming interface approach you used. Your PR targeted the pre-plugin adapter layout, so #66542's equivalent implementation on the current adapter was cherry-picked instead. Thanks — the finalize-by-stopStream design you documented is exactly what shipped.

@teknium1 teknium1 closed this Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/streaming Streaming responses: gateway delivery, provider wire comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/slack Slack app adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants