buzz-acp: a top-level DM is answered in a thread, not inline — the DM branch supplies no reply destination
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
## 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:
```rust
// 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 63c62fcf3.
Contributor guide
Research direction
Start in crates/buzz-acp/src/queue.rs at format_context_hints; compare the existing threaded DM handling with the channel branch's top-level case. Run the relevant tests described by the repository and verify that a top-level DM is answered inline while threaded DM turns retain their current destination.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100