block / block/buzz

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

Open
#3,075 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.