buzz-acp: native mid-turn steer sends no reply destination, so the message is answered in the previous thread
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
`buzz-acp` has two transports for delivering a message into a turn that is already running, and only one of them tells the agent where to reply. A message steered in from thread B gets answered in thread A. Thread B shows no reply, so from the human's side the second message looks unanswered.
It is the default path, not an edge case: `--multiple-event-handling` defaults to `steer` (`config.rs:362`), native steering is tried first, and the cancel+merge fallback — which *does* supply a destination — runs only when native declines. Not goose-specific either: any adapter advertising `_meta.steering.supported` is affected, `claude-agent-acp` included.
**Steps to reproduce**
1. Run an agent under `buzz-acp` at default settings, on an adapter advertising `_meta.steering.supported`.
2. `@mention` it in thread A. It starts a turn.
3. Before that turn finishes, `@mention` it in **thread B** — a different thread, same channel. This is delivered as a native mid-turn steer.
4. **Expected:** the answer lands in thread B. **Actual:** it lands in thread A.
**Root cause**
`try_native_steer` (`lib.rs:3635`) hand-assembles its own prompt instead of going through `queue::format_prompt`, where the reply anchor is resolved (`queue.rs:1604`). With no destination in the steered prompt, the newest one left in the agent's context belongs to the turn already in flight. The rule is not missing — this transport never calls it.
**Proposed solution**
Move ownership of the steer body into `queue`, so the anchor cannot be skipped by whoever assembles the prompt:
- Add `queue::format_native_steer_prompt` — framing, event block, **and** the resolved reply anchor.
- Make `native_steer_framing` and `format_event_block` private to `queue`, so hand-assembly cannot recur.
- Fold `format_prompt`'s inline DM branch into `resolve_reply_anchor`. One function should hold the rule; that duplication is how the transports diverged.
Only per-event routing is added. Standing context, channel metadata and history stay out, preserving the delta property that makes non-cancelling steering worth having.
**Related**
**#5190** is the closest: the same symptom from the agent's side, attributed to a `[Context]` block carrying the in-flight turn's root. We think that is what this absence looks like from inside a session, and filed separately only because the fix location differs — recomputing per event is what `format_prompt` already does; the gap is a transport that never calls it. **Say the word and we will fold this into #5190.**
**#1549** is the closest prior art — same root cause, approved, then closed unmerged by its own author with no reason given. It never reached `main`. **If it was closed for a reason that still applies, we would rather hear it now than rediscover it.**
Nearby but distinct: #5839, #5219, #3075, #4072 — characterised in the comment.
**Version and platform**
`block/buzz@main` at `52621c09b`; macOS (Darwin 25.6.0), self-hosted relay, desktop-managed agent, default settings.
Contributor guide
Assessment
This issue has not been assessed yet.