DM: agent never posts a reply to a top-level DM — the turn context omits the send instruction when there is no thread root
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
### Summary
When a user sends the **first, top-level message in a DM** to an agent, the agent runs the turn, does the
work, composes an answer — and never posts it. From the user's side the agent simply never replies.
The cause is in the prompt the harness builds: the DM branch attaches the "reply with `buzz messages send`"
instruction **only when the triggering event is already inside a thread**. A top-level DM has no thread root,
so the agent receives a context block that tells it how to *read* the conversation but never how to *send*
anything. The channel branch does not have this gap — it covers both the threaded and the top-level case.
### Reproduction
1. Open a DM with an agent.
2. Send a message that is not a reply to anything (a fresh top-level message).
3. The agent picks up the turn and works, but nothing is posted back.
4. Now reply to your own message (or to any message in that DM) and ask again — the agent answers normally.
### Evidence
Two consecutive turns in the same DM session. Only the second one contains the send instruction, and only the
second one results in a published event:
| turn | triggering message | thread root | context block | `buzz messages send` calls | reply posted |
|---|---|---|---|---|---|
| 1 | top-level DM | absent | 742 chars, **no send instruction** | **0** | **no** |
| 2 | reply (carries an `e` tag to turn 1) | present | 1466 chars, send instruction present | 2 | yes |
Turn 1 was not a failed send. The agent completed the turn cleanly — it listed channels, read the day's
messages with `buzz messages get`, and produced a full answer as its final assistant text. It simply never
called the send command, and the relay log shows no `kind:9` event published by the agent in that window.
The same agent posted replies normally in channels throughout the same period (4-15 send calls per turn).
For contrast, channel-scope turns in the same session period carry the instruction in both shapes:
```
Scope: channel
Channel: ...
Hint: Use `buzz messages get --channel ` for recent messages if needed.
IMPORTANT: This is a new top-level message. For ordinary replies in this turn, use
`--reply-to ` on `buzz messages send` ...
```
while the failing DM turn carried only:
```
Scope: dm
Channel: DM (#)
```
### Root cause
`crates/buzz-acp/src/queue.rs`, DM branch (`Scope: dm`):
```rust
// If this is a DM reply, include thread structural info as supplementary.
if let Some(ref root) = thread_tags.root_event_id {
s.push_str(&format!("\nThread root: {root}"));
...
if let Some(event_id) = reply_anchor {
append_reply_instruction(&mut s, event_id); // <- only reached inside a thread
}
}
```
The channel branch immediately below handles the same two cases symmetrically:
```rust
// thread case
if let Some(event_id) = reply_anchor {
append_reply_instruction(&mut s, event_id);
}
// top-level case
if let Some(event_id) = reply_anchor {
append_new_thread_reply_instruction(&mut s, event_id);
}
```
`append_reply_instruction` and `append_new_thread_reply_instruction` are the only places that mention
`buzz messages send` in the turn context, so when neither fires the agent has no instruction to publish
anything.
Related: `resolve_reply_anchor` is documented as "Resolve the `--reply-to` anchor for a **non-DM** turn",
which suggests the DM path was intended to be handled separately and the top-level sub-case was missed.
### Suggested fix
Give the DM branch the same top-level handling the channel branch has — attach
`append_new_thread_reply_instruction` (anchored to the triggering event) when `thread_tags.root_event_id` is
`None`. That keeps DM replies threaded on the triggering message, matching channel behaviour.
A cheaper alternative, if threading in DMs is deliberately looser, is to always state that replies are sent
with `buzz messages send` in the DM context block and leave the `--reply-to` anchor optional.
### Impact
The *first* message a user ever sends an agent in a DM is by definition top-level, so this is the most likely
DM interaction to hit. The agent looks unresponsive with no error anywhere: no failed command, no rejected
event, nothing in the relay log — the work is done and the answer is discarded. Users have no way to tell
this apart from the agent being broken.
Contributor guide
Assessment
This issue has not been assessed yet.