Skip to content

Owner control commands are invisible in the chat: no command affordance, and the outcome is only logged #3711

Description

@Berenger-Wooclap

Motivation

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)
  • !shutdowntracing::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.

Additional context

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions