Skip to content

buzz-acp: a top-level DM is answered in a thread, not inline — the DM branch supplies no reply destination #3075

Description

@muqsitnawaz

Describe the bug

A plain, top-level message to an agent in a 1:1 DM gets answered inside a thread rather than
inline in the DM, so the human has to open a collapsed "N replies" affordance to read a reply to
their own direct message. Nothing in the conversation asked for a thread.

This is the second half of #2748, which fixes the anchor for replies already inside a thread and
explicitly parks this part:

Worth discussing whether top-level DM exchanges should thread at all — a 1:1 DM arguably reads
best flat, with threading reserved for explicit user-initiated threads.

Filing it separately so the behavior question can be settled on its own. It is orthogonal to
#2748: the two touch different branches and neither PR (#2766, #2778) changes this path.

Steps to reproduce

  1. DM a managed agent (no @mention, no thread) — e.g. "find the X repo".
  2. The agent answers.
  3. The answer is not in the DM. The DM shows the original message with a 3 replies footer, and
    the answer only appears after opening the thread panel.

Expected behavior

An ordinary answer to a top-level DM arrives inline in the DM. A DM is already a private 1:1
conversation — there is nothing to keep a thread tidy for. Threads inside a DM stay reserved for
threads the human opened (or explicitly asks for), and those already anchor correctly once #2748
lands.

Root cause

crates/buzz-acp/src/queue.rs — in format_context_hints, the DM branch only emits a reply
instruction inside the if let Some(ref root) = thread_tags.root_event_id arm:

// If this is a DM reply, include thread structural info as supplementary.
if let Some(ref root) = thread_tags.root_event_id {
    ...
    if let Some(event_id) = reply_anchor {
        append_reply_instruction(&mut s, event_id);
    }
}
s

A top-level DM has no thread tags, so reply_anchor is None and the [Context] block says
nothing at all about where to reply. With no destination supplied, the agent falls back to the
general rule in base_prompt.md:

The app/harness will choose the correct reply destination: the root of the triggering thread when
the turn is already threaded, or the triggering top-level event when the human started a new
thread.

…and opens a thread rooted at the DM. In a channel that rule is right — format_context_hints
emits append_new_thread_reply_instruction for exactly that case. In a DM it produces a thread
nobody asked for.

Proposed fix

Give the top-level DM case its own explicit instruction — answer inline, no --reply-to — as the
else of the existing if let Some(root) arm, mirroring how the channel branch handles its
top-level case. Threaded DM turns keep their existing instruction, so this does not touch the lines
#2766/#2778 modify.

I have this implemented and tested locally and will open a PR against this issue.

Version and platform

Desktop app on macOS (Darwin 25.4.0), self-hosted relay, desktop-managed agent, default settings.
Reproduced on main at 63c62fc.

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