block / block/buzz

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

Open
#3,711 3 comments 1 reaction 0 assignees View on GitHub
Dominant language
Rust
Stars
32.7k
Forks
4.3k
Avg merge
1d 13h
Merged PRs (30d)
253

Description

**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`)
- `!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:3622`) 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**

- Searched open issues and PRs — none found on the UI treatment or the acknowledgement of the owner control commands. Neighbours, none overlapping: #3380 (`!model`, open — reuses the same notice path for its reply, and touches the same `lib.rs` block, so whichever lands second rebases), #3537 (`/` slash-command autocomplete, open — the precedent for part 2), #3535 (`!model` listing shows a stale current marker — same family of "a chat command's answer doesn't reflect reality"), #3042 / #3587 (cancel and turn-lifecycle internals, not the feedback path).
- Reference points: handlers at `crates/buzz-acp/src/lib.rs:2032` (`!shutdown`), `:2063` (`!cancel`), `:2101` (`!rotate`); matcher `is_owner_control_command` at `:2739`; documented behaviour table at `crates/buzz-acp/README.md:152`.
- Happy to open the PR. Part 1 is a handful of lines plus tests and can go first; part 2 wants a nod on the palette shape before I write it.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.