You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Steer: say why a message queued instead of steering
#20839
The Steer button is often disabled, or a message I meant as a steer lands in the queue, and the UI never says why. From the outside the composer just didn't do what I asked, and there's nothing to learn from so it keeps happening.
Underneath, the server's behavior mostly makes sense. There are several distinct reasons a steer can't land right now:
Reason
What's going on
Copy shown
starting
run is preparing or starting
the agent is still starting
finishing
run is waiting: turn ended, drain/checkpoint in progress
the turn is wrapping up
between_turns
run is active but no provider turn is running
the agent is between turns
maintenance
active message is /compact or /logout
compaction or sign-out is running
unsupported
provider has neither active steering nor interrupt-restart
this provider can't steer a running turn
The client collapses all of these into one boolean (canPromoteToSteer), so the UI can only disable a button. The web composer goes further and still offers Steer during finishing, and then the server quietly converts it into a queued start.
Proposal
Make it a reason, not a boolean, and show it where the decision happens. The behavior stays mostly the same.
packages/client-runtime: replace canPromoteToSteer with steerUnavailableReason: SteerUnavailableReason | null, derived by mirroring the server's steer checks, plus a single shared copy table used by web and mobile.
Queue strip header: Queued · can't steer: <reason>, so you can see it without hovering. The disabled Steer button's tooltip says the same.
Send button: when a steer can't land, it shows Queue with a Will queue: <reason> tooltip before you send, and the message is sent as a queue to match.
Mobile: the send long-press menu and queue sheet list Steer disabled, with Can't steer: <reason> as the subtitle, instead of dropping the option.
One behavior change on web: for between_turns, maintenance and unsupported, an explicit steer from the composer now queues instead of being refused by the server. Mobile already does this, and since the button reads Queue beforehand nothing happens silently. This overlaps with what #12824 (queue messages sent during compaction) was getting at.
Before / after
These render the real QueuedRunsControl and ComposerPrimaryActions (HEAD vs. changed) with the app's CSS. The thread state is a fixture for each reason, and tooltips are forced open so they show up in a still image.
Turn wrapping up (waiting). Before: offers Steer, then silently queues.
Before
After
Between turns
Before
After
/compact running
Before
After
Provider can't steer
Before
After
Run still starting
Before
After
Steerable (unchanged)
Before
After
Questions for maintainers
Is "show the reason, keep the behavior" the right direction, or should any of these cases behave differently? finishing is the obvious candidate, since a steer there could arguably attach to the next turn.
Is the web fallback (steer → queue, labelled before sending) acceptable, or should web keep refusing and show an error instead?
Is the copy OK? It's all in one table, so it's cheap to change.
The implementation is ready locally, with focused tests covering each reason. I'm happy to open a PR if the direction looks right.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Problem
The Steer button is often disabled, or a message I meant as a steer lands in the queue, and the UI never says why. From the outside the composer just didn't do what I asked, and there's nothing to learn from so it keeps happening.
Underneath, the server's behavior mostly makes sense. There are several distinct reasons a steer can't land right now:
startingfinishingwaiting: turn ended, drain/checkpoint in progressbetween_turnsmaintenance/compactor/logoutunsupportedThe client collapses all of these into one boolean (
canPromoteToSteer), so the UI can only disable a button. The web composer goes further and still offers Steer duringfinishing, and then the server quietly converts it into a queued start.Proposal
Make it a reason, not a boolean, and show it where the decision happens. The behavior stays mostly the same.
packages/client-runtime: replacecanPromoteToSteerwithsteerUnavailableReason: SteerUnavailableReason | null, derived by mirroring the server's steer checks, plus a single shared copy table used by web and mobile.Queued · can't steer: <reason>, so you can see it without hovering. The disabled Steer button's tooltip says the same.Will queue: <reason>tooltip before you send, and the message is sent as a queue to match.Can't steer: <reason>as the subtitle, instead of dropping the option.One behavior change on web: for
between_turns,maintenanceandunsupported, an explicit steer from the composer now queues instead of being refused by the server. Mobile already does this, and since the button reads Queue beforehand nothing happens silently. This overlaps with what #12824 (queue messages sent during compaction) was getting at.Before / after
These render the real
QueuedRunsControlandComposerPrimaryActions(HEAD vs. changed) with the app's CSS. The thread state is a fixture for each reason, and tooltips are forced open so they show up in a still image.Turn wrapping up (
waiting). Before: offers Steer, then silently queues.Before
After
Between turns
Before
After
/compactrunningBefore
After
Provider can't steer
Before
After
Run still starting
Before
After
Steerable (unchanged)
Before
After
Questions for maintainers
finishingis the obvious candidate, since a steer there could arguably attach to the next turn.The implementation is ready locally, with focused tests covering each reason. I'm happy to open a PR if the direction looks right.
🤖 Claude Opus 5.5
All reactions