Skip to content

fix(simplex): batch multi-attachment sends into one event instead of interrupting the run - #66141

Open
cwade12c wants to merge 1 commit into
NousResearch:mainfrom
cwade12c:fix/simplex-media-batching
Open

cwade12c wants to merge 1 commit into
NousResearch:mainfrom
cwade12c:fix/simplex-media-batching

Conversation

@cwade12c

@cwade12c cwade12c commented Jul 17, 2026

Copy link
Copy Markdown

What does this PR do?

Stops a multi-attachment send from interrupting the agent's in-flight run. SimpleX has no album concept: sending 2+ images as one message from a phone client delivers each image as its own chat item with its own XFTP transfer. The adapter dispatched every non-text item immediately, so image 1 started an agent run and image 2 arrived seconds later into the busy session, with the default busy_input_mode: interrupt, the user gets "⚡ Interrupting current task. I'll respond to your message shortly." instead of one reply that saw both images.

The adapter already batches rapid-fire text through a quiet-period batcher (mirroring Telegram), and the batcher's merge path already merges media_urls/media_types; that branch was just unreachable, because the dispatch gate only routed TEXT events into it.

This PR:

  • Widens the gate so PHOTO/DOCUMENT/VOICE events flow through the same quiet-period batcher (a voice-note burst is the same shape as an image pair).
  • Fixes the merge path to recompute message_type on every merge (VOICE > PHOTO > DOCUMENT > TEXT, the same priority used to classify a single message); previously a batch stayed stuck at the first event's type, so text-then-image would dispatch as TEXT and the gateway's media handling would never see the attachment.
  • Holds the flush while sibling transfers are downloading. A quiet period alone is not enough: the chat items of one send arrive together (sub-second), but their XFTP downloads complete serially and regularly finish further apart than any sane quiet window. Since the adapter already tracks pending transfers, the flush now re-arms while a transfer for the same chat is still on the wire, bounded by a 60s cap so one stalled download can't hold the chat's messages hostage.
  • Adds an optional platforms.simplex.extra.media_batch_delay (defaults to the existing text batch delay; no new env var, per AGENTS.md; behavioral settings live in config.yaml).

Verified end-to-end on a live deployment (Fedora, Hermes v0.18.0, simplex-chat v6.5.5, iOS client).

Before (two images sent as one message split into two events and the second interrupted the run):

18:25:45,554 SimpleX: file 23 (IMG_....jpg) not yet received, accepting transfer
18:25:46,141 SimpleX: file 24 (IMG_..._1.jpg) not yet received, accepting transfer
18:25:48,812 [SimpleX] Flushing message batch simplex:4 (0 chars, 1 media items)
18:25:50,238 [SimpleX] Flushing message batch simplex:4 (0 chars, 1 media items)

(the two completions landed ~1.4s apart, outside any reasonable quiet window, which is why the pending-transfer hold exists rather than a bigger delay).

After (one event carrying both images plus the caption, one agent reply, no interrupt):

08:23:40,760 SimpleX: file 25 (IMG_....jpg) not yet received, accepting transfer
08:23:41,459 SimpleX: file 26 (IMG_..._1.jpg) not yet received, accepting transfer
08:23:44,133 [SimpleX] Flushing message batch simplex:4 (31 chars, 2 media items)
08:23:44,245 inbound message: platform=simplex user=... chat=4 msg='...'

Note: on current main, inbound images reach the deferral path (and therefore the pending-transfer hold) only once the audio-only /freceive guard is generalized; #55180 / PR #55185, same dependency #59974 has. This fix stands alone for voice-note bursts and captioned sends, and composes with #55185 for the full multi-image flow. Telegram has the same class of bug on file (#53198, #31541); this is the SimpleX sibling. Also adjacent but orthogonal: #63163 changes the batch key (per-sender authorship in groups); this PR changes what the batch accepts (they compose).

Related Issue

