Skip to content

feat(task-dispatcher): idle-backoff / self-suspend the board poller (#4090)#4481

Merged
senamakel merged 2 commits into
tinyhumansai:mainfrom
senamakel:fix/GH-4090-poller-idle-backoff
Jul 4, 2026
Merged

feat(task-dispatcher): idle-backoff / self-suspend the board poller (#4090)#4481
senamakel merged 2 commits into
tinyhumansai:mainfrom
senamakel:fix/GH-4090-poller-idle-backoff

Conversation

@senamakel

Copy link
Copy Markdown
Member

Summary

  • Give the board poller a diminishing-returns backoff: after a short grace window of idle ticks the sweep interval doubles up to a 15-minute ceiling (an effective self-suspend), and any dispatch resets it to the 60s base cadence.
  • Thread a "did-work" signal (dispatched a card vs idle) up from poll_board / poll_once to drive it.
  • next_poll_delay(idle_ticks) is a pure, unit-tested backoff curve.
  • (CI) Also adds config_*_privacy_mode to a stale schema-catalog golden — see ## Related.

Problem

Issue #4090: long-running monitor/poll loops don't self-suspend on diminishing returns — they keep working every tick even when nothing changes. The concrete in-repo loop is the board poller (task_dispatcher/poller.rs), which swept every board on a fixed 60s interval forever, whether or not there was anything to dispatch.

Solution

  • poll_board / poll_once now return true when a card was dispatched (real work) and false when the board was idle (nothing to claim / no capacity).
  • The spawn loop tracks an idle_ticks counter and sleeps next_poll_delay(idle_ticks) before each sweep: base cadence within a small grace window, then exponential backoff (double per extra idle tick) saturating at a 15-minute ceiling. A dispatch resets idle_ticks to 0 → immediate return to the 60s cadence.
  • The capped ceiling still rechecks periodically, so newly-arrived work is picked up within the ceiling without an unbounded stall (an event-driven instant wake on board mutation is noted as a follow-up).

next_poll_delay is pure and deterministic (no timer), so the whole backoff curve is unit-tested: base within the grace window, exponential past it, monotonic non-decreasing, overflow-safe, and saturating at the ceiling.

Submission Checklist

  • Tests added or updated (happy path + edge) — next_poll_delay_holds_base_cadence_within_the_grace_window, next_poll_delay_backs_off_exponentially_past_the_grace_window, next_poll_delay_saturates_at_the_ceiling (the edge: overflow/u32::MAX and the monotonic-≤-ceiling invariant).
  • Diff coverage ≥ 80% — the backoff curve is fully covered; the did-work threading is a small return-value change on already-covered sweep paths.
  • Coverage matrix updated — N/A: behaviour change to an existing internal loop.
  • All affected feature IDs listed under ## Related — N/A.
  • No new external network dependencies introduced.
  • Manual smoke checklist updated if this touches release-cut surfaces — N/A: background poller cadence.
  • Linked issue closed via Closes #NNN.

Impact

  • Desktop/CLI background board poller only. An idle board is no longer swept every 60s indefinitely; it backs off to a 15-minute recheck and snaps back to 60s the moment it dispatches work. No change to what gets dispatched or to the single-in-progress / agent-assigned guards.

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: fix/GH-4090-poller-idle-backoff
  • Commit SHA: see head

Validation Run

  • pnpm --filter openhuman-app format:check — N/A (no frontend changes)
  • pnpm typecheck — N/A (no TS changes)
  • Focused tests: openhuman::agent::task_dispatcher::poller::tests (the three next_poll_delay cases) — execution deferred to CI (shared build host)
  • Rust fmt/check (if changed): rustfmt --edition 2021 --check clean; cargo check --bin openhuman-core passed
  • Tauri fmt/check (if changed): N/A

Validation Blocked

  • command: cargo test --lib -- openhuman::agent::task_dispatcher::poller::tests
  • error: none — shared build host saturated
  • impact: cargo check compiled clean; the backoff curve is a pure deterministic function validated by CI

Behavior Changes

  • Intended behavior change: the board poller backs off on consecutive idle ticks (up to a 15-min ceiling) and resets to base cadence on any dispatch.
  • User-visible effect: none directly; less redundant background work when the board is idle.

Parity Contract

  • Legacy behavior preserved: the sweep logic (stale reclaim, single-in-progress cap, agent-assigned guard, urgency ordering) is unchanged; the loop still skips the immediate startup fire (it sleeps before the first poll). Only the inter-tick interval adapts.
  • Guard/fallback/dispatch parity checks: an errored tick counts as idle for backoff (a persistently failing sweep won't spin at base cadence).

Duplicate / Superseded PR Handling

  • Duplicate PR(s): none
  • Canonical PR: this one

senamakel added 2 commits July 3, 2026 23:51
…inyhumansai#4090)

The board poller swept every board on a fixed 60s interval forever, even
when the board was idle tick after tick — the diminishing-returns loop
issue tinyhumansai#4090 describes. Thread a "did-work" signal up from `poll_board` /
`poll_once` (true = dispatched a card) and drive an exponential backoff:
after a short grace window of idle ticks the interval doubles up to a
15-minute ceiling (an effective self-suspend), and any dispatch resets it
to the 60s base cadence. The capped ceiling still rechecks periodically so
newly-arrived work is picked up without an unbounded stall.

`next_poll_delay(idle_ticks)` is a pure, unit-tested backoff curve
(base within the grace window; exponential past it; saturating at the
ceiling, monotonic, overflow-safe). An event-driven instant wake on
board mutation is a follow-up.

Closes tinyhumansai#4090.

Claude-Session: https://claude.ai/code/session_01KcmdqJVpjmnH31HqTHRLwG
@senamakel senamakel requested a review from a team July 4, 2026 00:10
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 49 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6521a055-ad8e-4288-b423-13978c624308

📥 Commits

Reviewing files that changed from the base of the PR and between e6bb4bf and d8b9524.

📒 Files selected for processing (2)
  • src/openhuman/agent/task_dispatcher/poller.rs
  • tests/config_auth_app_state_connectivity_e2e.rs

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d8b95246c8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

.any(|c| c.status == TaskCardStatus::InProgress)
{
return Ok(());
return Ok(false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep polling at base cadence while a card is in progress

When a board has an InProgress card, this now reports an idle tick to the outer backoff loop. Since task completion only writes the card status back and there is no event-driven wake for the poller, a long-running task can push idle_ticks to the 15-minute cap; if other todo/ready cards are queued, and the running card finishes just after a sweep, the next card may not dispatch for up to 15 minutes instead of the previous 60 seconds. Treat an in-progress board as busy/non-idle or keep a shorter cadence while it can unblock queued work.

Useful? React with 👍 / 👎.

@senamakel senamakel merged commit 3791f30 into tinyhumansai:main Jul 4, 2026
13 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Harness] Self-suspend long-running monitor/poll loops on diminishing returns

1 participant