Skip to content

Owner control commands are unsendable from Buzz Desktop: is_owner_control_command's exact-content + p-tag requirements are jointly unsatisfiable from every composer flow #6014

Description

@sfasching

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 — documented

crates/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

Suggested fixes (any one unblocks)

  1. 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.
  2. Desktop: emit the right shape — add the parent-author p tag on replies (also the NIP-10 convention), or give the composer a command affordance that strips the mention text from content while keeping the p tag (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).
  3. Document the exact-content requirement in crates/buzz-acp/README.md meanwhile, 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

  1. uevuleocha commented on Aug 20, 2026

    @uevuleocha

    The CLI satisfies both conditions, which localizes the fix to the composer.

    buzz messages send --mention <pubkey> attaches p tags independently of the message text, so a bare !rotate body can carry the required tags. Confirmed on 0.5.17: one CLI send with --content "!rotate" and four --mention flags 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 @Name in content while deriving the p tag 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 @Name before the command in the matcher. Either one closes it without touching the harness gate's semantics.

  2. kaminai0000 commented on Sep 10, 2026

    @kaminai0000

    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 !cancel typed into a DM with an agent, no @, no reply. Raw event as stored by the relay:

    kind:    9
    content: "!cancel"
    tags:    [["h", "dfb66e55-…"], ["p", "c10455b3…"]]
    pubkey:  cb422fce…        // owner, matches the harness's --agent-owner

    Content exact ✅ · p tag ✅ → intercepted. Harness log for the same second:

    09:28:15  Prompt on session 36a15e87-…
    09:28:15  conversation turn: session=36a15e87-… model=… platform=acp history=28
    09:28:24  control signal sent to in-flight task (scope-exact)
                  channel=dfb66e55-… scope=conversation mode=Cancel
    09:28:24  Cancelled session 36a15e87-…
    09:28:24  Turn ended: reason=interrupted_during_api_call … tool_turns=9
    

    Sub-second from publish to cancel. No !cancel received but no in-flight task warning, and the agent process was never restarted (same PID before and after, 21h uptime) — the ACP server acknowledged session/cancel inside CONTROL_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:

    time kind content author tags
    01:28:14 9 從 1 數到 400… (the prompt) owner h,p
    01:28:15 7 👀 agent e
    01:28:15 7 💬 agent e
    01:28:24 5 (deletion) agent e
    01:28:24 9 !cancel owner h,p
    01:28:24 5 (deletion) agent e

    The 👀/💬 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 !cancel event 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:

    content starts with @ p tags
    @Agent 打開apple music yes 1
    @Agent 目前電腦上… yes 1
    目前電腦上… no 0
    @Agent 你好嗎 yes 1
    @Agent 請回覆 yes 1
    @Agent 你好嗎 yes 1
    @Agent 目前電腦上… yes 1

    Perfect anti-correlation: in a stream channel, the p tag 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 the p tag, 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 p tag rather than inline text, which is exactly the shape is_owner_control_command wants — 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-acp connecting 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 on main since are not included. Self-hosted relay. buzz-acp runs 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 Postgres events table; timestamps in the event tables are UTC, harness log lines are UTC+8.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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