Skip to content

fix(gateway): recover resume_pending sessions instead of sending a blank turn - #56262

Merged
teknium1 merged 2 commits into
mainfrom
hermes/hermes-5363b8db
Jul 1, 2026
Merged

fix(gateway): recover resume_pending sessions instead of sending a blank turn#56262
teknium1 merged 2 commits into
mainfrom
hermes/hermes-5363b8db

Conversation

@teknium1

@teknium1 teknium1 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

After a gateway restart, an auto-resumed thread now always gets its recovery system note instead of occasionally receiving a genuinely blank user turn (which made the model reply with confused "that message came through blank" noise).

Root cause: _schedule_resume_pending_sessions dispatches an empty-text turn expecting the injector to fill it with a recovery note, but the scheduler judges freshness off last_resume_marked_at (interrupt time) while the injector gates _is_resume_pending on the transcript clock (_last_transcript_timestamp). For a thread quiet >1h before the crash, those disagree → _is_resume_pending goes False → the note is never injected → the model gets a blank user turn.

Changes

  • gateway/run.py: (1) dual freshness signal_is_resume_pending now fires when the transcript clock or last_resume_marked_at is within the window, so the scheduler's decision and the per-turn injection agree. (2) empty-turn safety net — if message is still blank AND the session is resume_pending, backfill the recovery note; scoped to resume_pending so a legitimately empty user turn (uncaptioned image) on a normal session is untouched. The safety-net note reuses the canonical reason-aware recovery wording from the _is_resume_pending branch (follow-up on the salvage).
  • scripts/release.py: AUTHOR_MAP entry for the salvaged commit.

Validation

Before After
Interrupted-just-now, transcript row 1h+ old _is_resume_pending=False → blank turn dual signal → note injected
Blank turn on resume_pending (branch missed) reaches model empty backfilled recovery note
Blank turn on ordinary session untouched untouched
tests/gateway/test_restart_resume_pending.py 78 passed (3 new, confirmed RED pre-fix)

E2E-verified against the real _is_fresh_gateway_interruption / _last_transcript_timestamp helpers: the exact stale-transcript bug case flips from False (old single-signal) to True (dual signal).

Salvaged from #46997 by @abchiaravalle (first-time contributor); authorship preserved via rebase-merge.

Infographic

Gateway resume no blank turns

Nous Research

@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state P2 Medium — degraded but workaround exists labels Jul 1, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Companion to #56264 (freshness gate in gateway/session.py) — this fixes the recovery-note injection mismatch in gateway/run.py. related_to #46997/#46934. Not a duplicate.

@teknium1
teknium1 force-pushed the hermes/hermes-5363b8db branch 3 times, most recently from 0278dbc to 9b1375a Compare July 1, 2026 10:51
abchiaravalle and others added 2 commits July 1, 2026 03:51
…ank turn

A session interrupted by a gateway restart is flagged resume_pending and
auto-continued on startup via _schedule_resume_pending_sessions(), which
dispatches an empty-text internal MessageEvent. The recovery system note
that should fill that empty turn is gated, in _run_agent(), on
_interruption_is_fresh — the age of the LAST PERSISTED TRANSCRIPT ROW.

For an active thread returned to after >1h of silence, that transcript
clock is stale even though the interruption (last_resume_marked_at) is
seconds old. The gate evaluates False, the note is not prepended, and the
model receives a genuinely blank user turn — replying with confused
'that message came through blank' noise.

Fix (two parts, both default-on, behavior unchanged for healthy turns):

1. resume_pending freshness now also considers last_resume_marked_at (the
   restart watchdog's own stamp). The branch fires when EITHER the
   transcript clock OR the resume mark is fresh, so the startup scheduler's
   freshness decision and the per-turn injection agree.

2. Empty-turn safety net: if the user turn is still blank after all
   injections AND the session is resume_pending, backfill a recovery note
   so a blank turn can never reach the model. Scoped to resume_pending so
   ordinary empty turns (e.g. uncaptioned image) are untouched.

Adds 3 regression tests; the two core ones fail on the pre-fix logic.
…rding

Follow-up on the salvaged resume_pending fix: the empty-turn safety net
now emits the same reason-aware recovery note as the _is_resume_pending
branch (reason phrase + 'session restored' guidance + no-re-execute
instruction) instead of a second, differently-worded note. Also adds the
AUTHOR_MAP entry for the salvaged commit.
@teknium1
teknium1 force-pushed the hermes/hermes-5363b8db branch from 9b1375a to 979a343 Compare July 1, 2026 10:52
@teknium1
teknium1 enabled auto-merge (rebase) July 1, 2026 10:52
@teknium1
teknium1 merged commit 27347b2 into main Jul 1, 2026
29 checks passed
@teknium1
teknium1 deleted the hermes/hermes-5363b8db branch July 1, 2026 10:57
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 P2 Medium — degraded but workaround exists 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.

3 participants