Skip to content

fix(gateway): tag remaining permanent loops out of the scale-to-zero busy check - #90594

Closed
benbarclay wants to merge 4 commits into
mainfrom
fix/scale-to-zero-permanent-loop-tags
Closed

fix(gateway): tag remaining permanent loops out of the scale-to-zero busy check#90594
benbarclay wants to merge 4 commits into
mainfrom
fix/scale-to-zero-permanent-loop-tags

Conversation

@benbarclay

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #84327 from the first live E2E run on staging (hermes-agent-stg-test-6698, 2026-08-20, post-#84339 image): the instance armed at 05:56:56, went fully idle (only cron fires + housekeeping), and still never logged going dormant after 25+ minutes.

Root cause

#84327 excluded _spawn_supervised watchers from _scale_to_zero_has_live_background_work() — but two permanent loops are started directly with asyncio.create_task, parked in _background_tasks, and never tagged:

  • the loop-liveness heartbeat (_loop_heartbeat_task, started in run() on every boot — confirmed alive on the staging box: state/gateway.heartbeat refreshing every tick), and
  • the gateway-wide heartbeat poller (_start_heartbeat_poller, started once any /heartbeat is registered).

The loop-liveness task alone is enough to hold the busy check True for the whole process life on every gateway, hosted or not.

Fix

Tag both call sites with _hermes_supervised_watcher = True (the same tag _spawn_supervised applies). No behavioral change to either loop; transient tasks in _background_tasks still block suspend. Audited the remaining _background_tasks.add sites: startup-resume events, plugin injection futures, completion-batch flushes, and supervised respawn helpers are all transient + self-discarding — correctly counted.

Tests

  • test_heartbeat_poller_does_not_block_idle — exercises the real _start_heartbeat_poller call site; fails on main (verified: 1 failed / 16 passed with the fix stashed), passes with the fix.
  • test_loop_heartbeat_shape_is_excluded_when_tagged — the tagged-forever-task contract.
  • ./scripts/run_tests.sh tests/gateway/test_scale_to_zero_watcher.py17 passed, 0 failed; ruff clean.

Verification plan

Same staging E2E, after image roll: going dormant ... then self-suspending + suspend accepted by flaps in agent.log within ~5–6 min of idle, suspension/suspended (flyd) in Fly events, Chronos cold-fire resume. (The instance's recurring 15-min cron leaves a ~9-min idle window — enough for the 5-min timeout.)

Refs: #84295, #84327, #84339, NousResearch/nous-account-service#898.

Infographic

still-awake

…busy check

First live E2E after the supervised-watcher exclusion STILL never went
dormant on staging: the loop-liveness heartbeat task is started directly
in run() (not via _spawn_supervised), so it sits untagged in
_background_tasks for the whole process life and holds
_scale_to_zero_has_live_background_work() True forever. The gateway-wide
heartbeat poller (_start_heartbeat_poller) is the same shape once any
/heartbeat is registered.

Tag both permanent loops with _hermes_supervised_watcher at their call
sites. Transient tasks still block suspend. The heartbeat-poller test
fails without the fix (exercises the real _start_heartbeat_poller call
site, not a stubbed set).
@benbarclay benbarclay closed this Aug 20, 2026
@benbarclay benbarclay reopened this Aug 20, 2026
@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 duplicate This issue or pull request already exists labels Aug 20, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Duplicate of #84558 — same two permanent heartbeat call sites, watcher tag mechanism, and scale-to-zero regression coverage.

@benbarclay

Copy link
Copy Markdown
Collaborator Author

Closing as a duplicate of #84558, which landed the same fix 8 days earlier (pierrenode) and is strictly better — its _start_loop_heartbeat_task() extraction makes the loop-heartbeat regression test exercise the real production entry point, which this PR's shape-level test did not. The live staging E2E evidence from this PR's investigation has been added to #84558 as confirmation. Credit to @pierrenode for the original diagnosis and fix.

@benbarclay benbarclay closed this Aug 20, 2026
@benbarclay
benbarclay deleted the fix/scale-to-zero-permanent-loop-tags branch August 20, 2026 10:19
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 duplicate This issue or pull request already exists P2 Medium — degraded but workaround exists 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.

2 participants