Skip to content

feat(gateway): add Telegram single-message streaming - #110571

Open
KoNit-K wants to merge 5 commits into
NousResearch:mainfrom
KoNit-K:fix/telegram-single-stream-message-110564
Open

KoNit-K wants to merge 5 commits into
NousResearch:mainfrom
KoNit-K:fix/telegram-single-stream-message-110564

Conversation

@KoNit-K

@KoNit-K KoNit-K commented Sep 14, 2026 •

Copy link
Copy Markdown

What does this PR do?

Adds an opt-in Telegram streaming mode that keeps one draft message editable for an entire agent turn, including text emitted on both sides of tool calls.

Related Issue

Closes #110564

Type of Change

  • ✅ Feature (non-breaking change that adds functionality)

Changes Made

  • gateway/display_config.py — adds and normalizes the per-platform streaming single-message option, disabled by default.
  • gateway/run_turn.py — enables the option only for Telegram turns with text tool progress disabled.
  • gateway/stream_consumer.py — retains the active edit target at a tool boundary when the option is active.
  • tests/gateway/test_stream_consumer.py — covers display resolution and a multi-segment turn that finalizes one message.

How to Test

  • HERMES_PYTHON=/Users/blockkonit./Dev/hermes/hermes-agent/.venv/bin/python scripts/run_tests.sh tests/gateway/test_stream_consumer.py tests/gateway/test_display_config.py — 97 passed.
  • Configure display.platforms.telegram.streaming_single_message: true and tool_progress: off; a turn with a tool boundary edits one Telegram message through the final response.
  • Configure any text tool-progress mode other than off; the existing segmented message ordering remains in place.
  • scripts/run_tests.sh tests/gateway/test_stream_consumer.py tests/gateway/test_display_config.py

Evidence

  • BEFORE: the focused probe was RED on origin/main before this change
  • AFTER: - [x] HERMES_PYTHON=/Users/blockkonit./Dev/hermes/hermes-agent/.venv/bin/python scripts/run_tests.sh tests/gateway/test_stream_consumer.py tests/gateway/test_display_config.py — 97 passed. — 97 passed
  • CONTROL: neighboring paths listed in How to Test still pass

Checklist

Code

  • ✅ I've read the Contributing Guide
  • ✅ My commit messages follow Conventional Commits
  • ✅ I searched for existing PRs to make sure this isn't a duplicate
  • ✅ My PR contains only changes related to this fix
  • ✅ I've run relevant tests locally (see How to Test)
  • ✅ I've added tests for my changes
  • ✅ I've tested on my platform: macOS

Documentation & Housekeeping

  • ✅ Documentation update: N/A unless noted in Changes Made
  • ✅ cli-config.yaml.example: N/A
  • ✅ CONTRIBUTING.md or AGENTS.md: N/A
  • ✅ Cross-platform impact considered
  • ✅ Tool descriptions/schemas: N/A

@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/gateway Gateway runner, session dispatch, delivery platform/telegram Telegram bot adapter area/streaming Streaming responses: gateway delivery, provider wire sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Sep 14, 2026
@Maziyang2

Copy link
Copy Markdown

Independent verification + a trace of the adjacent paths (from a macOS host that runs a live Telegram gateway — these checks are the local suites plus a mock-adapter scenario, not a live-Telegram E2E):

Suites (branch head 047f829d93):

  • tests/gateway/test_stream_consumer.py → 73 passed, 1 skipped (incl. the two new tests)
  • test_display_config.py → 23 passed · test_streaming_tts_consumer.py → 17 passed
  • test_stream_consumer_tool_progress.py → 15 passed · test_stream_consumer_fresh_final.py → 10 passed · test_gateway_streaming_nested_config.py → 9 passed · test_stream_abandon_on_turn_death.py → 4 passed · test_stream_consumer_silence.py → 3 passed · test_per_platform_streaming_defaults.py → 1 passed
  • Total: 155 passed, 1 skipped, 0 failed across 9 related suites.

Scenario check (mock adapter; interim narration → 2 tool boundaries → final text different from the accumulated prefix):

  • single_message_per_turn=False → 3 separate sends (exactly the fragmentation this fixes)
  • True → 1 send + in-place edits; final edit = the authoritative final text (interim replaced on adoption, no duplicated prefix)

Traced while reviewing:

  • The _FLUSH barrier still resolves in single-message mode: _end_segment()'s early return doesn't bypass _signal_flush(...) — that call lives in run() after the segment-break branch, so flush_pending_sync() waiters can't stall.
  • Overflow paging (_split_first_send / _seal_overflow_heads) is untouched — a turn longer than the platform limit still pages, keeping the tail editable.
  • Approval/clarify prompts remain their own messages (they have to be, for interactivity), and the preview keeps evolving afterwards — arguably nicer than the old post-approval new-segment behavior.

Two small questions/suggestions:

  1. display.platforms.* options are documented in website/docs/user-guide/messaging/index.md and listed in cli-config.yaml.example — worth a line for streaming_single_message (including that it needs streaming enabled to matter)?
  2. Is the tool_progress: off gate the intended v1 scope? The code comment says "preserving existing chronology otherwise" — just confirming it's deliberate rather than a missed case.

