Skip to content

fix(gateway): preserve queued follow-up media continuity - #71031

Closed
StellarisW wants to merge 7 commits into
NousResearch:mainfrom
StellarisW:fix/queued-followup-media-continuity
Closed

StellarisW wants to merge 7 commits into
NousResearch:mainfrom
StellarisW:fix/queued-followup-media-continuity

Conversation

@StellarisW

Copy link
Copy Markdown

What does this PR do?

Queued follow-up delivery had a separate direct-send path from normal final-response delivery. That path could expose a literal MEDIA: directive instead of uploading the attachment, and it skipped explicit media entirely when the first response text had already been streamed.

This PR keeps queued text and attachment delivery continuous across both fallback and streamed cases:

  • strip explicit attachment directives from fallback text before sending it;
  • deliver explicit attachments exactly once even when the text was already streamed;
  • reuse the queued turn's precomputed thread/reply metadata for the attachment;
  • preserve bare local paths as visible text because post-stream uploads are intentionally explicit-only;
  • keep intentional-silence filtering and the existing first-response-before-follow-up ordering.

This is an attribution-preserving consolidation of #25119. Its three commits remain in the branch with Kong Ka Weng (vKongv) as author; the additional commits add current-main compatibility and cover the streamed sibling path.

Related Issue

Fixes #60845

Related: #18539, #55806, #19011, #25119, #46223, #51805

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • gateway/run.py
    • route queued first responses through the existing explicit-media delivery flow;
    • skip only duplicate text after confirmed streaming, not its attachments;
    • reuse precomputed thread/reply metadata for text and media;
    • retain bare local paths in visible text rather than silently deleting paths that will not be uploaded.
  • tests/gateway/test_run_progress_topics.py
    • cover fallback and streamed queued-follow-up delivery through the real recursive path;
    • assert text cleanup, exactly-once native attachment delivery, follow-up completion, and Discord thread metadata.
  • tests/gateway/test_tts_media_routing.py

How to Test

  1. Run the related gateway files:
    pytest tests/gateway/test_run_progress_topics.py tests/gateway/test_post_stream_media_delivery.py tests/gateway/test_tts_media_routing.py -p no:cacheprovider -o 'addopts=' -q
    Expected: 63 passed.
  2. Run queued-follow-up coverage across gateway tests:
    pytest tests/gateway -k 'queued_followup or queued_message or queued_resend' -p no:cacheprovider -o 'addopts=' -q
    Expected: 14 passed, 1 skipped.
  3. Run blocking static gates:
    ruff check .
    python scripts/check-windows-footguns.py --all

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: Linux (Python 3.11; focused gateway integration tests and blocking static gates)

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A: no user-facing configuration or API change
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A: no config change
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A: no architecture/workflow change
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — check-windows-footguns.py --all passes
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A: no tool schema change

Screenshots / Logs

Not applicable. The regression is covered by gateway integration tests that assert observable text, attachment, recursion, and routing behavior.

StellarisW and others added 7 commits July 25, 2026 04:53
Ensure queued follow-up resends keep MEDIA-backed attachments by replaying the
first response through the gateway's text-plus-media delivery flow instead of a
plain adapter text send.
MEDIA:/tmp paths are filtered by delivery safe roots; mirror other tests
by placing the fixture under an allowed cache directory.
Drop the broad MEDIA: regex after extract_media so code/inline examples
survive, and cover the real queued first-response resend path in tests.
@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages needs-decision Awaiting maintainer decision before any implementation labels Jul 24, 2026
@alt-glitch

Copy link
Copy Markdown

This was generated by AI during triage.

Related: #25119, #46223, and #51805. This consolidates #25119's explicit-MEDIA path and additionally covers media after confirmed streamed text, so it is a broader competing repair rather than a duplicate.

@teknium1

Copy link
Copy Markdown
Collaborator

Thanks for consolidating the queued-media repair. The premise remains verified on current main: gateway/run.py:24346-24356 directly sends the queued first response, while gateway/run.py:24359-24363 skips it after confirmed streaming; neither branch invokes the explicit-media uploader. By contrast, the normal streamed completion path invokes _deliver_media_from_response() at gateway/run.py:17258-17264, and that helper extracts and dispatches explicit attachments at gateway/run.py:18359-18463.

The PR's text/media split and reuse of the queued turn's routing metadata fit that existing delivery contract. Its fallback and confirmed-stream coverage addresses both current branches. The branch is conflicting after the July 29 gateway refactor, but the required salvage is a narrow, conflict-aware reapplication around gateway/run.py:24346 plus its regression tests.

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 30, 2026
@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown

This was generated by AI during triage.

Summary

Two PRs address the queued-delivery bypass in Issue #60845. #25119 fixes the fallback queued resend path by routing text and explicit MEDIA attachments through the existing delivery helper; #71031 consolidates that path and additionally fixes explicit-media delivery when the first response was already streamed before queued recursion.

Related pull requests

Duplicates

#25119 and the fallback portion of #71031 implement the same explicit-MEDIA queued-resend repair; #71031 is the broader consolidation because it also covers media after confirmed streamed text.

Suggested consolidation

keep open with a salvage path for #71031: rebase or reapply its verified text/media split and streamed-branch handling around the current gateway implementation, retaining the fallback and streamed regression tests. After that salvage is complete, close #25119 as duplicate/superseded by #71031; this explicitly addresses the keep_open review on #25119 because #71031 contains its salvageable fix while covering the additional streamed path.

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
    I60845(["issue #60845 (open)"])
    subgraph Dup25119 ["PRs duplicating each other"]
        P25119["PR #25119 (open)"]
        P71031["PR #71031 (open)"]
    end
    P71031 -->|best fix| I60845
    class I60845 open
    class P25119 open
    class P71031 open
    class P71031 best
    class P71031 target
    click I60845 "https://github.com/NousResearch/hermes-agent/issues/60845"
    click P25119 "https://github.com/NousResearch/hermes-agent/pull/25119"
    click P71031 "https://github.com/NousResearch/hermes-agent/pull/71031"
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 2 pull requests and 1 issue in this complex. Each diff was read against this issue; Assessment working set: 40 kB of PR diffs, 8 kB of issue/PR text, 6 kB of discussion (10 comments), 4 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

@alt-glitch

Copy link
Copy Markdown

This was generated by AI during triage.

Related: this is an attribution-preserving consolidation of #25119 and overlaps #46223/#51805 for #60845. The branch is conflicting and needs a rebase before maintainers select a queued-media repair.

@teknium1

teknium1 commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

Merged via #82162 with all seven of your commits and authorship preserved (rebase-merge; d220f1f … a52dd17 on main), plus one follow-up commit (0b17b69) adding the failed-result guard: failed first turns deliver their normalized failure text but skip attachment upload, mirroring the completed-turn path.

This was the strongest of the five PRs attacking this bug — the only one covering BOTH branches (fallback text leak + streamed attachment drop) while preserving the explicit-only delivery policy, thread routing, and protected MEDIA examples, with exactly-once assertions. The consolidation of @vKongv's #25119 commits with attribution was handled well. Verified against the full tests/gateway/ suite (5101 pass).

Closing this original since the salvage PR carried it in — thanks for the persistent, careful work here.

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 needs-decision Awaiting maintainer decision before any implementation 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 type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(gateway): queued follow-up responses bypass MEDIA extraction — attachment delivered as plain text path

5 participants