Skip to content

fix(simplex): make polling recovery visible and bounded - #27415

Open
Zoffka wants to merge 4 commits into
NousResearch:mainfrom
Zoffka:fix/simplex-polling-recovery
Open

fix(simplex): make polling recovery visible and bounded#27415
Zoffka wants to merge 4 commits into
NousResearch:mainfrom
Zoffka:fix/simplex-polling-recovery

Conversation

@Zoffka

@Zoffka Zoffka commented May 17, 2026

Copy link
Copy Markdown

Summary

This PR tightens the SimpleX gateway fallback path for cases where daemon push events are missing or stale but /tail still exposes the message history.

It does not claim to fix every SimpleX daemon transport stall. During live testing, the local simplex-chat daemon itself could get contact-transport stale (sndSent stuck, no fresh inbound in daemon history) and recovered after restarting only the daemon. This PR focuses on the Hermes-side failures that are reproducible and covered by tests.

Changes

  • Add a polling fallback for missed SimpleX chat items using /tail 50.
  • Bound poll commands with short connect/read timeouts plus a hard wall-clock timeout so a stuck ephemeral websocket cannot stall the poll loop.
  • Persist composite seen keys (direct:<contactId>:<itemId>, group:<groupId>:<itemId>) so restarts do not replay old mail.
  • Do not seed unread inbound text as already seen; unread user messages remain actionable after restart/reconnect.
  • Dispatch each polled batch in daemon order while keeping the poll loop free for the next tick.
  • Mark polled inbound items read with /_read chat items ... so SimpleX users get a visible read indicator.
  • Resolve outbound numeric Hermes chat IDs to SimpleX display-name targets before sending.
  • Add a delayed SimpleX processing notice for slow turns because SimpleX has no typing indicator.
  • Add an optional per-adapter stale active-session cap, used by SimpleX, so a fresh polled DM cannot sit behind an old stuck turn indefinitely.

Verification

Local tests:

47 passed   tests/gateway/test_simplex_plugin.py
288 passed  tests/gateway/test_simplex_plugin.py tests/gateway/test_platform_base.py tests/gateway/test_api_server.py

The broader gateway slice was run with live API-server CORS environment overrides unset so default-CORS tests used their expected defaults.

Live SimpleX checks on a local gateway:

  • Outbound SimpleX marker delivered and acknowledged as sndRcvd.
  • Fresh DM test ingested from SimpleX and replied to.
  • Fresh DM wow ingested from SimpleX and replied to.
  • Fresh group message test here ingested from SimpleX and replied to.
  • Restart recovery test: an unread inbound item that predated gateway restart was left unseeded, dispatched on the first poll tick, marked read, and replied to.

Observed daemon-level caveat:

  • In one failure mode, Hermes was still polling but the local simplex-chat daemon did not show the fresh contact message in /tail, and outbound messages stayed sndSent rather than sndRcvd. Restarting only simplex-chat.service restored delivery. That daemon transport-health issue is intentionally not hidden by this PR and should be handled separately if needed.

Zoffka added 3 commits May 17, 2026 10:37
Reduce fallback polling latency, persist seen item IDs, and resolve SimpleX send targets via CLI display names.
Improve the SimpleX fallback path when daemon push events are stale or missing. Keep polling on a short wall-clock budget, dispatch polled batches in daemon order, mark polled inbound items read, avoid seeding unread inbound text as stale history, and show a delayed processing notice for slow SimpleX turns.
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/plugins Plugin system and bundled plugins comp/gateway Gateway runner, session dispatch, delivery labels May 17, 2026
@muddlebee

Copy link
Copy Markdown

why?

@teknium1

teknium1 commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Thanks for the detailed SimpleX recovery work. I verified that the underlying gap still exists on current main: SimplexAdapter.connect() starts only _ws_listener() and _health_monitor() (plugins/platforms/simplex/adapter.py:235-238), and inbound delivery still depends on pushed newChatItems / newChatItem events (plugins/platforms/simplex/adapter.py:421-436).

Problems

  • The PR’s poll-thread dispatch does not actually hop back to the gateway loop. PR head plugins/platforms/simplex/adapter.py:319-342 runs polling inside a dedicated thread event loop, but _dispatch_polled_items() then sees that loop via asyncio.get_running_loop() and creates the dispatch task on it (plugins/platforms/simplex/adapter.py:791-800). That means _handle_new_chat_item() / handle_message() can run on the poll-thread loop; the intended run_coroutine_threadsafe() gateway-loop branch (plugins/platforms/simplex/adapter.py:802-809) is bypassed from the poller.
  • A salvage needs to preserve current main’s newer SimpleX handler behavior: correlated command responses are handled at plugins/platforms/simplex/adapter.py:370-390, and file descriptor/completion events at plugins/platforms/simplex/adapter.py:406-467.

Suggested changes

  • Schedule polled dispatch on self._gateway_loop whenever the current loop is not the gateway loop; keep direct create_task() only for gateway-loop/test calls.
  • Port the polling pieces into the current adapter without replacing the newer event/file handling paths.

This is an automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery 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-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades 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.

4 participants