Skip to content

refactor(coding-agent): derive scoped heartbeats at render time - #1857

Merged
xeophon merged 3 commits into
mainfrom
snimu/derive-scoped-heartbeats
Aug 29, 2026
Merged

refactor(coding-agent): derive scoped heartbeats at render time#1857
xeophon merged 3 commits into
mainfrom
snimu/derive-scoped-heartbeats

Conversation

@snimu

@snimu snimu commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

What was wrong

The TUI stored a scoped heartbeats list — a projection of the heartbeat catalog x connection state x subagent snapshots — kept aligned by updateScopedHeartbeats() calls scattered across unrelated code paths. A missed call site meant stale heartbeat counts or manager rows (audit: dup-truth.md finding 5; TUI-side sibling of #1743).

The fix

The stored projection, its updater, and setHeartbeats() are deleted. The tray label, refresh scheduler, and heartbeat manager derive the scoped list from the three sources at use/render time; the manager holds only selection state by heartbeat ID (re-anchoring gracefully when the selected entry disappears). Production code is net deletion; stale test stubs of the deleted method removed.

How it's verified

Reviewer accounted for all 4 former sync call sites, bounded the per-render derivation cost (tiny, overlay-scoped), traced selection survival through catalog refreshes, and confirmed the getter wiring holds no leak. Focused suites 190/190; full CI-style failing set exactly matches the stack base. Two-model implement/review loop.

Stacked on #1748 (test the whole stack at the leaf; merge base-first).

Note: intentionally no Linear ticket for this cleanup stack, so that check stays red.


Note

Low Risk
Interactive TUI refactor with no auth or persistence changes; main risk is stale or wrong heartbeat UI if the getter or selection re-anchoring regresses.

Overview
Removes the cached scoped heartbeats list and updateScopedHeartbeats() / setHeartbeats() push path. The tray, refresh scheduler, and heartbeat overlay now call getScopedHeartbeats() (catalog + connection + subagents) or a getHeartbeats callback when they need the list.

HeartbeatManagerComponent no longer stores the array: it sorts on read, tracks selection by job id (with reset when scope drops the row), and reconciles list vs actions mode during render.

scheduleHeartbeatManagerRefresh keeps an earlier armed timer when subagent churn would otherwise postpone an overdue 5s poll, and can move the deadline earlier when a sooner nextRunAt appears; timer teardown is centralized in clearHeartbeatManagerRefreshTimer.

Tests cover live list updates on render and the new scheduling behavior.

Reviewed by Cursor Bugbot for commit 1e3cce0. Bugbot is set up for automated code reviews on this repo. Configure here.

Linear ticket: ENG-5665
(ticket linked above)

Note

Derive scoped heartbeats at render time in HeartbeatManagerComponent

  • Moves heartbeat scoping from a cached array to an on-demand fetch. HeartbeatManagerComponent now uses a HeartbeatManagerOptions.getHeartbeats callback to read the current list during render.
  • Tracks selection by job.id via selectedHeartbeatId to keep it stable across list re-sorting and updates. The view automatically resets to list mode when the selected heartbeat disappears.
  • Reworks the refresh timer in InteractiveMode.scheduleHeartbeatManagerRefresh to keep an existing earlier deadline instead of postponing it during frequent subagent updates, and re-arms earlier when a sooner heartbeat appears.
  • Behavioral Change: Removes HeartbeatManagerComponent.setHeartbeats and InteractiveMode.updateScopedHeartbeats. The constructor for HeartbeatManagerComponent no longer accepts an initial heartbeats array, requiring callers to provide the getHeartbeats callback.

Macroscope summarized 1e3cce0.

snimu added 3 commits August 27, 2026 14:04
scheduleHeartbeatManagerRefresh now runs on every subagent snapshot
update, and each call cleared and re-armed the refresh timer. An overdue
heartbeat uses a relative 5s fallback delay, so children updating more
often than every 5s postponed the open manager's refresh indefinitely.
Track the scheduled deadline and keep the pending timer unless the newly
derived deadline is earlier.
…er deadline

The keep-timer-always mutant (returning whenever a timer is armed)
survived the touched suites: nothing proved a sooner heartbeat pulls the
pending refresh forward past the capped 60s poll.
@snimu
snimu requested a review from xeophon August 29, 2026 08:22
@xeophon
xeophon merged commit 85c236d into main Aug 29, 2026
23 checks passed
@xeophon
xeophon deleted the snimu/derive-scoped-heartbeats branch August 29, 2026 08:54
PR9000 pushed a commit to PR9000/prime-agent that referenced this pull request Sep 1, 2026
…eIntellect-ai#1857)

* refactor(coding-agent): derive scoped heartbeats at render time

* fix(coding-agent): keep the earliest heartbeat manager refresh deadline

scheduleHeartbeatManagerRefresh now runs on every subagent snapshot
update, and each call cleared and re-armed the refresh timer. An overdue
heartbeat uses a relative 5s fallback delay, so children updating more
often than every 5s postponed the open manager's refresh indefinitely.
Track the scheduled deadline and keep the pending timer unless the newly
derived deadline is earlier.

* test(coding-agent): cover re-arming the heartbeat refresh to an earlier deadline

The keep-timer-always mutant (returning whenever a timer is armed)
survived the touched suites: nothing proved a sooner heartbeat pulls the
pending refresh forward past the capped 60s poll.
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.

2 participants