fix(acp): steer running turns with edited mentions - #6132
Conversation
9dd506f to
ff25d69
Compare
9e2b81a to
b6a5612
Compare
ff25d69 to
4a6fe5c
Compare
b6a5612 to
d333f64
Compare
4a6fe5c to
681ae2c
Compare
d333f64 to
82e0415
Compare
jedwards27
left a comment
There was a problem hiding this comment.
Verdict: APPROVE
Reviewed 681ae2c4d34dccf1adb9fb82908fd9eb88fcf3d1..82e041561d5a5cf04b54ec983fd208202c8ae2f6 at exact head 82e041561d5a5cf04b54ec983fd208202c8ae2f6.
Risk: high — this changes messaging correctness and the ACP agent-runtime lifecycle across asynchronous preparation, live-turn steering, acknowledgements, membership changes, and fallback delivery.
Findings: no blocking or non-blocking defect found.
Behavior/contracts traced:
- Prepared edits carry membership generation, originating turn ID, and the exact queue reservation; all three must still match before native steering is sent (
crates/buzz-acp/src/lib.rs:1550-1582,3504-3555). Mutation-removing the turn-ID fence causedprepared_edit_is_stale_after_replacement_turn_startsto fail, then the tree was restored clean. - Membership removal/re-add drains or invalidates old work, advances generation, and prevents stale acknowledgements from mutating queue state, reactions, deadlines, fallback, or the delivery ledger (
crates/buzz-acp/src/lib.rs:1614-1631,2745-2862,3585-3597,4445-4482;crates/buzz-acp/src/queue.rs:764-812). - Result/ack ordering is loss-averse: a reservation is marked sent before watcher spawn, completion remains fenced until the sent acknowledgement settles, and failure/unknown outcomes release to ordinary delivery or cancel-and-merge rather than dropping the edit (
crates/buzz-acp/src/lib.rs:3602-3750,4150-4193;crates/buzz-acp/src/queue.rs:440-469,932-1012). - Edit enrichment resolves the signed original and thread before formatting; reactions and failure notices remain attached to the original visible message rather than exposing the auxiliary edit as a reply anchor (
crates/buzz-acp/src/pool.rs:3470-3506,3526-3591;crates/buzz-acp/src/queue.rs:1704-1724;crates/buzz-acp/src/lib.rs:1634-1649,3721-3727). - The exact diff is confined to
buzz-acpRust and does not alter Desktop rendering, focus, keyboard, or accessibility surfaces.
Validation at exact clean head:
cargo test -p buzz-acp --all-targets— PASS: 856 library tests plus 9 lifecycle integration tests, 0 failures.cargo fmt --all -- --check— PASS.cargo clippy -p buzz-acp --all-targets -- -D warnings— PASS.git diff --check— PASS.- Targeted mutation of the replacement-turn fence — expected regression test failure; restored tree clean at the reviewed head.
- Fresh GitHub state: head remains pinned, merge state
CLEAN, 26 checks successful and 4 intentionally skipped.
Manual/native evidence: no real Goose, Claude, or Codex process was observed accepting an edited mention end to end through relay enrichment, native steer, visible reply, and reaction cleanup. Deterministic transport/lifecycle tests cover the changed seams, but do not prove real-adapter timing.
Residual risk: acp.rs retains an existing reader-first biased select that can defer a ready steer while stdout remains continuously ready; its pre-select deadline check bounds the delay, but fairness was not live-probed. That mechanism is outside this PR's exact diff, so it is a pre-existing follow-up risk rather than author rework.
Author action: none.
Verification owner: reviewer/tooling for optional live-adapter observation and a separate fairness probe; CI/release gate for normal post-merge artifact validation.
681ae2c to
a168da6
Compare
82e0415 to
52d1981
Compare
52d1981 to
d9a0d57
Compare
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 thanks for the rewrite, this is a lot smaller now that it reuses #4741's route. combined review at d9a0d573: two independent source passes plus a live run against a local relay with signed events and a scripted ACP peer on both steer transports (_session/steering and Goose's run-ID method).
no runtime defect in the incremental change. edits really do take the native path live, keep the original's route through success, delayed acks, and fallback, and a steer only ever lands in the original's own session (idle threads queue, busy siblings aren't touched, a failed steer cancels only its own thread).
one blocker, inline: the tests don't pin either behavior this PR adds. restoring the old "edits never steer" rule, or dropping the route right at the send, both leave the full buzz-acp suite green.
inherited from earlier in the stack, not new here:
- when the running turn is itself an edit and native steer isn't available, the #4741 interrupt bug still applies (old edit re-runs before the correction). native success avoids that path but doesn't fix it.
- a message delivered by a successful steer keeps its 👀, since reaction cleanup only covers the turn's original batch. for edits that 👀 sits on the original. this predates the stack.
nonblocking, description only: "if native steering fails, the cancel-and-restart fallback runs" is broader than what the code does. unsupported, transport, and rejected outcomes cancel and restart, but other agent errors and turn-completed outcomes just requeue the edit for normal delivery. that split is existing behavior, so this is just wording.
46fc851 to
70f4d4b
Compare
d9a0d57 to
b28022b
Compare
|
On the inherited interrupt issue in your review: #4741 now fixes it (e079ba6). An interrupted edit merges into the next turn as earlier work, the new request gets the supersede framing, and the old edit does not run again. This PR's head b5d201a includes that fix. The 👀 cleanup gap for steered messages predates the stack; I left it out of scope. |
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent — APPROVE exact head b5d201a2a811a4cdfe9721b99bead37e2b975bf2 against base 70f4d4b3c0e9437bdca695db7fff5223294878c1.
No author-actionable defect remains.
The refreshed implementation preserves one routing authority: after normal authorization/subscription admission, the signed original is fetched under channel/message-kind constraints and verified, and the resulting ResolvedEdit travels in the same queued BatchEvent used by native steering (crates/buzz-acp/src/lib.rs:604-739,3503-3576; edit_routing.rs:23-93). Scope selection is derived from that verified route, so per-thread sessions cannot steer/cancel another thread, while channel-scoped sessions retain their intended behavior (scope.rs:135-159; lib.rs:4252-4386; pool.rs:1146-1162).
Failure handling remains loss-averse: the queued edit is synchronously withheld before the acknowledgement watcher; success records delivery and removes it, while unsupported/rejected/transport/application/completion/drop/timeout paths release it for cancel+merge or ordinary dispatch (lib.rs:3819-3975,4341-4376; queue.rs:921-1050). The native steer body reuses ordinary event formatting, retaining Edit of: <original> and the verified original thread root (lib.rs:4389-4399; queue.rs:1402-1489).
The replacement regression reaches the production decision seam: it establishes a running scope, submits a routed edit, invokes steer_or_interrupt, and asserts the actual SteerRequest carries the original ID and thread root without cancellation (lib.rs:9535-9616). This is causally coupled: restoring the old edit exclusion prevents the request, and dropping the resolved route breaks the root assertion.
Validation at clean exact head:
- Exact-head CI: 50 successful, 23 intentional skips, including Rust lint/unit/Windows, relay/backend/PostgreSQL/Desktop integration and builds, security, and DCO.
cargo fmt --all -- --check: pass.git diff --check 70f4d4b3...b5d201a2: pass.- Diff audit: no production
unwrap/expect,unsafe, schema/migration, IPC, Desktop, or packaging changes. - Local package tests/clippy could not execute because the reviewer host linker exits 69 with the Xcode license unaccepted. Exact-head CI owns those required gates and is green; this is not author rework.
Residual confidence gap: no fresh real Goose/Codex/Claude edited-mention run was observed on this head. Optional live-adapter observation belongs to reviewer/tooling. Successful native steers may also retain the admission-time reaction; source tracing shows this behavior predates the PR and applies to ordinary native steers, so it is a separate follow-up rather than required scope expansion here.
Author action: none.
Verification owner: reviewer/tooling for optional live-adapter observation and inherited reaction cleanup; CI/release gates for normal artifact validation.
🤖 ## Summary When someone edits a message to add an @mention, the app does not change the original message. It publishes a separate hidden "edit" event that points back at it. An agent that handles the edit must work on the **original message**: it reacts, replies, and posts any error notice on that message or in its thread. Before this PR, an agent treated the hidden edit as a new message. It replied in the wrong place, and with per-thread sessions it started a new, empty conversation. This is the first of three PRs. It sets where agents reply to edits. It does not yet make edits wake agents by default (#6131 does that). - The agent looks up the original message once, when it accepts the edit. Every later step uses that result: the session, reactions, typing indicator, reply target, and failure notices. - **Per-thread sessions** (the `thread` session policy, where each thread has its own agent conversation): the edit joins the original message's thread session. It does not start a new session. - The original is a reply in a thread: the edit joins that thread. - The original is a top-level message: the edit joins the thread that starts at the original. - **Channel sessions** (the default policy) and DMs: nothing changes. The whole channel shares one conversation. - Setup mode (the reply that asks the owner to finish configuring the agent) now accepts edits. It replies at the original message or its thread. If the original could not be fetched, the reply posts at the top of the channel. - Error notices for an edit (sign-in failure, hard timeout, retries used up) now appear in the original's thread, or as a reply to a top-level original. Before, they appeared at the top of the channel. If the original could not be fetched when the edit was accepted, the notice looks it up once more. If it is still unknown, the notice posts at the top of the channel. - If a newer request interrupts a turn that is working on an edit, the next turn treats the edit as earlier work that the new request replaces. The old edit does not run again. ## Details - The lookup has a 2-second limit. It runs only for edits that already matched a subscription rule (by default, edits that newly mention this agent). It must finish before the edit is queued, because the session is chosen at that point. - If the lookup fails, the agent still targets the edited message's ID, never the hidden edit event. Notices and setup replies never claim that message as a thread root, because the relay rejects a root that does not match the message's real thread. **Known gap:** if that message is a reply in a thread, the agent cannot learn the thread root. So under per-thread sessions it uses a separate session rooted at that reply, and it loses the thread's earlier context. Its reply still goes to the right message. Closing this gap needs a second lookup, which is left out on purpose. - The original is fetched with an explicit kind (stream message or its edit) and a channel filter, because the relay rejects lookups without a kind. - Each queued event carries its own route, so an edit shares a batch with other events like any message. Batch order is unchanged from `main`. ### Stack **1 of 3**, based on `main`. Next: #6131 (edits wake agents by default), then #6132 (edits join a turn that is already running). ### Related issue Part of #2540. The issue is fixed when #6131 merges. ### Testing - `cargo test -p buzz-acp --all-targets` passes. One existing test, `session_new_forwards_complete_git_block_without_duplicate_names`, fails in a shell where the git setting `nostr.keyfile` is set. It passes when the setting is removed. This PR does not touch that test. - Session tests: an edit uses the original's thread for a threaded original, a top-level original, and an original that could not be fetched. It never uses the edit event. - Tests check the signed events that the agent publishes: setup-mode replies and failure notices, each for a threaded original, a top-level original, and an original that could not be fetched (including a notice whose second lookup finds the original). If either routing fix is reverted, these tests fail. - A queue-to-prompt test interrupts a turn on an edit with a newer request. The next prompt has the edit as earlier work and the new request with the "replaces earlier work" framing, and nothing is left to run again. --------- Signed-off-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
70f4d4b to
06d5a51
Compare
b5d201a to
8aa6164
Compare
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent — blocking review finding at exact head 8aa61647f318bd4286132248420f00c99d81ca44 against base 06d5a51289f7495d22b93a723b68fc92d59fc1f3.
Major — the steered edit can retain the running turn's old authoritative reply destination
Under the default channel session policy, a running turn from thread A can accept an edited mention whose verified original belongs to thread B. The new native-steer payload includes B's event metadata, but it does not replace the live turn's authoritative <context> routing metadata:
- Normal dispatch derives
thread_tags,routing_event_id, and the human-awarereply_anchor, then emits them throughformat_context_hints(crates/buzz-acp/src/queue.rs:2108-2122,2148-2180). - The base prompt explicitly tells the agent that
<context>is authoritative for routing and the reply destination (crates/buzz-acp/src/base_prompt.md:4-6,56-64). - Native steering emits only
<new-message-arrived-while-you-were-working>plus a<buzz-event>(crates/buzz-acp/src/lib.rs:4317-4333,4400-4410). Despite the comment claiming the edit is “anchored exactly as a dispatched one would be,” the payload contains no replacement<context>and therefore cannot supersede thread A's authoritative reply destination.
Failure scenario: a channel-scoped turn starts from human thread A; an edited mention for an original in human thread B is natively steered into it. The model sees B's root in supporting event metadata while its authoritative routing context still points to A, so following the harness contract can post the response in A. That violates this PR's central promise to preserve the edited original's thread route.
Required fix: make the native-steer delta carry authoritative routing/reply-destination context derived by the same logic as normal dispatch. Add a production-seam regression with a running thread-A turn and a thread-B edited original under SessionPolicy::Channel; assert the effective reply destination is B and A is absent. Mutation-prove that removing the routing update makes the test fail.
Integrated evidence and verification
The systems lane found no loss, stale-session, or cross-scope lifecycle defect: exact SessionScope task selection, capacity-one steering, withheld-event acknowledgement/release, deadline recovery, cancel+merge fallback, and delivery persistence remain coherent. That does not resolve the product lane's distinct prompt-contract defect: transport can safely deliver an event to the correct channel session while leaving that session's reply anchor stale.
Exact-head CI reports 50 successful and 23 skipped checks. Local package test and clippy attempts were blocked by reviewer tooling (cc exit 69 from an unaccepted Xcode license), not by this PR. No Desktop rendering/accessibility surface changed. A real adapter journey was not run.
Author action: update authoritative routed context and add the causal cross-thread regression above.
Verification owner: author for the fix and mutation-proven test; reviewer for exact-head lifecycle/fallback re-review and optional live adapter observation.
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent
Verdict: REQUEST CHANGES
Reviewed: 06d5a51289f7495d22b93a723b68fc92d59fc1f3..8aa61647f318bd4286132248420f00c99d81ca44 (exact head 8aa61647f318bd4286132248420f00c99d81ca44)
Risk: high — cross-thread agent reply routing during an in-flight channel-scoped turn.
Behavior/contracts traced: edited-event identity and original-route resolution; SessionScope task selection; native-steer payload construction; withheld-event acknowledgement/release; exact-scope cancel+merge fallback; reply/reaction anchoring; ACP-session delivery lifecycle.
Finding — blocking: a native-steered edit does not replace the running turn’s authoritative reply destination. Under the default channel session policy, an edit targeting thread B may steer a turn originally started from thread A (crates/buzz-acp/src/lib.rs:3535-3575; the new test uses SessionPolicy::Channel at lib.rs:9850-9884). Normal dispatch computes the routed reply anchor and writes it into <context> (crates/buzz-acp/src/queue.rs:2108-2122,2148-2180), and the base prompt declares that context authoritative (crates/buzz-acp/src/base_prompt.md:6,58-64). Native steer sends only a framed <buzz-event> delta, omitting an updated <context> (crates/buzz-acp/src/lib.rs:4317-4333,4400-4409). The new test checks Edit of: and root= text but not the effective reply destination (lib.rs:9896-9904). Consequently, following the harness contract can send the response for B into A.
Author action: make the native-steer delta carry the authoritative routed reply destination for the edited original—preferably using the same anchor/context computation as normal dispatch—and add a production-seam regression for running thread A plus edited original in thread B under channel policy. Assert the effective --reply-to is B and A is absent; mutation-prove that dropping the routing update fails.
Verification owner: author for code and causal regression; reviewer/tooling for exact-head lifecycle/fallback re-review and optional live adapter observation.
Validation: live GitHub head/base and authenticated identity rechecked immediately before delivery; merge state CLEAN; exact-head CI reports 50 successful / 23 intentionally skipped checks; both lanes report clean git diff --check. Local package test/clippy were blocked before PR-code compilation by this review host’s unaccepted Xcode license (exit 69), a non-author tooling gap. CI success does not resolve the routing contradiction.
Residual risk: no real Goose/Codex/Claude native-steer journey was observed at this head. No Desktop/UI files changed, so visual/accessibility evidence is not applicable.
🤖 ## Summary Today an agent does not wake up when someone edits an existing message to @mention it. With this PR, adding the mention in an edit is enough to reach the agent. The agent then works on the original message, as #4741 set up. - Agents now receive message edits by default. Before, they subscribed only to new messages (and a few other event types). - The desktop app puts a mention tag on an edit only for people whom that edit newly mentions. So an agent wakes once, for the new mention. It does not wake again when the same message is edited later. This PR also fixes one case in the desktop app: when the original also mentioned someone whose profile was not loaded, every mention counted as new, so a typo fix woke the agent again. Now the recipients that the app knows from the original always count as already notified. - This default applies in normal mode, to channels the agent joins while it is running, and in setup mode. - If you give an explicit `--kinds` list, it is used without change. - Sessions do not change in this PR. An edit uses the session that #4741 chooses: with per-thread sessions, the original message's thread; with channel sessions, the channel. ## Details - The change adds the edit event kind (40003) to one shared default list. The startup rules, the relay subscription filters, and the setup rules all use that list. - An agent accepts an event only if both the relay subscription and its local rules allow it. The normal listener's startup rules now come from one function, `startup_subscription_rules`. The harness and the tests both call it, so the tests check the rules that actually run. ### Stack **2 of 3**, based on #4741. Next: #6132. ### Related issue Fixes #2540. ### Testing - `cargo test -p buzz-acp --all-targets` passes. The one test that fails when the git setting `nostr.keyfile` is set is described in #4741. - Admission tests run on the rules the harness installs, for the normal listener and for setup mode. An edit that mentions the agent is accepted. An edit that does not mention it is rejected. - I removed kind 40003 from each of the two defaults, one at a time. Each time, these tests failed. - Config tests check that edits are in the default kinds for startup channels and for channels joined later. - Desktop regression: `@Agent @missing User please chek` edited to `check`, with only the agent's profile loaded, sends no new notification. It fails on the old code. --------- Signed-off-by: npub1em3jmyn4vu57urqf03txrwreccvejvwdy5c4er8nnrwt7rc4tncscs3ssu <cee32d92756729ee0c097c5661b879c6199931cd25315c8cf398dcbf0f155cf1@buzz.block.builderlab.xyz> Signed-off-by: Larry <8cf5a83f590ec0955b11647d1c88f796a98e088c30a492c58e0e46c3026ae7a4@buzz.block.builderlab.xyz> Signed-off-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Co-authored-by: npub1em3jmyn4vu57urqf03txrwreccvejvwdy5c4er8nnrwt7rc4tncscs3ssu <cee32d92756729ee0c097c5661b879c6199931cd25315c8cf398dcbf0f155cf1@buzz.block.builderlab.xyz> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
|
🤖 Thanks, the finding was correct. Fixed in
Local: |
An edit that arrives during an agent turn now uses the native steer path like any other event. The steer carries the edit's resolved original-message route, so the steered turn replies and reacts in the original's thread instead of treating the edit as a new top-level message. The route is resolved at admission, so native steer needs no separate preparation, reservation or membership fence. Fallback, cancellation and channel removal reuse the existing withheld-steer lifecycle, which already keeps the queued edit and its route together. Replaces the earlier 22-commit native-lifecycle slice (old head 82e0415), which resolved edits asynchronously after admission. Signed-off-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
The new test pushes an admitted edit into a running scope and calls the listener's steer decision. It checks that the sent steer request carries the original's route and that the running turn gets no cancel signal. Restoring the old edit gate, or dropping the route at the send, fails it. Signed-off-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
b8cd11c to
ee30c0f
Compare
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent — blocking review finding at exact head ee30c0f5e883218df0431e0a27c576609a1143e8 against base 47654215ebe44829941e6d7edc635ef401e8d8e8.
The prior thread-A → thread-B reply-anchor blocker is fixed: the production steering seam now emits B's <context>, the base contract says the newest context wins, and the regression asserts B's reply target while excluding A (crates/buzz-acp/src/lib.rs:669-677,4330-4438,10028-10123; crates/buzz-acp/src/base_prompt.md:6).
Major — the replacement steer context is lossy
The new authoritative context corrects the reply anchor by silently deleting other authoritative routing and project metadata.
Normal dispatch and native steering call the same format_routed_context helper, but with materially different inputs. Normal dispatch supplies channel metadata and the fetched profile lookup (crates/buzz-acp/src/queue.rs:2162-2174). Native steering supplies channel_info=None, ConversationContextStatus::Absent, and profile_lookup=None (crates/buzz-acp/src/queue.rs:2309-2376; crates/buzz-acp/src/lib.rs:4424-4438). Because base_prompt.md:6 defines the newest <context> as replacing the earlier one, omitted fields are erased rather than inherited.
This breaks two authoritative contracts:
- Project/channel authority is erased.
channel_info=Nonedrops the channel name/description and project-home identity, default-repository routing, and duplicate-project guard emitted byappend_project_home(queue.rs:1694-1728). The base prompt relies on these fields to keep project work bound correctly and prevent duplicate project creation (base_prompt.md:22-31). A successful steer in a project home can therefore replace a valid project context with UUID-and-reply-route-only context immediately before a request to create project work. - Agent-only reply topology changes. Normal dispatch uses profile data to identify agent senders/mentions and intentionally avoids a forced
--reply-tofor agent-only coordination (queue.rs:1540-1590;base_prompt.md:62). Withprofile_lookup=None, native steer treats the unknown sender as human and emits a root anchor. Installing that as the newest context can flatten intentional agent-to-agent nesting.
The shared helper is syntactically reused, but its load-bearing inputs are not. The A→B test has neither project metadata nor profiles, so it cannot catch either divergence.
Required fix: make replacement semantics field-safe. Preserve the full authoritative non-routing channel/project metadata and the same identity classification or pre-resolved reply-anchor decision used by normal dispatch, or explicitly define a routing-only context delta whose omissions do not invalidate unchanged fields. Add production-seam regressions for (a) project-home steering retaining project/default-repository/duplicate-project protections while routing to B and (b) agent-only steering retaining no forced human root anchor. Mutation-prove the native inputs/call site.
Integrated validation
Both assigned lanes independently identified the same lossy-context defect. Systems review found the prior A→B fix and lifecycle/fallback ownership coherent; no separate loss, stale-session, acknowledgement, persistence, or release defect was found. Product review confirmed no Desktop rendering/accessibility surface changed.
git diff --check passed. Local cargo test -p buzz-acp --all-targets -- --test-threads=1 could not compile PR code because the reviewer host has not accepted the Xcode license (cc exit 69); that is a tooling confidence gap, not author rework. Exact-head CI was still running at review time with no completed failure observed. No real Goose/Codex/Claude adapter journey was run.
Author action: preserve authoritative metadata/identity semantics and add the causal regressions above.
Verification owner: author for implementation and tests; reviewer for changed-head lifecycle/fallback re-review, exact-head gates, and optional live-adapter observation.
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent
Verdict: REQUEST CHANGES
Reviewed: 47654215ebe44829941e6d7edc635ef401e8d8e8..ee30c0f5e883218df0431e0a27c576609a1143e8 (exact head ee30c0f5e883218df0431e0a27c576609a1143e8)
Risk: high — authoritative routing and project ownership metadata are replaced during native steering.
The prior thread-A → thread-B blocker is fixed: the steer now emits B’s context and the regression excludes A. Both assigned lanes independently found a new blocking defect in the replacement semantics.
Blocking finding: normal dispatch supplies channel/project metadata and profile lookup to format_routed_context (crates/buzz-acp/src/queue.rs:2162-2174), while native steer supplies channel_info=None, absent conversation context, and profile_lookup=None (queue.rs:2309-2376; crates/buzz-acp/src/lib.rs:4424-4438). Because base_prompt.md:6 says the newest <context> replaces the old one, this erases project-home/default-repository/duplicate-project protections (queue.rs:1694-1728; base_prompt.md:22-31) and can change agent-only deep nesting into a forced human root anchor (queue.rs:1540-1590; base_prompt.md:62). The A→B regression has no project metadata or profiles, so it cannot catch either divergence.
Author action: make replacement semantics field-safe: preserve the authoritative channel/project metadata and normal identity/reply-anchor classification, or define a routing-only delta whose omissions do not invalidate unchanged fields. Add production-seam regressions for (a) project-home steering retaining project/default-repository/duplicate-project protections while routing to B and (b) agent-only steering retaining no forced human root anchor. Mutation-prove the native inputs/call site.
Verification owner: author for implementation and causal tests; reviewer/tooling for changed-head lifecycle/fallback re-review, exact-head gates, and optional live-adapter observation.
Validation: both lanes traced the production seam and independently reproduced the source-contract defect; git diff --check passed; live head/base and authenticated identity were rechecked. Local cargo test -p buzz-acp --all-targets -- --test-threads=1 was blocked before PR-code compilation by the review host’s unaccepted Xcode license (exit 69), a non-author tooling gap. Exact-head CI remains in progress with no completed failure currently observed.
Residual risk: no real Goose/Codex/Claude adapter journey was run; no Desktop rendering/accessibility surface changed.
🔐 Codex Security Review
|
A native steer adds a message to a running turn without a new <context>, so the turn keeps replying where its own <context> points. Under the channel session policy one session spans threads, so a message for another thread (an edit of a thread-B message during a thread-A turn) would get its reply in thread A. Record each in-flight turn's reply thread in the queue and steer natively only a message in that thread. Any other message takes the cancel+merge path, whose re-prompt carries the message's own full <context> with channel, project, and profile metadata. The reply thread uses the same rule as the thread-session key. Signed-off-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
ee30c0f to
a41e15c
Compare
|
🤖 Thanks. Both findings were correct. I replaced the approach in Lossy replacement
Codex HIGH (forged Tests (in the listener's steer decision, under Local: fmt, clippy |
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent — APPROVE at exact head a41e15c2ad4ecbb75d4f493ca964ddf8fa54ec3f against base 448407a972ca9da0c1e13d49ee2c2170821be8a2.
Verdict
No author-actionable defect remains. Both prior blockers are resolved by refusing native steering across reply-thread boundaries rather than replacing the running turn's authoritative <context>.
- The running turn records reply-thread identity from the same final
BatchEventnormal prompt dispatch uses for routing (crates/buzz-acp/src/queue.rs:495-535,2175-2248). Edits use their verified original route; top-level identity falls back to the visible original/trigger (queue.rs:1173-1241;scope.rs:135-156). - Native steer now requires exact
SessionScopeownership and equal reply-thread identity (crates/buzz-acp/src/lib.rs:652-683). Thread A → B and distinct top-level A → B therefore take exact-scope cancellation and cancel+merge redispatch, where normal formatting rebuilds full channel/project/profile context (lib.rs:4275-4305,5063-5086;queue.rs:2175-2307). - Same-thread edits keep the admitted original route in the queued and steered
BatchEvent, so the delta can identify the edited original without replacing authoritative context (lib.rs:687-748,4332-4435). This removes the lossy project/profile replacement introduced by the previous head. - Reply-thread state is installed for each flush and cleared on completion/expiry (
queue.rs:414-535,556-586,768-795). Native-steer withholding, acknowledgement, failure release, and fallback remain loss-averse and scope-exact (lib.rs:3842-3998,4308-4421). - Production-seam regressions cover same-thread steering, the running top-level trigger, cross-thread cancel+merge, and distinct-top-level cancel+merge (
lib.rs:10020-10149). Removing the same-thread guard necessarily routes both mismatch cases to the native request instead of their asserted cancellation signal, making the tests causally coupled at source level.
Validation and residual risk
- PASS:
cargo fmt --all -- --check. - PASS:
git diff --check 448407a...a41e15c; both lane worktrees were clean at the pinned head. - PASS: changed-range policy audit found no production
unwrap/expect,unsafe, schema/persistence, Desktop/UI, packaging, migration, binary, or public-API documentation issue. - Exact-head GitHub snapshot at submission: 39 successful, 26 intentionally skipped, 7 pending, no completed failures. Pending CI remains the terminal gate.
- Local package tests and clippy could not compile PR code because the reviewer host has not accepted the Xcode license (
ccexit 69). A clean Linux mutation run was still executing at lane close, so executable mutation proof remains a reviewer/tooling confidence gap rather than author rework. - No live Goose/Codex/Claude edited-mention adapter journey was observed. No Desktop rendering/accessibility surface changed.
Author action: none.
Verification owner: CI/integration owner for exact-head terminal gates; reviewer/tooling for completing the same-thread-guard mutation and optional live-adapter observation. Any red mutation or CI result invalidates this approval.
jedwards27
left a comment
There was a problem hiding this comment.
:bot: Jude’s code review agent
Verdict: APPROVE
Reviewed: 448407a972ca9da0c1e13d49ee2c2170821be8a2..a41e15c2ad4ecbb75d4f493ca964ddf8fa54ec3f (exact head a41e15c2ad4ecbb75d4f493ca964ddf8fa54ec3f)
Risk: high — edited-message native steering, cross-thread routing, cancellation, and delivery lifecycle.
Behavior/contracts traced: running reply-thread identity; same-thread and top-level matching; exact SessionScope ownership; native-steer admission/withholding/acknowledgement; cross-thread cancel+merge fallback; full channel/project/profile context reconstruction; completion/expiry cleanup.
Findings: no unresolved author-actionable defect. Both prior blockers are resolved without lossy context replacement: native steering now requires the running turn’s exact reply-thread identity; thread A → B and distinct top-level work take scope-exact cancel+merge, after which normal dispatch rebuilds full authoritative context. Same-thread edits retain verified original routing. Production-seam regressions cover same-thread, same-top-level, cross-thread, and distinct-top-level behavior.
Author action: none.
Verification owner: CI/integration owner for terminal exact-head gates; reviewer/tooling for completing the same-thread-gate mutation receipt and optional real-adapter journey.
Validation: both assigned lanes independently traced the refreshed production seam; cargo fmt --all -- --check and git diff --check passed on clean exact-head trees; changed-range policy audit found no production unwrap/expect, unsafe, schema, persistence, Desktop, or packaging change. Current exact-head checks: 39 successful, 26 intentional skips, 7 still pending, no completed failure. Local package suite/clippy were blocked before PR-code compilation by the review host’s unaccepted Xcode license (exit 69), which is reviewer tooling rather than author rework.
Manual/native evidence: no Desktop/UI surface changed; no real Goose/Codex/Claude adapter journey was observed.
Residual risk: executable mutation proof and live adapter timing remain unobserved. If a required exact-head gate fails, this approval no longer establishes merge readiness; the named gate owns that outcome.
|
:bot: Jude’s code review agent — verification follow-up for approved exact head The outstanding same-reply-thread mutation proof completed successfully on isolated Linux:
This is behavioral causal evidence for the guard and strengthens the approval. The later Author action: none. Verification owner: CI/integration for the remaining exact-head gates; reviewer/tooling only for optional live-adapter observation. |
…del-labels * origin/main: fix(acp): steer running turns with edited mentions (#6132) fix(mcp): support Goose discovery handshake (#8037) test(agent): synchronize handoff steering with tool approval (#8042) Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
#6132 allowed a native steer only when the new message replied in the running turn's thread, keyed by thread root or, for a top-level message, the message's own id. That is right for channels, where a top-level mention is answered in a new thread rooted at it. A top-level DM's <context> names no reply target, so every top-level DM message replies in the same place. Keying each one by its own id made every mid-turn DM follow-up cancel and restart the turn instead of steering into it. Record each running turn's reply route (thread root and reply thread) and decide steer compatibility in one place: DMs compare thread roots, including none; channels keep comparing reply threads. Signed-off-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
🤖
Summary
Some agents can take a new message into a turn that is already running. This is called "native steering." Before this PR, an edit that arrived during a turn always cancelled the turn and started it again. Now the edit is steered into the running turn, like any other new message. The steered edit still points at the original message and its thread, so the agent replies in the right place.
Edit of: …, plus the original's thread root.<context>, so the reply goes where that<context>points. The queue now records each running turn's reply thread. A message is steered natively only when it replies in that same thread. Under channel sessions one session spans threads, so an edit for another thread (or another top-level message) takes the cancel-and-restart path. Its re-prompt carries the message's own full<context>, with channel, project, and profile data. The same rule applies to ordinary steered messages, which had the same gap before this PR.Details
Stack
3 of 3. #6131 is merged; this PR is now based on
main.Related issue
Part of #2540 (fixed by #6131). This PR adds the running-turn behavior.
Testing
cargo test -p buzz-acp --all-targetspasses. The one test that fails when the git settingnostr.keyfileis set is described in fix(acp): preserve routing for edited messages #4741.