Repository navigation
Description
Summary
The owner control commands (!rotate, !cancel, !shutdown) cannot be issued from Buzz Desktop at all: is_owner_control_command requires an event that is simultaneously (a) content exactly equal to the command and (b) p-tagged to the agent — and no Desktop composer flow produces that combination. A mention pollutes the content; a bare message and a reply omit the p tag; a DM to a desktop-managed agent routes as a normal turn. Every attempt silently becomes a prompt.
This is adjacent to but distinct from #3711, which designs feedback for commands on the premise that they work. From the Desktop, they never fire — which is likely the real substrate of the "commands feel invisible" experience: a successful command and an unsendable one are indistinguishable in chat, and today's client can only produce the second.
The matcher vs. the docs
crates/buzz-acp/src/lib.rs (at 6e5c462 / v0.2.1, fn is_owner_control_command):
kind_u32 == KIND_STREAM_MESSAGE
&& event.content.trim() == command // exact-match — undocumented
&& event_mentions_agent(event, agent_pubkey_hex) // p tag — documentedcrates/buzz-acp/README.md (owner-command section) documents: kind:9, from owner, "must mention this agent with a p tag". It does not document the exact-content requirement. The two requirements are individually reasonable and jointly unsatisfiable from the client.
Event-level evidence
Self-hosted relay (ghcr.io/block/buzz:sha-6e5c462 = v0.2.1), Buzz Desktop 0.5.14 amd64 (Linux), harness buzz-acp self-built at the same commit running claude-agent-acp 0.68.0. Raw events as stored by the relay, all authored by the owner key:
Attempt 1 — bare command:
content: "!rotate"
tags: [["h", "<channel>"]]Content exact ✅ · p tag ❌ (composer adds no p without a mention) → routed as prompt.
Attempt 2 — mention flow (@Aero !rotate):
content: "@Aero !rotate"
tags: [["h", "<channel>"], ["p", "851bc7fe…"]]p tag ✅ · content ❌ (mention text lands in content) → routed as prompt; the agent received "!rotate" as conversation.
Attempt 3 — reply to one of the agent's messages, body exactly !rotate:
content: "!rotate"
tags: [["h", "<channel>"], ["e", "6fb3356f…", "", "reply"]]Content exact ✅ · p tag ❌ (Desktop replies add an e reply marker, not a p for the parent author) → routed as prompt.
Attempt 4 — DM to a desktop-managed local agent (built-in persona): the relay log shows the classic turn choreography, not an intercept — kind:9 ingested → two kind:7 reactions (👀/💬) → kind:5 deletions of both. An intercepted command produces no reactions (the handler consumes the event before the turn machinery). Same result in a DM.
Harness logs over the same window contain zero !rotate received lines; every attempt instead produced a model turn in which the agent speculated about what !rotate might be.
Impact
- Session rotation/cancel/shutdown are unreachable from the product's own client; the only working reset is operator-side (restart the harness process).
- Users read this as "
!rotatedoes nothing" — and with a local Claude Code agent that also cannot reply (Local Claude Code agents cannot execute any tool: permission rules are never consulted #5317/buzz-acp: claude-agent-acp turns complete successfully but the reply is never published (only typing reactions, then deleted) #5390/buzz-acp: no permission mode lets an agent publish its reply — mute turns report outcome="ok" #5322), the whole exchange is silent on both sides. - Owner control commands are invisible in the chat: no command affordance, and the outcome is only logged #3711's acknowledgment UX, when it lands, would acknowledge nothing: there is no way to get a command consumed in the first place.
Suggested fixes (any one unblocks)
- Relax the matcher to accept a p-tagged owner message whose trimmed content ends with the command (or equals it after stripping leading mention tokens) — makes the existing mention flow (
@Agent !rotate) work with no client change. Near-misses stay conservative: require the command to be the final token. - Desktop: emit the right shape — add the parent-author
ptag on replies (also the NIP-10 convention), or give the composer a command affordance that strips the mention text fromcontentwhile keeping theptag (pairs naturally with Owner control commands are invisible in the chat: no command affordance, and the outcome is only logged #3711 part 2's!palette). - Document the exact-content requirement in
crates/buzz-acp/README.mdmeanwhile, so self-hosters can at least issue commands via the CLI with a hand-built event.
Related
#3711 (feedback for consumed commands — premise: they work), #5317 / #5390 / #5322 (local Claude Code agents mute — the companion failure that makes this one fully silent), #3380 (!model, same handler block).
Activity
uevuleocha commentedon Aug 20, 2026
The CLI satisfies both conditions, which localizes the fix to the composer.
buzz messages send --mention <pubkey>attachesptags independently of the message text, so a bare!rotatebody can carry the required tags. Confirmed on 0.5.17: one CLI send with--content "!rotate"and four--mentionflags reached all four agents, and all four harness logs record receipt within 5 ms of each other at the same timestamp.That matters for the framing here. The gate is not jointly unsatisfiable in general — it is jointly unsatisfiable from the Desktop composer specifically, because the composer keeps
@Nameincontentwhile deriving theptag from it. A client that decouples the two satisfies both conditions today.So the smallest fix is on the client side: let the composer attach mention tags without leaving the text in the body, which is what the CLI already does. Alternatively, allow a leading
@Namebefore the command in the matcher. Either one closes it without touching the harness gate's semantics.kaminai0000 commentedon Sep 10, 2026
The "every composer flow" scope in the title no longer holds: the DM composer now produces a satisfying event, and the command fires. The channel/mention flow is still broken exactly as described. Narrowing evidence below.
DM: works
Bare
!canceltyped into a DM with an agent, no@, no reply. Raw event as stored by the relay:Content exact ✅ · p tag ✅ → intercepted. Harness log for the same second:
Sub-second from publish to cancel. No
!cancel received but no in-flight taskwarning, and the agent process was never restarted (same PID before and after, 21h uptime) — the ACP server acknowledgedsession/cancelinsideCONTROL_CANCEL_GRACE, so the SIGKILL fallback never ran.Applying this issue's own intercept criterion — an intercepted command produces no reactions, because the handler consumes the event before the turn machinery — here is every event in the channel across the whole exchange:
從 1 數到 400…(the prompt)h,peee!cancelh,peThe 👀/💬 pair and both kind:5 deletions are the prompt's turn choreography, opened at 01:28:15 and torn down when the turn ended. The
!cancelevent itself draws no reaction of its own — it is consumed, not prompted.Channels: still broken, and the failure is a clean dichotomy
Every owner-authored kind:9 event in a stream channel on the same relay:
@@Agent 打開apple music@Agent 目前電腦上…目前電腦上…@Agent 你好嗎@Agent 請回覆@Agent 你好嗎@Agent 目前電腦上…Perfect anti-correlation: in a stream channel, the
ptag and clean content are mutually exclusive. Attempts 1 and 2 in the original report reproduce unchanged. Row 3 is the interesting one — a bare message sent immediately after an addressed one did not retain theptag, so persistent addressing does not rescue the top-level channel case.Likely cause of the divergence
The original report is dated 2026-08-16 against v0.2.1 / Desktop 0.5.14. Persistent agent addressing landed after it:
That sequence is consistent with what's observed: addressing became a persistent audience carried as a
ptag rather than inline text, which is exactly the shapeis_owner_control_commandwants — and #7144 then scoped automatic retention to threads, which matches row 3 above losing the tag at channel top level.Caveat: this is correlation from dates and behaviour, not a bisect. There is a second uncontrolled difference from the original Attempt 4, which used a Desktop-managed local agent; the run above used an externally hosted
buzz-acpconnecting to the relay as an agent. Either factor could explain the DM difference, and I have not isolated which.Suggestion
Suggested fix 1 (relax the matcher to accept a p-tagged owner message whose trimmed content ends with the command) still resolves the remaining channel case and needs no client change. Suggested fix 3 (document the exact-content requirement) is worth doing regardless — the DM/channel split is invisible from the product, so a user who succeeds in a DM and then fails in a channel has no way to tell why.
Environment
block/buzz@3c7f288c(v0.5.23, 2026-09-05). Desktop and harness both built from that tree on 2026-09-08; the 14 commits onmainsince are not included. Self-hosted relay.buzz-acpruns an external ACP server over stdio, launched with--agent-owner <owner-hex>and default--multiple-event-handling(steer). Events read directly from the relay's Postgreseventstable; timestamps in the event tables are UTC, harness log lines are UTC+8.