Skip to content

fix(kanban): avoid false dispatcher stuck warnings - #65581

Open
jtstothard wants to merge 2 commits into
NousResearch:mainfrom
jtstothard:fix/dispatcher-false-stuck-warnings
Open

jtstothard wants to merge 2 commits into
NousResearch:mainfrom
jtstothard:fix/dispatcher-false-stuck-warnings

Conversation

@jtstothard

Copy link
Copy Markdown

Problem

The kanban dispatcher's health telemetry raises "stuck" warnings whenever the ready queue is non-empty and no workers spawn on a tick — even when the non-spawn is intentional and correct (concurrency caps reached, profile already at max_in_progress_per_profile, a task is locked, or respawn is guarded). This produces noisy false-positive stuck warnings that make real stalls harder to spot and erode trust in health monitoring.

What changed

  • DispatchResult gains skipped_global_capped — set when the board's global max_in_progress is already filled, mirroring the existing skipped_per_profile_capped.
  • Dispatcher health (both gateway and daemon paths) now treats intentional deferrals (global cap, per-profile cap, locked, respawn-guarded) as healthy ticks. bad_ticks only increments when spawnable work is genuinely stuck, not deliberately deferred.
  • hermes kanban dispatch CLI surfaces skipped_global_capped so operators see "Deferred (global in-progress cap reached)" instead of silent nothing.
  • hermes gateway status now scopes its running/not-running messaging to the active profile, and when the active profile is down but another profile's gateway is live, it says so explicitly ("not a system-wide outage") with a "Live gateways in other profiles" section.

Why it matters

On multi-profile hosts the gateway-status wording previously implied a system-wide outage when only one profile was idle, and the health telemetry cried "stuck" on every tick where caps correctly prevented over-spawning. Both undermine operator confidence in the very signals meant to catch real problems.

Verification

  • tests/hermes_cli/test_kanban_per_profile_cap.py — new test_global_cap_marks_dispatch_as_intentionally_deferred; all 10 tests pass
  • tests/hermes_cli/test_gateway_service.py — new test_gateway_status_promotes_live_other_profile; all 189 tests pass
  • Combined: 199 passed, 0 failed

Risks / follow-ups

  • A genuine stall that coincides with a cap/lock/respawn-guard state on the same tick will not increment bad_ticks. This is a deliberate trade (false-positive reduction) but means a real stall masked by concurrent deferral would rely on subsequent ticks where the deferral clears.

Treat concurrency caps and deliberate dispatch deferrals as healthy, and make gateway status clearly profile-scoped.

Co-Authored-By: Claude <noreply@anthropic.com>
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/gateway Gateway runner, session dispatch, delivery comp/cli CLI entry point, hermes_cli/, setup wizard comp/cron Cron scheduler and job management needs-decision Awaiting maintainer decision before any implementation labels Jul 16, 2026
@alt-glitch

Copy link
Copy Markdown

This was generated by AI during triage.

Related: #46892 covers the global-cap false-stuck case and #44899 covers respawn-guard readiness. This PR combines those areas through DispatchResult flags and also changes gateway-status wording; maintainers should choose the preferred scope.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for tracing the false-stuck telemetry path. The current-main premise is real: a full global cap returns without spawning at hermes_cli/kanban_db.py:7600-7605, while the gateway increments its aggregate streak when any board still has spawnable ready work at gateway/kanban_watchers.py:1253-1258.

Problems

  • gateway/kanban_watchers.py:1255 computes intentionally_deferred with any(...) across every board, while ready_pending is also an any-board probe. A cap, lock, or respawn guard on board A can therefore clear the aggregate streak while board B has genuinely stuck spawnable work.
  • The added flag test in tests/hermes_cli/test_kanban_per_profile_cap.py:121 does not exercise either changed health predicate.

Suggested changes

  • Make gateway health accounting board-local, so only the deferred board is exempted from a stuck warning.
  • Add a multi-board regression plus direct health-predicate coverage for gateway and the legacy daemon path.

