refactor(coding-agent): derive scoped heartbeats at render time - #1857
Merged
Conversation
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.
xeophon
approved these changes
Aug 29, 2026
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was wrong
The TUI stored a scoped
heartbeatslist — a projection of the heartbeat catalog x connection state x subagent snapshots — kept aligned byupdateScopedHeartbeats()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
heartbeatslist andupdateScopedHeartbeats()/setHeartbeats()push path. The tray, refresh scheduler, and heartbeat overlay now callgetScopedHeartbeats()(catalog + connection + subagents) or agetHeartbeatscallback when they need the list.HeartbeatManagerComponentno 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 duringrender.scheduleHeartbeatManagerRefreshkeeps an earlier armed timer when subagent churn would otherwise postpone an overdue 5s poll, and can move the deadline earlier when a soonernextRunAtappears; timer teardown is centralized inclearHeartbeatManagerRefreshTimer.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
HeartbeatManagerComponentHeartbeatManagerComponentnow uses aHeartbeatManagerOptions.getHeartbeatscallback to read the current list during render.job.idviaselectedHeartbeatIdto keep it stable across list re-sorting and updates. The view automatically resets to list mode when the selected heartbeat disappears.InteractiveMode.scheduleHeartbeatManagerRefreshto keep an existing earlier deadline instead of postponing it during frequent subagent updates, and re-arms earlier when a sooner heartbeat appears.HeartbeatManagerComponent.setHeartbeatsandInteractiveMode.updateScopedHeartbeats. The constructor forHeartbeatManagerComponentno longer accepts an initial heartbeats array, requiring callers to provide thegetHeartbeatscallback.Macroscope summarized 1e3cce0.