Skip to content

fix(buzz-acp): anchor replies for natively-steered events#1549

Open
baxen wants to merge 1 commit into
mainfrom
fable00/reply-routing-investigation
Open

fix(buzz-acp): anchor replies for natively-steered events#1549
baxen wants to merge 1 commit into
mainfrom
fable00/reply-routing-investigation

Conversation

@baxen

@baxen baxen commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Problem

Reported in buzz-bugs: an agent working in a thread was asked for a status update via a new top-level mention, and replied inside its old task thread instead of the new message.

Trace from the live channel confirmed it's a real gap, not just LLM confusion: the top-level ask (6d7d24c5…, no e-tags) was answered by the busy agent with an event e-tagged to its old thread root, while the two idle agents (fresh turns) replied to the right place.

Root cause

When a message arrives mid-turn and is delivered via the goose-native steer path (try_native_steer, lib.rs), the steer body is framing header + event block + closing noteno reply-anchor instruction. Every other delivery path anchors replies:

  • fresh top-level mention → append_new_thread_reply_instruction (anchor = triggering event)
  • fresh thread mention → append_reply_instruction (anchor = thread root)
  • cancel+merge steer fallback → format_prompt re-derives the anchor from the last event

So a natively-steered agent's only in-context --reply-to guidance is the stale anchor from its original prompt — replying to the old thread is literally what its context tells it to do.

Fix

queue::native_steer_reply_instruction derives an anchor from the steered event's own thread tags, mirroring resolve_reply_anchor:

  • steered event is threaded → anchor to its thread root
  • steered event is top-level → anchor to the event itself (new thread root)

and appends the instruction to the steer body after the event block. The instruction is scoped to "replies to this new message" so the agent keeps its original anchor for in-progress work. No profile lookup exists on this synchronous path, so no agent↔agent exemption is applied — consistent with turn_is_human_facing's fail-open policy (unknown → treat as human).

Testing

  • New unit tests pin both derivations (top-level → event id; threaded → thread root, never event id).
  • cargo test -p buzz-acp --lib: 436 passed. fmt + clippy clean.

Found and fixed by fable00 (agent) — thread: buzz-bugs 7c614ae5…

When a message reaches an agent mid-turn via the goose-native steer
path (try_native_steer), the steer body carried only the framing
header, the event block, and the closing note — no reply-anchor
instruction. The agent's only in-context --reply-to guidance was the
stale anchor from its original prompt, so it replied into the old
thread even when the steered message was top-level or belonged to a
different thread. Observed in the wild: a top-level 'give me a status
report' mention was answered inside the agent's old task thread.

Every other delivery path already anchors: fresh top-level turns get
append_new_thread_reply_instruction, fresh thread turns get
append_reply_instruction, and the cancel+merge fallback re-derives the
anchor in format_prompt from the last event. Native steer was the only
path with no anchor at all.

Fix: native_steer_reply_instruction derives the anchor from the
steered event's own thread tags — its thread root when threaded, the
event itself when top-level — mirroring resolve_reply_anchor, and is
appended to the steer body after the event block. No profile lookup is
available on this synchronous path, so no agent-to-agent exemption is
applied; that matches turn_is_human_facing's documented fail-open
policy (unknown identity is treated as human). The instruction is
scoped to 'replies to this new message' so in-progress work keeps its
original anchor.

Tests pin both derivations (top-level anchors to the event, threaded
anchors to the steered event's root, never the event id).

Co-authored-by: Bradley Axen <baxen@squareup.com>
Signed-off-by: Bradley Axen <baxen@squareup.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 885585188f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

let tags = parse_thread_tags(event);
let event_id = event.id.to_hex();
let mut s = String::new();
match tags.root_event_id {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve DM reply anchoring in native steers

When the in-flight channel is a DM, the normal prompt path applies DM-specific routing: top-level DM messages intentionally get no --reply-to, and DM thread replies anchor to the triggering event id rather than the thread root (format_prompt's is_dm branch, also pinned by test_reply_instruction_absent_for_dm_non_reply). This new helper only switches on thread tags and try_native_steer calls it without channel type, so the exact same DM message is routed differently whenever it arrives mid-turn via native steer: top-level DMs become threaded replies, and threaded DMs reply to the root instead of the current message. Please pass the DM/channel context into this derivation or suppress/adjust the instruction for DMs.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants