Skip to content

fix(gateway): notify resets finalized through the recovery fence - #89324

Open
liuhao1024 wants to merge 1 commit into
NousResearch:mainfrom
liuhao1024:liuhao/cron-bugfix-89314
Open

fix(gateway): notify resets finalized through the recovery fence#89324
liuhao1024 wants to merge 1 commit into
NousResearch:mainfrom
liuhao1024:liuhao/cron-bugfix-89314

Conversation

@liuhao1024

Copy link
Copy Markdown
Contributor

What does this PR do?

The "◐ Session automatically reset …" notice is delivered on the next inbound message after the reset, keyed off was_auto_reset on the routing entry. That works on the live-entry paths (stale routing drop and clean policy reset), but not when the reset was finalized through the recovery fence:

  1. The expiry watcher ends the session in state.db (end_reason=session_reset).
  2. By the time the next message arrives, the sessions.json routing entry is missing or stale — recovery runs instead.
  3. find_latest_gateway_session_for_peer returns None: the session_reset boundary fences recovery (Gateway session recovery resurrects ancient never-ended sessions after session_reset — nightly reset silently undone, stale context resurfaces #68539), by design.
  4. The fresh-session path creates the new session with was_auto_reset=False — the notice is silently dropped, and the user's next reply lands in an unexplained empty conversation.

This PR closes that gap: when recovery comes back empty, it looks up the peer's latest intentional reset boundary (find_latest_reset_boundary_for_peer). A session_reset boundary flags the fresh session as auto-reset (reason derived from the policy mode), carrying prev_session_id and reset_had_activity like the live-entry paths — so the existing notice machinery in the gateway fires unchanged.

Scoping guarantees:

Related open PRs in the same function are complementary, not overlapping: #59597 flags was_auto_reset when a stale routing entry is dropped (its trigger requires a dropped entry), and #66264 evaluates the reset policy on successfully recovered rows. Neither covers the entry-missing + fence-blocked shape this PR fixes.

Related Issue

Fixes #89314

Type of Change

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

Changes Made

  • hermes_state.py — new find_latest_reset_boundary_for_peer(session_key, source): the peer's most recent row with an end_reason in _RESET_END_REASONS, ordered by ended_at; returns id/end_reason/ended_at/last_activity/message_count.
  • gateway/session.py — in get_or_create_session's Phase 3, when recovery returned nothing and nothing set was_auto_reset, consult the boundary; a session_reset boundary under a non-none policy sets was_auto_reset, auto_reset_reason (idle/daily per mode), reset_had_activity (boundary row's message count), and prev_session_id.
  • tests/gateway/test_session_store_runtime_stale_guard.pyTestResetBoundaryNotice: fenced session_reset boundary flags the fresh session (fails on main with was_auto_reset=False); a new_command boundary never flags; mode=none never flags.

How to Test

python -m pytest tests/gateway/test_session_store_runtime_stale_guard.py -q
# Observed result: 13 passed

python -m pytest tests/gateway/test_async_session_store.py tests/gateway/test_session_store_expiry_finalized.py tests/gateway/test_session_store_lock_io.py -q
# Observed result: 10 passed

python -m pytest tests/test_hermes_state.py -q -k "peer or recover or reset"
# Observed result: 11 passed

Fail-on-main check: with the two source files stashed, test_fenced_reset_boundary_flags_auto_reset_on_fresh_session fails on main (was_auto_reset stays False); the two scoping pins pass on both.

Manual repro from the issue: with session_reset.mode: idle, let the watcher finalize the session (state.db row gets end_reason=session_reset), remove/stale the routing entry (or restart losing the mapping), then message the chat — on main the reply arrives with no notice; with this change the "◐ Session automatically reset (inactive for …)" notice is delivered.

Checklist

  • Code follows the project's style guidelines
  • Self-review completed
  • Comments explain intent (why the fence shape needs its own flag, why a manual /reset can't false-positive)
  • Corresponding changes documented via comments
  • Tests added and passing (3 new; stale-guard and session-store suites green)
  • Single root cause, no unrelated changes

The session-reset expiry notice is delivered on the next inbound
message, keyed off was_auto_reset on the routing entry. When the
expiry watcher finalized the session in state.db and the sessions.json
entry is missing or stale by the time that message arrives, recovery
returns None — the session_reset boundary fences it (NousResearch#68539) — and the
fresh-session path created the new session with was_auto_reset=False,
silently dropping the notice the live-entry path delivers.

When recovery comes back empty, look up the peer's latest reset
boundary; a session_reset boundary flags the fresh session as
auto-reset (reason per policy, mode:none still opts out). A manual
/reset cannot be confused with this: it mints a fresh live row that
recovery would have found, so an unrecoverable session_reset boundary
means the reset was never followed by a new conversation yet.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery area/sessions Session lifecycle, resume, persistence, history sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/sessions Session lifecycle, resume, persistence, history 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.

[Bug]: session_reset auto-reset notification is skipped when the reset surfaces via the stale sessions.json recovery path (#54878)

2 participants