Nice, and much smaller than I expected when I filed the feature request (#110564) — the one-line skip at the boundary is a clean shim onto the existing machinery.

@KoNit-K

KoNit-K commented Sep 14, 2026

Copy link
Copy Markdown
Author

Thanks for the review notes — both are fair, and neither was a code bug.

1. Docs for streaming_single_message

Added a line in the same style as the neighboring display.platforms.* options, including that it only matters when streaming is enabled:

  • website/docs/user-guide/messaging/index.md
  • cli-config.yaml.example

Commit: 8991e7a827 (docs(gateway): document Telegram streaming_single_message).

2. tool_progress: off gate — yes, intended v1 scope

Deliberate, not a missed case. The existing comments/PR wiring say as much:

  • gateway/display_config.py: the flag is “active only while text tool-progress is off, preserving existing chronology otherwise.”
  • gateway/run_turn.py: “Preserve the established text↔tool-progress chronology unless progress is explicitly off.” The opt-in is also Telegram-only so other adapters keep their current segment-boundary semantics.

So with tool_progress on, we keep today’s text ↔ progress ordering. Single-message streaming is the quiet-progress / streaming-preview path for v1. Left that gate unchanged.

@Maziyang2

Copy link
Copy Markdown

@KoNit-K heads-up, no pressure: I packaged the small extensions from my review trace as a companion PR #110611 (quiet gate extended to tool_progress: log, commentary continuity, docs + tests). Same config key, so the maintainers can take either one or fold them together. Thanks again for the base implementation — the boundary-skip idea is the right one.

Maziyang2 added a commit to Maziyang2/hermes-agent that referenced this pull request Sep 16, 2026
…gate + docs (NousResearch#110564)

Keeps one editable streaming preview alive for a whole agent turn — text on
both sides of tool calls keeps updating the same message instead of sealing a
new one at every boundary. Opt-in via
display.platforms.telegram.streaming_single_message; Telegram-only; active
while text tool-progress is quiet (off/log).

Approach prototyped in NousResearch#110571 (@KoNit-K) — same config key and boundary
skip — extended with:

- quiet-progress gate also accepts tool_progress: log (no chat bubbles either;
  matches the gateway's own {"off", "log"} quiet definition);
- mid-turn commentary no longer resets the evolving preview;
- docs (messaging guide + cli-config.yaml.example) and boundary tests
  (2 tool boundaries, commentary continuity, quiet-mode gating).

Tests: scripts/run_tests.sh tests/gateway/test_stream_consumer.py
       base: 3 failed → with change: 74 passed, 1 skipped;
       adjacent stream/display suites: 98 passed, 0 failed.
@KoNit-K
KoNit-K force-pushed the fix/telegram-single-stream-message-110564 branch from 8991e7a to 97634e0 Compare September 17, 2026 13:56
Maziyang2 added a commit to Maziyang2/hermes-agent that referenced this pull request Sep 19, 2026
…gate + docs (NousResearch#110564)

Keeps one editable streaming preview alive for a whole agent turn — text on
both sides of tool calls keeps updating the same message instead of sealing a
new one at every boundary. Opt-in via
display.platforms.telegram.streaming_single_message; Telegram-only; active
while text tool-progress is quiet (off/log).

Approach prototyped in NousResearch#110571 (@KoNit-K) — same config key and boundary
skip — extended with:

- quiet-progress gate also accepts tool_progress: log (no chat bubbles either;
  matches the gateway's own {"off", "log"} quiet definition);
- mid-turn commentary no longer resets the evolving preview;
- docs (messaging guide + cli-config.yaml.example) and boundary tests
  (2 tool boundaries, commentary continuity, quiet-mode gating).

Tests: scripts/run_tests.sh tests/gateway/test_stream_consumer.py
       base: 3 failed → with change: 74 passed, 1 skipped;
       adjacent stream/display suites: 98 passed, 0 failed.
@KoNit-K

KoNit-K commented Oct 8, 2026 •

Copy link
Copy Markdown
Author

The off-only gate remains the intended scope, as explained earlier. In 967e4fd793, I corrected the configuration examples so the single-message option is paired with quiet progress; d163ed0472 also integrates the current PR base. The stream now retains its editable preview across mid-turn commentary: commentary is still sent separately, while later response text and the authoritative final continue editing the original preview. Approval and clarify boundaries are unchanged.

e6a4f8bb32 adds staged coverage for two tool boundaries: both continue editing the same Telegram preview ID before the authoritative final. Focused validation: scripts/run_tests.sh tests/gateway/test_stream_consumer.py tests/gateway/test_display_config.py tests/gateway/test_gateway_streaming_nested_config.py — 109 passed, 0 failed; git diff c712f06d..HEAD --check — passed. Live Telegram delivery was not tested. The full suite could not provide a valid result in this checkout because its development environment lacks acp and anthropic, and sandboxed tests cannot bind local ports.

This does not absorb the broader features in #110611.

Maziyang2 added a commit to Maziyang2/hermes-agent that referenced this pull request Oct 14, 2026
…gate + docs (NousResearch#110564)

Keeps one editable streaming preview alive for a whole agent turn — text on
both sides of tool calls keeps updating the same message instead of sealing a
new one at every boundary. Opt-in via
display.platforms.telegram.streaming_single_message; Telegram-only; active
while text tool-progress is quiet (off/log).

Approach prototyped in NousResearch#110571 (@KoNit-K) — same config key and boundary
skip — extended with:

- quiet-progress gate also accepts tool_progress: log (no chat bubbles either;
  matches the gateway's own {"off", "log"} quiet definition);
- mid-turn commentary no longer resets the evolving preview;
- docs (messaging guide + cli-config.yaml.example) and boundary tests
  (2 tool boundaries, commentary continuity, quiet-mode gating).

Tests: scripts/run_tests.sh tests/gateway/test_stream_consumer.py
       base: 3 failed → with change: 74 passed, 1 skipped;
       adjacent stream/display suites: 98 passed, 0 failed.

This branch has not been deployed

No deployments
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 P3 Low — cosmetic, nice to have platform/telegram Telegram bot adapter 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.

[Feature]: Option to keep one evolving message per turn on Telegram (edit in place across tool boundaries)

3 participants