Automated hermes-sweeper review.

Comment thread gateway/kanban_watchers.py Outdated
# Health telemetry (aggregate across boards)
ready_pending = await asyncio.to_thread(_ready_nonempty)
if ready_pending and not any_spawned:
intentionally_deferred = any(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

results spans every board, while ready_pending is also an any-board probe. A cap/lock/guard on board A therefore resets bad_ticks even if board B has spawnable work that fails every tick. Please account for health per board, or suppress only when the board containing ready work is itself intentionally deferred.

@teknium1 teknium1 added the sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform label Jul 18, 2026
Address teknium1 review on NousResearch#65581: the aggregate any()-board
intentionally_deferred check could mask genuinely stuck work on
one board when another board was capped/locked/respawn-guarded.
Scope the deferral exemption per-board so only the board with
ready work is exempted when it is itself deferred.

Add multi-board regression test and direct health-predicate
coverage for both gateway and daemon paths.
@jtstothard

Copy link
Copy Markdown
Author

Addressed in b84456b. The gateway health predicate is now board-local: ready work on a board only exempts bad_ticks when that same board is intentionally deferred, so a cap/lock/guard on board A no longer masks stuck work on board B. Added a multi-board regression test (board A deferred, board B stuck → warning still fires) plus direct health-predicate coverage for both gateway and daemon paths.

@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown

This was generated by AI during triage.

Summary

Thirteen PRs address or reference four Kanban gateway issues through distinct diffs covering dispatcher exclusivity, false stuck telemetry, auto-decompose diagnostics and secret scope, concurrency parsing, activity and notifier reporting, archived-board races, and worker-log cleanup. Material overlap is concentrated in the singleton-lock, false-stuck, max_spawn-coercion, and auto-decompose secret-scope groups.

Related pull requests

Duplicates

#41679 and #44919 overlap on #41448, with #49068 as the merged machine-global consolidation; #46892 and #65581 overlap on #46800 but contain complementary health-accounting work; #59588 overlaps #65644 on max_spawn coercion; the Kanban half of #76864 duplicates #57837.

Suggested consolidation

Author action: rebase #65581 onto main or split out a focused #46800 change that preserves its board-local gateway and daemon health accounting, incorporates #46892's externally occupied max_spawn detection per physical board, and removes the unrelated gateway-status changes; this respects both keep_open reviews and the recorded best-fix status of #46892 and #65581. Keep #49861, #57837, #62893, #64605, and #65321 open on their documented salvage paths, require #65644's finally-based exceptional-Popen cleanup before closing #59588 as duplicate of #65644, and leave #41679, #44919, and #76864 closed in favor of #49068, #57837, and #76871 respectively.

Complex graph

flowchart LR
    classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
    classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
    classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
    classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
    classDef best stroke-width:3px,stroke:#b45309
    classDef target stroke-width:3px,stroke:#4338ca
    I46800(["issue #46800 (open)"])
    subgraph Dup46892 ["PRs duplicating each other"]
        P46892["PR #46892 (open)"]
        P65581["PR #65581 (open)"]
    end
    P65581 -->|best fix| I46800
    class I46800 open
    class P46892 open
    class P65581 open
    class P46892 best
    class P65581 best
    class P65581 target
    click I46800 "https://github.com/NousResearch/hermes-agent/issues/46800"
    click P46892 "https://github.com/NousResearch/hermes-agent/pull/46892"
    click P65581 "https://github.com/NousResearch/hermes-agent/pull/65581"
Loading

Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label).

Cross-PR triage: Reviewed 13 pull requests and 4 issues in this complex. Each diff was read against this issue; Assessment working set: 142 kB of PR diffs, 45 kB of issue/PR text, 29 kB of discussion (31 comments), 11 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/cron Cron scheduler and job management comp/gateway Gateway runner, session dispatch, delivery needs-decision Awaiting maintainer decision before any implementation P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants