feat(web): one cross-session "Needs you" queue - #330
Merged
Conversation
P5.3 (docs/conductor-frontends-design.md §8). Every blocking event across every agent in one ranked list, with the count always visible in the status bar and the queue one click away. The problem it removes: today you find the agent that stopped by opening sessions until you hit it. Three unrelated things block — a session wedged on a tool approval, a provider dialog waiting on an answer, a dispatch task that hit the anti-spin limit — and none of them announces itself anywhere shared. Collection and ranking are pure (`lib/attention.ts`); `state/attention.ts` only joins the three sources; the component is the surface. **Ranked by §8's (blocking-cost × staleness).** A `question` or `approval` has stopped a LIVE agent mid-turn, so it outranks a `blocked` task, which is stopped but burning nothing. `failed` ranks lowest — it is the one kind that can still resolve without you, since it retries with backoff. Staleness then overtakes kind as things sit, which is what makes "an agent stuck 8 minutes on a yes/no floats to the top" true. An age FLOOR is what makes that work at both ends. Without it every fresh item scores zero regardless of kind, so the queue would rank purely by age exactly when a live wedged agent most needs to be first. Three correctness details worth naming: - **One stoppage is one row.** A session showing a provider dialog is usually ALSO `waiting_approval`; counting both would tell you two things need you when one does, in a queue whose entire job is that count. - **An unknown age reads as brand new, never ancient**, so something with no usable timestamp cannot jump the queue. Future timestamps clamp to zero age too — clock skew between daemon and browser is normal and must not bury a real interruption. - **Ties break totally and stably** (since, then key). A queue that reshuffles equal rows between renders is one you cannot click. Timestamps are approximate and the code says so: neither a ui_request nor a FleetTaskWire carries the moment it began waiting, so a session falls back to `lastActivityAt` (a wedged session stops producing activity the instant it wedges) and a task to its creation time. Both err toward looking OLDER than reality, which biases toward surfacing rather than hiding — the right direction here. The badge shows zero rather than disappearing: a badge that vanishes cannot be trusted as an at-a-glance "nothing is stuck" signal, because you cannot tell clear from broken. Verified against a real board, not just fixtures: the two tasks blocked by the old daemon-collision bug outrank a 3-minute-old wedged agent — staleness overtaking kind weight — and the finished tasks stay out. 14 attention tests; 243 web tests; typecheck, lint and build clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
rsharath
approved these changes
Sep 9, 2026
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.
P5.3 (conductor-frontends-design.md §8). Every blocking event across every agent in one ranked list, with the count always visible in the status bar and the queue one click away.
The problem it removes
Today you find the agent that stopped by opening sessions until you hit it. Three unrelated things block — a session wedged on a tool approval, a provider dialog waiting on an answer, a dispatch task that hit the anti-spin limit — and none of them announces itself anywhere shared.
Ranking — §8's (blocking-cost × staleness)
A
questionorapprovalhas stopped a live agent mid-turn, so it outranks ablockedtask, which is stopped but burning nothing.failedranks lowest — it's the one kind that can still resolve without you, since it retries with backoff (§6).Staleness then overtakes kind as things sit, which is what makes §8's worked example true: "an agent stuck 8 minutes on a yes/no floats to the top."
An age floor is what makes that work at both ends. Without it every fresh item scores zero regardless of kind, so the queue would rank purely by age exactly when a live wedged agent most needs to be first.
Three correctness details
One stoppage is one row. A session showing a provider dialog is usually also
waiting_approval. Counting both would tell you two things need you when one does — in a queue whose entire job is that count.An unknown age reads as brand new, never ancient, so something with no usable timestamp can't jump the queue. Future timestamps clamp to zero age too: clock skew between daemon and browser is normal and mustn't bury a real interruption.
Ties break totally and stably (
since, thenkey). A queue that reshuffles equal rows between renders is one you can't click accurately.An honest approximation
Neither a
ui_requestnor aFleetTaskWirecarries the moment it began waiting. A session falls back tolastActivityAt(a wedged session stops producing activity the instant it wedges); a task falls back to its creation time. Both err toward looking older than reality, which biases toward surfacing rather than hiding — the right direction for this list. The code says so where it matters.One small UX call
The badge shows zero rather than disappearing. A badge that vanishes can't be trusted as an at-a-glance "nothing is stuck" signal, because you can't tell clear from broken.
Verified against a real board
Not just fixtures — running the queue over a captured board plus a simulated 3-minute-old wedged agent:
The two tasks blocked by the old daemon-collision bug (#319) outrank the fresher wedged agent — staleness overtaking kind weight, exactly as specified — and the finished tasks stay out.
Verification: 14 attention tests, 243 web tests total, typecheck / lint / build clean.
Next
§8's remaining pieces are the four-action approval grammar (Approve · Edit args · Respond · Reject) and approval mirroring with escalate-to-parent — both of which extend
ApprovalBarrather than this queue.🤖 Generated with Claude Code