Skip to content

fix(channel): filter hermes progress messages, deliver streamed final updates#70

Merged
madtank merged 1 commit intomainfrom
orion/channel-streaming-reply-filter
Apr 18, 2026
Merged

fix(channel): filter hermes progress messages, deliver streamed final updates#70
madtank merged 1 commit intomainfrom
orion/channel-streaming-reply-filter

Conversation

@madtank
Copy link
Copy Markdown
Member

@madtank madtank commented Apr 18, 2026

Summary

Restores two ax-channel bridge behaviors that regressed when the TypeScript bridge was ported to Python axctl channel.

Root cause

  1. The Python bridge subscribed only to message and mention SSE events; message_updated was dropped. Hermes-runtime sentinels seed a placeholder message ("Working…") on reply start and overwrite the same message_id in place as the real reply streams in. Without message_updated, the bridge saw the placeholder creation and nothing else.
  2. The Python bridge had no filter for progress/chunk payloads, so every "Working…" / "Received" / streaming placeholder woke the Claude Code session.

Net effect: sessions got constant chatter from sentinels but never saw the actual replies without manually fetching via GET /api/v1/messages/{id}.

Fix

  • Subscribe to message_updated in addition to message / mention.
  • Allow message_updated to bypass seen-dedup only when the id has NOT been delivered yet (so final streaming updates supersede the placeholder but can't re-wake after delivery).
  • Skip payloads where metadata.streaming_reply.final is explicitly false (structured runtime signal).
  • Defensive regex fallback anchored with \Z: drop payloads whose first line (after stripping any leading @mention) is entirely progress-only — Working… / Received / Thinking... / Processing… with only trailing whitespace / dots / ellipses, or No response after <N>[smh] .... Prompts that merely start with those words like "Working-state cleanup proposal" are NOT dropped.
  • Skipped payloads are NOT added to seen_ids so the subsequent final update for the same id still delivers.
  • Also fixes the leading-mention strip regex to handle hyphenated agent handles.

Tests

Seven targeted tests in tests/test_channel.py:

  • test_channel_skips_streaming_reply_non_final — structured metadata.streaming_reply.final=false filter.
  • test_channel_skips_working_progress_message — defensive regex catches Working… / Received / Thinking... / No response after 5m ....
  • test_channel_delivers_prompts_that_merely_start_with_progress_words"Working-state cleanup proposal" still lands (regression for over-broad regex caught in review).
  • test_channel_delivers_processing_webhook_errors_prompt
  • test_channel_delivers_thinking_through_issue_prompt
  • test_channel_delivers_message_updated_final — placeholder → final update round-trip delivers the real content.
  • test_channel_skips_message_updated_for_already_delivered — duplicate finals don't re-wake.

All 20 channel tests pass. ruff check and ruff format --check clean. Full test suite: 224 passed.

Test plan

  • CI green
  • Install from branch, restart a Claude Code session, verify sentinel replies deliver without progress noise
  • Regression check: a normal non-streaming message still delivers immediately

@madtank
Copy link
Copy Markdown
Member Author

madtank commented Apr 18, 2026

Code review note: one issue before merge. The PR did come from Orion's branch (orion/channel-streaming-reply-filter); the GitHub PR author shows as madtank, but the main commit is authored by orion and co-authored by Claude.

Blocking issue: ax_cli/commands/channel.py around the new progress-message regex skips any first line that merely starts with Working, Received, Thinking, or Processing. Because the pattern is not anchored to the end of the progress marker, a legitimate prompt like @anvil Working-state cleanup proposal, @anvil Processing webhook errors, or @anvil Thinking through this API issue would be silently dropped and not delivered to the channel.

Please tighten the fallback regex so it only suppresses known progress-only lines, e.g. exact/punctuation-only Working..., Received, Thinking..., Processing..., and No response after ..., while allowing ordinary user/agent messages that start with those words. Add a regression test for a real prompt such as @anvil Working-state cleanup proposal being delivered.

Everything else looks sound: subscribing to message_updated, not marking skipped progress chunks as seen, and delivering the final streamed update are the right fixes. CI is green after the format cleanup.

madtank pushed a commit that referenced this pull request Apr 18, 2026
Mirrors CI's three checks (ruff check, ruff format --check, pytest) so
lint-only failures like PR #70's channel.py format miss get caught
locally before push. Pytest runs at pre-push stage to keep everyday
commits fast.

Setup now:
  pip install pre-commit
  pre-commit install --install-hooks   # installs both pre-commit and pre-push

Verified: ruff check, ruff format, pytest tests/ all pass via
`pre-commit run --all-files` and `... --hook-stage pre-push`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…unks

Restores two behaviors from the original TypeScript ax-channel bridge
that regressed when it was ported to Python `axctl channel`:

1. Subscribe to `message_updated` SSE events in addition to `message` /
   `mention`. Hermes-runtime sentinels seed a placeholder message
   ("Working…") on reply start and overwrite the same message_id
   in place as the final reply streams in. Without `message_updated`
   the bridge saw only the placeholder creation and never the real
   text, so sessions had to manually fetch replies via API.

2. Filter progress/chunk payloads so every "Working…" / "Received" /
   streaming placeholder stops waking the Claude Code session. Two
   signals used in order:

   - Structured: `metadata.streaming_reply.final` is explicitly `false`
     (runtime tells us this is a placeholder it will overwrite).
   - Defensive: first-line content matches known progress patterns,
     anchored with `\Z` so the entire line must be progress-only —
     "Working…" is dropped but "Working-state cleanup proposal" is
     delivered normally. Covers: Working, Received, Thinking,
     Processing (with only trailing whitespace / dots / ellipses) and
     "No response after <N>[smh] ..." (runtime-emitted timeout form).

Seen-dedup semantics:
- New messages: skip if id already delivered (unchanged).
- message_updated: skip if id already delivered, otherwise deliver the
  final payload (so placeholder -> final round-trips work).
- Skipped progress chunks are NOT added to seen_ids, so the subsequent
  final update for the same id still delivers.

Also fixes the leading-mention strip regex to handle hyphens in agent
handles (e.g. `peer-agent`); the previous `^@\w+` stopped at the first
hyphen and left stray text that defeated the progress filter.

Tests (tests/test_channel.py):
- test_channel_skips_streaming_reply_non_final — structured filter
- test_channel_skips_working_progress_message — Working / Received /
  Thinking / No-response-after-5m all dropped
- test_channel_delivers_prompts_that_merely_start_with_progress_words —
  "Working-state cleanup proposal" lands (regression for over-broad
  regex caught in review)
- test_channel_delivers_processing_webhook_errors_prompt
- test_channel_delivers_thinking_through_issue_prompt
- test_channel_delivers_message_updated_final — placeholder -> final
  round-trip
- test_channel_skips_message_updated_for_already_delivered — dup final
  updates don't re-wake

Verified: pytest tests/test_channel.py -> 20 passed.
ruff check + ruff format --check clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@madtank madtank force-pushed the orion/channel-streaming-reply-filter branch from 7961b23 to 542c040 Compare April 18, 2026 05:36
@madtank madtank merged commit 1cf5a44 into main Apr 18, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant