Skip to content

fix(gateway): gateway sessions lack activity watchdog - agent loop stalls silently with no detection and notification - #73031

Closed
fangliquanflq wants to merge 9 commits into
NousResearch:mainfrom
fangliquanflq:fix/72016-session-activity-watchdog
Closed

fix(gateway): gateway sessions lack activity watchdog - agent loop stalls silently with no detection and notification#73031
fangliquanflq wants to merge 9 commits into
NousResearch:mainfrom
fangliquanflq:fix/72016-session-activity-watchdog

Conversation

@fangliquanflq

@fangliquanflq fangliquanflq commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Re-lands the session activity watchdog work after #72858 reverted #72817 (salvage of #72424).

Maintainer note on the revert: the original work is solid; the revert was a process correction because #72817 landed before a full review. This PR restores the same change set and is intended to go through the normal review cycle before merge.

Cherry-picks 3 commits from #72817 (skips the redundant noreply contributor mapping; fangliquan@oppo.com is already mapped):

  1. feat: session activity heartbeats, stall notify, compress_context timeout
  2. refactor: reuse _relative_time, hygiene provenance helper, record_timeout_failure()
  3. fix: propagate logging session context after daemon-pool compress_context

Behavior (unchanged from #72817 / #72424):

  1. Mid-turn activity heartbeats to SessionDB so hermes sessions list / hermes status show progress during long turns
  2. Stall watchdog: busy session + pending inbound + idle past agent.session_stall_timeout (default 300) -> WARNING + one-shot /new notify (notify-only)
  3. Progress-aware compaction timeout for fenceless compress_context callers (compression.context_timeout_seconds / compression.context_total_ceiling_seconds)

Does not include #72016 item 4 (cumulative SSE stream-retry deadline); that remains a follow-up.

Related Issue

Related to #72016 (closed when #72817 merged; code was later reverted by #72858)
Supersedes / re-opens the review path for #72424 and #72817

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)

Changes Made

How to Test

  1. Activity: during a long gateway turn, confirm hermes sessions list / hermes status advance last_active without a new message row
  2. Stall notify: busy stalled turn with queued follow-up -> WARNING + one /new notification; no spam on later ticks
  3. Compaction: hung/silent auxiliary model during auto-compress -> warning, no message drop, turn continues
  4. Automated:
scripts/run_tests.sh \
  tests/agent/test_compress_context_progress_timeout.py \
  tests/agent/test_compression_concurrent_fork.py \
  tests/agent/test_session_activity.py \
  tests/run_agent/test_session_activity_persist.py \
  tests/gateway/test_session_stall_watchdog.py \
  tests/gateway/test_config_env_bridge_authority.py \
  tests/hermes_cli/test_status.py \
  tests/test_hermes_state.py \
  tests/run_agent/test_compression_boundary_hook.py \
  -q

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature
  • I've run scripts/run_tests.sh on relevant tests and they pass
  • I've added tests for my changes
  • I've tested on my platform: Windows 10/11 (WSL test runner)

Documentation & Housekeeping

  • I've updated relevant documentation
  • I've updated cli-config.yaml.example if I added/changed config keys
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows - N/A
  • I've considered cross-platform impact
  • I've updated tool descriptions/schemas if I changed tool behavior - N/A

@fangliquanflq
fangliquanflq force-pushed the fix/72016-session-activity-watchdog branch from 207a69b to 85c763a Compare July 28, 2026 01:45
@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery area/config Config system, migrations, profiles P1 High — major feature broken, no workaround sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Jul 28, 2026
@fangliquanflq

Copy link
Copy Markdown
Contributor Author

@kshitijk4poor thanks again for the salvage/refactor work on the #72817 re-land path.

Could you take another look when you have a chance? A few follow-ups landed on top of the restored watchdog set:

  1. Silence detached compression heartbeat after host timeout, so a timed-out daemon compress_context cannot re-arm agent.compression / poison stall clocks.
  2. Force-persist compression completed past the SessionDB 60s rate limit, so /compress does not leave durable activity stuck on "in progress".

Happy to address any further review notes.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for restoring the watchdog and activity work. The current-main premise remains valid: active sessions queue follow-ups in gateway/platforms/base.py:5712-5724, while current startup has no session-stall watcher (gateway/run.py:10991-11073).

Problems

  • agent/conversation_compression.py:458-464 does not honor the advertised total compression ceiling after begin_commit(): it spins until the fence lock is available and then calls unbounded future.result(). The commit fence covers SessionDB mutation such as archive_and_compact() at agent/conversation_compression.py:2349 and publish_compression_child() at line 2412, so a hang there can still stall the turn indefinitely. The new test explicitly accepts an over-ceiling commit wait at tests/agent/test_compress_context_progress_timeout.py:125-149.

Suggested changes

  • Bound or otherwise make the commit phase recoverable, or narrow the documented/configured timeout guarantee and add a test for a commit-phase hang.

This is an automated hermes-sweeper review.

Comment thread agent/conversation_compression.py
@teknium1 teknium1 added sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit area/sessions Session lifecycle, resume, persistence, history labels Jul 30, 2026
fangliquanflq and others added 7 commits August 1, 2026 00:34
…out (NousResearch#72424)

Three mechanisms to detect and notify when gateway sessions stall silently:

1. Mid-turn activity heartbeats stamped to SessionDB so hermes sessions list
   and hermes status show progress during long turns without new message rows.

2. Stall watchdog: when a busy session has pending inbound and the shared
   activity clock is idle past agent.session_stall_timeout (default 300),
   log a WARNING and notify the user once to try /new. Notify-only; does
   not kill the turn.

3. Compaction timeout: fenceless compress_context callers get a progress-aware
   host budget (compression.context_timeout_seconds default 120 idle,
   compression.context_total_ceiling_seconds default 600 ceiling). On timeout,
   cancel via commit fence, skip compaction without dropping messages, and
   continue the turn.

Closes NousResearch#72016 (slices 1-3; slice 4 cumulative SSE stream-retry deadline
remains a follow-up).

Cherry-picked from PR NousResearch#72424 by @fangliquanflq.
Three code-reuse fixes applied during salvage:

1. Reuse _relative_time from hermes_cli/main.py instead of duplicating
   the relative-time formatting logic in hermes_cli/status.py.

2. Extract _stamp_hygiene_compression_provenance helper in gateway/run.py
   to deduplicate the two nearly-identical try/except blocks that stamp
   compression timeout/abort provenance in the hygiene path.

3. Add ContextCompressor.record_timeout_failure() method and use it from
   the in-agent compress_context timeout callback instead of re-implementing
   the (60, 300, 900) cooldown ladder inline. The existing summary-LLM
   exception handler already has this ladder — now both paths share one
   method.
…text

