Skip to content

fix(gateway): skip sender prefix for internal events + NO_REPLY contract in async delegation (#66480) - #66507

Open
Tranquil-Flow wants to merge 1 commit into
NousResearch:mainfrom
Tranquil-Flow:fix/66480-async-delegation-internal
Open

fix(gateway): skip sender prefix for internal events + NO_REPLY contract in async delegation (#66480)#66507
Tranquil-Flow wants to merge 1 commit into
NousResearch:mainfrom
Tranquil-Flow:fix/66480-async-delegation-internal

Conversation

@Tranquil-Flow

Copy link
Copy Markdown
Contributor

What

In shared multi-user gateway sessions, async-delegation completions injected via _inject_watch_notification impersonated the originating user: _prepare_inbound_message_text() applied the [user_name] sender prefix to all messages, including internal synthetic events. The parent then saw text shaped like [Alice] [ASYNC DELEGATION BATCH COMPLETE …] even though Alice sent nothing.

Separately, the async-delegation completion formatter told the agent to "act on the result or re-dispatch", but never told it to suppress a redundant reply when the result did not materially change an already-delivered answer. The gateway already supports suppressing a turn whose exact response is NO_REPLY (see gateway/response_filters.py), but the completion prompt never invoked that contract.

Closes #66480.

How

Two focused changes, both covering the full bug class:

  1. Sender prefix bypass for internal eventsgateway/run.py: the shared-session prefix condition gains and not event.internal, so internal synthetic events (async delegation completions, which carry the originating SessionSource but are marked internal=True) skip the [user_name] prefix. MessageEvent.internal defaults to False, so normal messages are unaffected.

  2. NO_REPLY no-news contract in BOTH completion formatterstools/process_registry.py: _format_async_delegation() now appends:

    "If this does not materially change or correct an answer you have already delivered, respond exactly NO_REPLY so no redundant follow-up is sent to the user."

    to both the single and the batch (fan-out) completion headers, so a no-news completion no longer triggers a redundant user-facing reply while still surfacing material updates/corrections.

Verification

Reproduced on current main before the fix; both symptoms gone after.

TDD red→green, run against the shared venv Python (Python 3.11.15):

  • tests/gateway/test_shared_group_sender_prefix.py::test_internal_event_skips_sender_prefix_in_shared_session — fails on main (output was [Alice] [ASYNC …]), passes after fix.
  • tests/tools/test_async_delegation.py::test_async_delegation_formatter_includes_no_reply_contract — fails on main (no NO_REPLY in single output), passes after fix.
  • tests/tools/test_async_delegation.py::test_batch_async_delegation_formatter_includes_no_reply_contract — fails on main (no NO_REPLY in batch output), passes after fix. The issue requires the contract in both single and batch formatters.

Existing invariants preserved:

  • test_preprocess_prefixes_sender_for_shared_non_thread_group_session — normal shared message still becomes [Alice] hello.
  • test_preprocess_keeps_plain_text_for_default_group_sessions — default group sessions still get plain text.

Full focused suite after rebase onto main (7f76fc040):

  • 167 passed, 0 failed across tests/tools/test_async_delegation.py, tests/gateway/test_shared_group_sender_prefix.py, tests/tools/test_process_registry.py, tests/gateway/test_completion_delivery.py, tests/gateway/test_async_delegation_session_binding.py, tests/gateway/test_internal_event_never_interrupts_busy_session.py.

Branch is one focused commit ahead of main (0 1); git diff --check clean.

Competitor analysis

No competing open PRs found for #66480 (targeted search by issue number, branch-name pattern, and keywords).


Auto-published by Moonsong via Path B automated pipeline.

@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery comp/tools Tool registry, model_tools, toolsets tool/delegate Subagent delegation P2 Medium — degraded but workaround exists sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Jul 17, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the focused regression fix. Current main still applies the shared-session sender prefix without checking event.internal in gateway/run.py:10794, while _inject_watch_notification() creates its synthetic completion event with internal=True at gateway/run.py:16019-16025. Both async-delegation formatter headers also still omit a NO_REPLY no-news instruction at tools/process_registry.py:2070-2077 and tools/process_registry.py:2128-2134; the gateway already suppresses successful exact NO_REPLY responses through gateway/response_filters.py:14-79 and gateway/run.py:12176-12181,12657-12667.

The proposed changes address those exact paths, and gateway/run.py plus tools/process_registry.py are unchanged from the PR base through current main, so this should be mechanically salvageable. This is an automated hermes-sweeper review.

@teknium1 teknium1 added the sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform label Jul 18, 2026
@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown

This was generated by AI during triage.

Summary

Five PRs address or reference two related sender-context issues: #18711 exposes the verified current Slack author mention, #66507 prevents internal-event impersonation and suppresses redundant async follow-ups, #67886 repairs sender-prefix ordering, #13939 broadens ID-qualified attribution, and #44705 labels unnamed shared-session participants. The attribution PRs overlap in gateway code but target distinct causes and have documented salvage requirements.

Related pull requests

Duplicates

No listed PRs are exact duplicates: #13939 and #44705 overlap on sender attribution, while #67886 changes ordering in the same path but fixes a separate enrichment bug; #18711 is the closed source implementation integrated through #69320, and #66507 addresses the separate internal-event and async-completion causes.

Suggested consolidation

Keep #66507 open with its focused salvage path for #66480. Keep #67886 open with a salvage path that rebases the ordering change onto current main, preserves the Slack mention augmentation, and adds the requested regression; author action for #13939 is to rebase or split out viable attribution work after resolving the contributor findings, and author action for #44705 is to split the participant-labeling fix from unrelated hunks and add the Yuanbao no-double-prefix guard. Leave #18711 closed because its implementation was integrated via #69320; the evidence does not support closing any remaining listed PR as an exact duplicate.

Complex graph

flowchart LR
    classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
    classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
    classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
    classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
    classDef best stroke-width:3px,stroke:#b45309
    classDef target stroke-width:3px,stroke:#4338ca
    I66480(["issue #66480 (open)"])
    P66507["PR #66507 (open)"]
    P66507 -->|best fix| I66480
    class I66480 open
    class P66507 open
    class P66507 best
    class P66507 target
    click I66480 "https://github.com/NousResearch/hermes-agent/issues/66480"
    click P66507 "https://github.com/NousResearch/hermes-agent/pull/66507"
Loading

Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label).

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

@yuzilongleif-collab

Copy link
Copy Markdown
Contributor

Independent real-world verification on Hermes Agent v0.20.2 and the current origin/main:

  • A background fan-out completed after the parent session had already checkpointed and paused.
  • The durable completion event re-entered the correct Telegram session.
  • The result did not materially change the accepted conclusion, risk, required action, or active plan.
  • The parent still emitted a redundant user-facing acknowledgement whose only value was "the late results were absorbed".
  • The delegation row was already terminal (completed/delivered), with no pending or running work, so this was not a cron job, replay loop, or wrong-session routing problem.

This confirms that the NO_REPLY contract belongs in both the single and batch completion formatters, as this PR proposes.

I independently validated an equivalent minimal change against the current tree:

  • tests/tools/test_process_registry.py: 79 passed, 4 skipped
  • async delegation / durable replay / gateway binding / internal marker / stream silence / CLI delivery slice: 45 passed
  • targeted ruff check: passed
  • py_compile: passed

One wording hardening may help in practice:

Do not send a message merely to say the result was received, absorbed, or unchanged.

The affected agent already had a general user preference to absorb unchanged late reviews silently, but still generated an acknowledgement. Making the prohibition explicit in the completion envelope reduces that ambiguity.

I did not open a duplicate PR. This branch is currently reported as mergeable=false / dirty; if a maintainer prefers a clean current-main replacement, I can provide the rebased minimal commit and regression tests.

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/tools Tool registry, model_tools, toolsets P2 Medium — degraded but workaround exists 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 tool/delegate Subagent delegation type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Async delegation completion impersonates user and prompts redundant follow-up

5 participants