Skip to content

fix(gateway): close silent response loss after agent tool calls (#29346)#34336

Merged
teknium1 merged 2 commits into
NousResearch:mainfrom
banditburai:fix/silent-response-loss-tool-calls
Jun 2, 2026
Merged

fix(gateway): close silent response loss after agent tool calls (#29346)#34336
teknium1 merged 2 commits into
NousResearch:mainfrom
banditburai:fix/silent-response-loss-tool-calls

Conversation

@banditburai

@banditburai banditburai commented May 29, 2026

Copy link
Copy Markdown
Contributor

Summary

On gateways, a tool-using turn could produce a correct, non-empty final answer
that the user never received — logs show response ready with no matching
Sending response and no error. This fixes the two independent gateway-side
causes and adds a loud invariant so a non-empty response is no longer dropped
silently.

Root cause

  • A streamed preamble ("Let me search…") finalized at a tool boundary marked the
    turn as already-delivered, so the gateway suppressed the genuine answer
    produced on the next API call. (Only with fresh_final_after_seconds > 0.)
  • The extract pipeline (media/image/local-file extraction + directive strips)
    can reduce a non-empty response to empty text with no attachment; the
    if text_content send guard then skips delivery silently.

Fix

  • Scope the stream consumer's final-delivery flags to the turn-final segment and
    clear them at every tool boundary, so a preamble can no longer mark the turn
    delivered.
  • When extraction yields empty text and no attachment, deliver the recovered
    original — taken from the post-extract body so a spaced MEDIA: path can't
    leak — on every platform.
  • Add a delivery invariant: every recovery logs response_delivery_recovered
    (WARNING); any genuinely undeliverable non-empty response logs
    response_delivery_dropped (ERROR).

Closes / supersedes

Closes #29346. Supersedes #33842 (Discord-only) and #29499 by consolidating both
into one all-platform fix that sanitizes the recovered text and won't re-send
alongside an attachment; #33842's test harness is salvaged and generalized.

Testing

Failing-first regressions for both causes; an exactly-once test guarding against
duplicate sends; a loud-drop ERROR test; and a no-leak test for spaced MEDIA:
paths. The gateway delivery and stream-consumer suites pass.

Risk

Scoping the final-delivery state to the turn-final segment is duplicate-safe by
construction — got_done returns before any reset, and the gateway reads that
state only after the stream task exits. The recovery and invariant are additive
(recover-or-log only, no change to existing send behavior). Known limitation: the
invariant treats a produced attachment as "delivered" even if its send later
fails, but those failures are already logged at the send site.

Out of scope

The freeze in #28834 (tool calls hanging after idle on macOS) is a separate
CLI/event-loop bug, not gateway delivery loss — not addressed here.

Infographic

gateway-reliability-three-fixes

@alt-glitch alt-glitch added type/bug Something isn't working P1 High — major feature broken, no workaround comp/gateway Gateway runner, session dispatch, delivery labels May 29, 2026
@banditburai banditburai force-pushed the fix/silent-response-loss-tool-calls branch 2 times, most recently from e467c0b to f40bfea Compare May 30, 2026 14:58
…esearch#29346)

A streamed preamble ("Let me search...") finalized at a tool boundary
routed through _try_fresh_final, which unconditionally set
_final_response_sent=True even though it is a NON-final segment. The
gateway then reads that flag as "final delivered" and suppresses the
genuine final answer produced on the next API call, so the user silently
gets nothing. Only reproduces with fresh_final_after_seconds > 0.

- _try_fresh_final / _send_or_edit take is_turn_final; the segment-break
  call site passes is_turn_final=got_done so only the turn-final answer
  marks final-delivered.
- _reset_segment_state clears the final-delivery flags at every tool
  boundary as defense-in-depth against any future premature setter.
- Failing-first regression + happy-path no-duplicate test.
NousResearch#29346)

The extract pipeline (extract_media/extract_images/extract_local_files +
directive strips) can reduce a non-empty tool-using response to empty
text_content with no deliverable attachment. The 'if text_content' send
guard then silently skips delivery: a 'response ready' log with no
'Sending response', no error, and the answer never reaches the user.

- A2: snapshot the pre-extract response; when extraction yields empty text
  and no image/local/media attachment, deliver the recovered original from
  the post-extract_media body (so a spaced MEDIA path can't leak). Applies
  on ALL platforms (supersedes the Discord-only NousResearch#33842 and the unsafe
  raw-fallback NousResearch#29499).
- A3: loud delivery invariant - a non-empty response that produces nothing
  deliverable logs response_delivery_dropped at ERROR; every recovery logs
  response_delivery_recovered. No silent drop survives.
- Factor a _strip_media_directives helper for the [[...]] strips; MEDIA
  stripping stays owned by extract_media, whose grammar handles spaced and
  quoted paths.
- Salvaged + de-scoped the NousResearch#33842 test harness to all platforms; added
  unrecoverable-drop and no-leak regression tests.
@banditburai banditburai force-pushed the fix/silent-response-loss-tool-calls branch from f40bfea to 8bee4ea Compare June 1, 2026 16:19
@teknium1 teknium1 merged commit a1f76ba into NousResearch:main Jun 2, 2026
23 checks passed
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 P1 High — major feature broken, no workaround type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Discord: tool-using responses (api_calls≥2) silently dropped — no Sending response log after response ready

3 participants