compress_context now runs on a daemon pool worker thread (via
run_compress_context_with_progress_timeout). The session id rotation
updates hermes_logging._session_context (a threading.local) on the
WORKER thread, not the caller thread. After the wrapper returns,
propagate self.session_id back to the caller's logging context so
subsequent log lines carry the rotated id (NousResearch#34089).

Fixes CI failure in test_compression_logging_session_context.
Host progress timeout leaves compress_context running on a daemon worker while
the live turn continues. Latch heartbeat silence on fence cancel or terminal
timeout/cooldown provenance so a later UNKNOWN stamp cannot re-arm
agent.compression and poison stall clocks.
Once begin_commit() wins, SessionDB mutation cannot be fence-cancelled;
document that context_total_ceiling_seconds covers the summary phase only
and pin the hang-wait contract in tests.
@fangliquanflq
fangliquanflq force-pushed the fix/72016-session-activity-watchdog branch from f82e688 to 7571298 Compare July 31, 2026 16:45
Keep relay turn teardown when clearing activity labels after a turn exits.
Resolve gateway/run.py conflicts: keep session stall watchdog and
hygiene activity provenance stamps; use DB-backed hygiene cooldown
from main (NousResearch#74136).
teknium1 added a commit that referenced this pull request Aug 1, 2026
…bility probe

The stubbed-compressor fixture still let the one-time compression-model
feasibility probe run inside the first _compress_context call. On machines
with real credentials configured, that probe resolves a live auxiliary
provider (credential pool seeding, Copilot token exchange over HTTPS),
which nondeterministically exceeds the 2s event-timing budget in
test_fence_cancelled_compression_leaves_lock_reacquirable (reproduced on
PR #73031's own head). Mark the probe done in the shared fixture: these
tests exercise locking/fencing/rotation, never aux feasibility. Suite
runtime drops from ~90s to ~4s.
@kshitijk4poor

Copy link
Copy Markdown
Collaborator

Closing as superseded by #76354@teknium1's clean re-land of the session activity watchdog onto current main, with the commit-phase ceiling fix and heartbeat write discipline composed with recent SessionDB merges. Your original work was solid (Teknium noted the revert was a process correction); #76354 carries it forward. Thanks!

teknium1 added a commit that referenced this pull request Aug 2, 2026
…bility probe

The stubbed-compressor fixture still let the one-time compression-model
feasibility probe run inside the first _compress_context call. On machines
with real credentials configured, that probe resolves a live auxiliary
provider (credential pool seeding, Copilot token exchange over HTTPS),
which nondeterministically exceeds the 2s event-timing budget in
test_fence_cancelled_compression_leaves_lock_reacquirable (reproduced on
PR #73031's own head). Mark the probe done in the shared fixture: these
tests exercise locking/fencing/rotation, never aux feasibility. Suite
runtime drops from ~90s to ~4s.
teknium1 added a commit that referenced this pull request Aug 2, 2026
…bility probe

The stubbed-compressor fixture still let the one-time compression-model
feasibility probe run inside the first _compress_context call. On machines
with real credentials configured, that probe resolves a live auxiliary
provider (credential pool seeding, Copilot token exchange over HTTPS),
which nondeterministically exceeds the 2s event-timing budget in
test_fence_cancelled_compression_leaves_lock_reacquirable (reproduced on
PR #73031's own head). Mark the probe done in the shared fixture: these
tests exercise locking/fencing/rotation, never aux feasibility. Suite
runtime drops from ~90s to ~4s.
@teknium1

teknium1 commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Superseded by PR #76354 (#76354), now merged — your full commit sequence was cherry-picked as its base with your authorship preserved via rebase-merge (the #72858 revert was process-only, and your work survived it intact).

On top of your base, the merged PR carries two adversarial review rounds' worth of hardening (bounded commit-phase waits, worker transcript isolation, holder-qualified lease cancellation, bounded pool admission, watchdog send bounds) plus live-test verification of the heartbeat cadence, stall notify, and compression timeout behavior.

Thanks for building the watchdog and for persisting through the revert — the activity/heartbeat design was validated as-is by both reviewers. Closing this re-land as superseded.

randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…bility probe

The stubbed-compressor fixture still let the one-time compression-model
feasibility probe run inside the first _compress_context call. On machines
with real credentials configured, that probe resolves a live auxiliary
provider (credential pool seeding, Copilot token exchange over HTTPS),
which nondeterministically exceeds the 2s event-timing budget in
test_fence_cancelled_compression_leaves_lock_reacquirable (reproduced on
PR NousResearch#73031's own head). Mark the probe done in the shared fixture: these
tests exercise locking/fencing/rotation, never aux feasibility. Suite
runtime drops from ~90s to ~4s.
33hodl pushed a commit to 33hodl/hermes-agent that referenced this pull request Aug 12, 2026
…bility probe

The stubbed-compressor fixture still let the one-time compression-model
feasibility probe run inside the first _compress_context call. On machines
with real credentials configured, that probe resolves a live auxiliary
provider (credential pool seeding, Copilot token exchange over HTTPS),
which nondeterministically exceeds the 2s event-timing budget in
test_fence_cancelled_compression_leaves_lock_reacquirable (reproduced on
PR NousResearch#73031's own head). Mark the probe done in the shared fixture: these
tests exercise locking/fencing/rotation, never aux feasibility. Suite
runtime drops from ~90s to ~4s.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles area/sessions Session lifecycle, resume, persistence, history comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery P1 High — major feature broken, no workaround sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages 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.

4 participants