Reply destination is keyed to the in-flight turn, not the triggering event — new top-level messages get answered inside the previous thread
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
## Summary
When an agent is mid-turn and a **new top-level message** arrives in the same channel, the reply destination handed to the agent is derived from the turn already in flight rather than from the message that triggered the new work. The agent then posts its answer into the *previous* thread, collapsing two deliberately separate conversations into one.
The user's mental model is "new thread = new topic." That intent is silently discarded whenever they type the second message before the agent has finished the first.
## Environment
- Buzz Desktop with an ACP-hosted coding agent (`buzz-acp` harness)
- Single channel, single human, single agent
- Reproduces reliably; frequency scales with how fast the human sends the follow-up
## Steps to reproduce
1. In channel `#eng`, post a new top-level message:
> @Agent audit our dependency lockfiles and tell me which ones are stale.
2. The agent picks it up and begins working (this step takes a while — it is shelling out, reading files).
3. **Before it replies**, post a second, unrelated **new top-level** message (not a reply to step 1):
> @Agent separately — what is our current Postgres connection pool size?
4. Wait for both answers.
## Expected
Two threads:
- Thread A, rooted at the lockfile message, contains the lockfile answer.
- Thread B, rooted at the pool-size message, contains the pool-size answer.
## Actual
One thread. The pool-size answer is posted as a reply inside Thread A, under the lockfile message. Thread B has no reply at all — from the human's side the second question looks unanswered until they scroll into the first thread and find it buried there.
## Cause (as visible from the agent side)
The harness injects a context block telling the agent where to reply. When a message arrives mid-turn, that block still carries the *in-flight* turn's root:
```
[Context]
Scope: thread
Channel: eng (#)
Thread root:
IMPORTANT: For ordinary replies in this turn, use
--reply-to
```
...even though the triggering event in the very same payload is the pool-size message:
```
[Buzz event: @mention]
Event ID:
Content: @Agent separately — what is our current Postgres connection pool size?
Tags: [["h",""],["p",""]]
```
Note the triggering event has **no `e` tag** — it is a genuine new top-level post, not a reply. The instruction and the event disagree, and the instruction is emphatic ("IMPORTANT", "Do NOT reply into any other thread"), so a compliant agent follows it and threads the answer wrongly.
## Suggested fix
Derive the reply destination from the **triggering event**, not from the turn in flight:
- If the triggering event has no `e` tag, it is a new thread root — the reply destination should be that event's own id.
- If the triggering event has an `e` tag, use its root as today.
- Recompute this per delivered event rather than once per turn, so a message arriving mid-turn gets its own destination.
## Workarounds today
- The human waits for an acknowledgement before sending the next message (defeats the point of asynchronous agents).
- The human states the destination in prose ("answer this in its own thread"), which the agent can honour as an override.
- The agent ignores the supplied destination and keys off the triggering event id itself — correct behaviour, but it requires disobeying an instruction the harness marks as IMPORTANT, which is a bad thing to teach an agent to do.
## Impact
Low severity, high annoyance, and it gets worse as agents get slower and users get more comfortable firing off parallel requests. It also makes channel history harder to read back later, since one thread ends up holding answers to questions that were never asked in it.
Contributor guide
Research direction
Start in the buzz-acp harness where the injected reply context is assembled and compare its thread-root selection with the triggering event's e tag and id. Add coverage for a new top-level event arriving during an in-flight turn and verify that it gets its own root, while replies still use their existing thread root.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- backend, backend-api-design
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100