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
The owner control commands work, but the product treats them as if they didn't exist. !shutdown, !cancel and !rotate are ordinary text on the way in, and produce nothing on the way out.
Nothing marks them as commands. In the composer, !rotate is indistinguishable from a message. There is no autocomplete, no listing, no hint that this particular text will be consumed by the harness instead of reaching the agent — the only place the three commands are enumerated is crates/buzz-acp/README.md. A near-miss (!rotat, !rotate please) silently becomes a prompt, and the sent message then sits in the timeline styled like anything else you said.
Nothing confirms the effect. Each handler computes the outcome precisely and then writes it only to the harness log before consuming the event:
!rotate with a turn in flight → tracing::info!("!rotate received — cancelling in-flight turn and rotating session") (crates/buzz-acp/src/lib.rs:2118)
!rotate while idle → tracing::info!(invalidated, "!rotate received — invalidated idle channel session(s)") (lib.rs:2125)
!cancel with no in-flight turn → tracing::warn!("!cancel received but no in-flight task — no-op") (lib.rs:2080)
!shutdown → tracing::info!("shutdown command from owner — exiting gracefully") (lib.rs:2045)
From the chat, all four look identical: your own message, then silence. A successful rotate, a no-op cancel, and a command the agent never received are indistinguishable without opening the harness log. For controls whose entire purpose is to steer a session you cannot otherwise see, that is the one thing that should be visible.
The asymmetry is easiest to see next to !model (#3380): that command answers in the channel, via spawn_notice / post_notice. In the same diff, the !cancel and !rotate branches keep their tracing::info! and continue, so the gap survives that PR.
Proposed solution
Two halves, independently landable. The first is the smaller one and needs no protocol change.
1. Acknowledge the outcome in the conversation. The plumbing already exists on main: post_failure_notice (crates/buzz-acp/src/pool.rs:3591) publishes a best-effort kind:9 notice into the channel, threaded via ThreadTags, errors swallowed — currently used only for dead-lettered batches (lib.rs:3049). #3380 generalises it to post_notice and adds spawn_notice for the !model reply. Call the same helper from the other three handlers, one short line each:
Command
State
Notice
!rotate
turn in flight
Cancelled the current turn — the next one starts from a fresh session.
!rotate
idle, session cached
Session rotated — the next turn starts fresh.
!rotate
idle, nothing cached
No session to rotate — the next turn already starts fresh.
!cancel
turn in flight
Cancelled the current turn.
!cancel
idle
Nothing to cancel — no turn in flight.
!shutdown
—
Shutting down. (published before the shutdown signal)
The no-op rows matter most: they are the cases that are invisible today and that read as a broken command.
2. Render commands as commands. Recognise the owner control commands in the composer — a ! palette listing them with their one-line effect, restricted to contexts where an owned agent is addressed — and give the sent message a distinct treatment in the timeline so a consumed command doesn't read as chat. #3537 already does exactly this for the ACP / slash commands (slashCommandAutocomplete.ts, SlashCommandAutocomplete.tsx, agentCommandCatalog.ts); the ! catalog is simpler still, since the commands are a fixed set known to the harness rather than discovered per-adapter.
Alternatives considered
A kind:40099 system message instead of kind:9. Better visual weight — the timeline already renders those as system rows (SystemMessageRow.tsx, JSON {type, actor, target} payload), and a rotate is an event, not an utterance. But 40099 is relay-signed (crates/buzz-relay/src/handlers/side_effects.rs:759), so it needs a relay path or an accepted harness-signed variant. Worth doing as a follow-up; kind:9 via the existing notice helper unblocks today.
Surface it in the observer transcript / harness log viewer instead. The information is roughly there already, and it is still the wrong place: the control was issued in the conversation, so the answer belongs in the conversation. The transcript panel is also agent-scoped, not channel-scoped, so a second person in the channel never learns why the agent stopped.
A desktop toast on send. Local-only — mobile and CLI issuers see nothing — and it would have to claim success before the harness has actually consumed the event.
Suppress the command message and show only the notice. Rejected: it hides an owner action from the other members of the channel and loses the audit trail.
Documentation only. The README already documents the commands; the gap is feedback, not documentation.
Motivation
The owner control commands work, but the product treats them as if they didn't exist.
!shutdown,!canceland!rotateare ordinary text on the way in, and produce nothing on the way out.Nothing marks them as commands. In the composer,
!rotateis indistinguishable from a message. There is no autocomplete, no listing, no hint that this particular text will be consumed by the harness instead of reaching the agent — the only place the three commands are enumerated iscrates/buzz-acp/README.md. A near-miss (!rotat,!rotate please) silently becomes a prompt, and the sent message then sits in the timeline styled like anything else you said.Nothing confirms the effect. Each handler computes the outcome precisely and then writes it only to the harness log before consuming the event:
!rotatewith a turn in flight →tracing::info!("!rotate received — cancelling in-flight turn and rotating session")(crates/buzz-acp/src/lib.rs:2118)!rotatewhile idle →tracing::info!(invalidated, "!rotate received — invalidated idle channel session(s)")(lib.rs:2125)!cancelwith no in-flight turn →tracing::warn!("!cancel received but no in-flight task — no-op")(lib.rs:2080)!shutdown→tracing::info!("shutdown command from owner — exiting gracefully")(lib.rs:2045)From the chat, all four look identical: your own message, then silence. A successful rotate, a no-op cancel, and a command the agent never received are indistinguishable without opening the harness log. For controls whose entire purpose is to steer a session you cannot otherwise see, that is the one thing that should be visible.
The asymmetry is easiest to see next to
!model(#3380): that command answers in the channel, viaspawn_notice/post_notice. In the same diff, the!canceland!rotatebranches keep theirtracing::info!andcontinue, so the gap survives that PR.Proposed solution
Two halves, independently landable. The first is the smaller one and needs no protocol change.
1. Acknowledge the outcome in the conversation. The plumbing already exists on main:
post_failure_notice(crates/buzz-acp/src/pool.rs:3591) publishes a best-effort kind:9 notice into the channel, threaded viaThreadTags, errors swallowed — currently used only for dead-lettered batches (lib.rs:3049). #3380 generalises it topost_noticeand addsspawn_noticefor the!modelreply. Call the same helper from the other three handlers, one short line each:!rotate!rotate!rotate!cancel!cancel!shutdownThe no-op rows matter most: they are the cases that are invisible today and that read as a broken command.
2. Render commands as commands. Recognise the owner control commands in the composer — a
!palette listing them with their one-line effect, restricted to contexts where an owned agent is addressed — and give the sent message a distinct treatment in the timeline so a consumed command doesn't read as chat. #3537 already does exactly this for the ACP/slash commands (slashCommandAutocomplete.ts,SlashCommandAutocomplete.tsx,agentCommandCatalog.ts); the!catalog is simpler still, since the commands are a fixed set known to the harness rather than discovered per-adapter.Alternatives considered
SystemMessageRow.tsx, JSON{type, actor, target}payload), and a rotate is an event, not an utterance. But 40099 is relay-signed (crates/buzz-relay/src/handlers/side_effects.rs:759), so it needs a relay path or an accepted harness-signed variant. Worth doing as a follow-up; kind:9 via the existing notice helper unblocks today.Additional context
!model, open — reuses the same notice path for its reply, and touches the samelib.rsblock, so whichever lands second rebases), feat(desktop): autocomplete ACP slash commands (rebase of #2653) #3537 (/slash-command autocomplete, open — the precedent for part 2), !model listing shows a stale current marker while a switch is queued behind a busy turn #3535 (!modellisting shows a stale current marker — same family of "a chat command's answer doesn't reflect reality"), buzz-acp: per-channel cancelled-batch side-map is uncapped — sustained mid-turn interrupts grow harness memory and the LLM prompt without bound #3042 / buzz-acp can publish replies after a turn is terminal #3587 (cancel and turn-lifecycle internals, not the feedback path).crates/buzz-acp/src/lib.rs:2032(!shutdown),:2063(!cancel),:2101(!rotate); matcheris_owner_control_commandat:2739; documented behaviour table atcrates/buzz-acp/README.md:152.