None found: I searched open/closed issues and PRs for simplex image batch, simplex multiple images, simplex interrupt, album, simplex batching; nearest matches are the Telegram analogs (#53198/#31541) and the complementary #55185. Root-cause analysis included above in lieu of a pre-filed issue; I am happy to split one out if preferred.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • plugins/platforms/simplex/adapter.py: route PHOTO/DOCUMENT/VOICE through _enqueue_text_event; extract _message_type_for_media() and recompute the merged event's type on every merge; track the batch key of each pending transfer and hold _flush_text_batch while siblings are downloading (MEDIA_BATCH_MAX_HOLD = 60s cap); read extra.media_batch_delay (defaults to the text delay); generalize the flush log line; document the setting in a module-docstring config.yaml section.
  • tests/gateway/test_simplex_plugin.py: six new tests: two photos in one window become one PHOTO event with both media_urls; text-then-image upgrades the merged type; mixed media resolves by VOICE > PHOTO > DOCUMENT precedence; a single image still dispatches after the window; the flush holds while a sibling transfer is pending (driven through the real rcvFileComplete path) and flushes both once it lands; the hold gives up at the cap. Config goes through PlatformConfig(extra={"media_batch_delay": ...}).
  • website/docs/user-guide/messaging/simplex.md: document platforms.simplex.extra.media_batch_delay with a config example.

How to Test

  1. Pair a phone client with the bot, start an agent conversation.
  2. Send 2+ images as a single message (optionally with a caption).
  3. Without this patch: gateway logs two separate one-item flushes and the user gets "⚡ Interrupting current task…" and the run restarts against the second image alone. With this patch: one Flushing message batch … (N chars, 2 media items) line, one agent reply that references both images.
  4. scripts/run_tests.sh tests/gateway/test_simplex_plugin.py (36 pass).

Checklist

Code

Infographic

hermes-simplex-media-batching-pr-66141-v2

SimpleX has no album concept -- sending 2+ images from the iOS app
arrives as separate chat items dispatched immediately, so image 2
lands mid-agent-run and triggers an "Interrupting current task" ack
instead of being treated as part of the same message.

Widen the existing text quiet-period batcher to also cover
PHOTO/DOCUMENT/VOICE events, and fix the merge path to recompute
message_type (VOICE > PHOTO > DOCUMENT > TEXT) instead of leaving it
stuck at the first event's type. Media delay defaults to the
existing 0.8s text delay and can be widened independently via
platforms.simplex.extra.media_batch_delay for slower XFTP transfers,
without adding a new env var.

A quiet period alone is not enough in practice: the chat items of a
multi-attachment send arrive together, but their XFTP downloads
complete serially and regularly land further apart than any sane
quiet period. Since the adapter already knows which transfers are
still pending, the flush now holds the batch open while a sibling
transfer for the same chat is downloading (bounded by a 60s cap so a
stalled transfer cannot hold the chat's messages hostage), instead
of guessing at a longer delay.
@alt-glitch alt-glitch added type/bug Something isn't working comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Jul 17, 2026
@cwade12c
cwade12c marked this pull request as ready for review July 17, 2026 07:03

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review Summary

Verdict: Approved with notes

PR #66141 fix(simplex): batch multi-attachment sends into one event instead of interrupting the run

Assessment

  • Correctness: SimpleX multi-attachment sends were arriving as separate chat items (interrupting the run per attachment). The fix batches them by holding the batch open while sibling file transfers are still on the wire, with MEDIA_BATCH_MAX_HOLD (60s) as an upper bound to prevent stalled transfers from holding messages hostage. _message_type_for_media() classifies batched media by priority (audio > image > document > text).
  • Scope: ~560 lines, focused on the batching logic. Consistent with the existing text batching pattern.
  • Testing: Docstring documents the behavior clearly; logic is sound.
  • Note: The MEDIA_BATCH_MAX_HOLD cutoff at 60s is a reasonable upper bound. The except clause that flushes on any error in _build_batch_hold_tasks is appropriate.

Reviewed by Hermes Agent

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the focused SimpleX investigation. The current-main batching premise is confirmed at plugins/platforms/simplex/adapter.py:661-667, but this needs a few correctness changes before salvage.

Problems

  • Incomplete image/document transfers still bypass the new hold path: current main's audio-only guard remains at plugins/platforms/simplex/adapter.py:580, and this PR only records the batch key inside that guard. The full XFTP image path therefore still depends on #55185.
  • Reclassifying a mixed voice/image batch as VOICE routes every attachment to STT: gateway/run.py:10830-10836 appends every path when event.message_type == MessageType.VOICE, irrespective of its MIME type.
  • The retained key at plugins/platforms/simplex/adapter.py:673-675 is chat-scoped, while group sender identity comes from groupMember at :526-530; rapid attachments from distinct members can merge under the first sender.

Suggested changes

  • Compose with the all-file deferral fix and add a pending-image completion regression.
  • Add a gateway-path mixed voice/image test and make audio routing attachment-MIME-aware before using VOICE as an aggregate type.
  • Use a sender/session-aware group batch key and test two concurrent group members.

Automated hermes-sweeper review.

file_id,
)
self._pending_file_transfers[file_id] = chat_item
self._pending_transfer_batch_keys[file_id] = (

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This new batch-key tracking only runs inside the existing _is_audio_ext(ext) pending-file branch. Incomplete XFTP images/documents still never enter the pending-transfer map or this hold path; please compose this with the all-file deferral change and cover an image completion event.

existing.media_types.extend(event.media_types)
existing.message_type = _message_type_for_media(existing.media_types)

prior_task = self._pending_text_batch_tasks.get(key)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

A mixed image+voice batch becomes VOICE here, but gateway/run.py:10830-10836 treats every path in a VOICE event as audio and sends it to STT. Please either make downstream audio selection per-attachment or avoid a whole-event VOICE type for mixed batches, with an end-to-end regression test.

@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 18, 2026
@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

This was generated by AI during triage.

Summary

Four open PRs touch the SimpleX integration, but their diffs address distinct causes: invalid contact-discovery documentation (#29520), spoofable allowlist matching (#41246), unresolved daemon-relative attachment paths (#59974), and separately dispatched multi-attachment events (#66141). They are related through the SimpleX integration, not competing implementations of one fix.

Related pull requests

  • docs(simplex): fix contact discovery command #29520 related — (+7/-1) — n/a: Replaces the invalid internal send_message action=list notation with the verified CLI command hermes send --list simplex. Consistent with the automated keep-open verdict, retain this documentation fix but reconcile it with current main, update the zh-Hans counterpart, and document the gateway-run prerequisite for populating the target cache.
  • fix(gateway): match SimpleX allowlist on local alias, not spoofable profile name #41246 related — (+87/-24) — n/a: Replaces authorization against the remote-controlled profile name with the locally assigned alias in user_id_alt, with tests for accepting the local alias and rejecting a spoofed profile name. Consistent with the automated keep-open verdict, the security fix needs a current-main port to gateway/authz_mixin.py, adapter-to-authorization coverage for direct and group payloads, and aligned configuration references.
  • fix(simplex): resolve received-file paths against SIMPLEX_FILES_FOLDER #59974 related — (+111/-1) — n/a: Resolves relative received-file paths against platforms.simplex.extra.files_folder, while preserving absolute paths and unchanged behavior when no base folder is configured. The contributor keep-open review requested this config.yaml surface and PlatformConfig(extra={"files_folder": ...}) coverage; the displayed diff already contains both, so retain the path-resolution implementation and validate it against the current head.
  • fix(simplex): batch multi-attachment sends into one event instead of interrupting the run #66141 related — (+358/-34) — n/a: Extends batching to media, recomputes the aggregate message type, and holds completed items while sibling transfers remain pending, directly targeting interruptions from separately delivered attachments. The contributor keep-open review remains blocking because incomplete image/document transfers can bypass the hold path, mixed voice/image batches can route every attachment through STT, and chat-scoped group keys can merge different senders.

Suggested consolidation

Keep #29520 open with a salvage path for the localized documentation, current-main reconciliation, and discovery-cache prerequisite. Author action on #41246: rebase onto main or split out the authorization fix with current adapter wiring and end-to-end coverage; keep #59974 open with its config-based path-resolution logic for current-head validation, and keep #66141 open until the contributor-identified transfer, mixed-media routing, and group-sender isolation blockers are addressed. None of these PRs should be closed as duplicates because their inspected diffs address separate causes.

Cross-PR triage: Reviewed 4 pull requests and 0 issues in this complex. Each diff was read against this issue; Assessment working set: 45 kB of PR diffs, 18 kB of issue/PR text, 5 kB of discussion (5 comments), 0 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have 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 sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants