mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
🤖 ## Summary Some agents can take a new message into a turn that is already running. This is called "native steering": the agent keeps working and folds the new message in, instead of stopping and starting over. #6132 made this safer in channels: a message is steered in only when it belongs where the running turn will reply. In a channel, each top-level message gets its own reply thread, so a second top-level message must not be steered into a turn that replies in the first one's thread. That rule also ran in DMs, where it does not fit. In a DM, a top-level message is answered at the top of the conversation, not in a new thread. So every top-level DM message replies in the same place. But the rule treated each one as its own thread. As a result, **every DM follow-up sent while the agent was working cancelled and restarted the turn**, instead of being steered in as it was before #6132. Now: - **DMs:** a follow-up is steered natively when it replies in the same place as the running turn: both top-level, or both in the same thread. A thread reply during a top-level DM turn (or the reverse) still takes the cancel-and-restart path, because the running turn would reply in the wrong place. - **Channels:** unchanged from #6132. ## Details - The queue now records each running turn's reply route: its thread root (or none) and its reply thread. One function, `ReplyRoute::accepts_steer`, decides whether a new message may be steered in. It compares thread roots in DMs and reply threads in channels. - The DM rule mirrors how the prompt is built: a top-level DM's `<context>` has no `--reply-to` target, and a threaded DM's target is in its thread. - The steer decision must agree with how the running turn's prompt was written. The prompt task records whether it writes the prompt as a DM, and the steer decision reads that recorded value. So if channel metadata was missing when the turn started (the prompt then uses the normal channel form) and loads later, the DM rule is not applied to that turn. - The prompt task records this value as soon as channel metadata resolves, before session setup. A follow-up sent during a slow `session/new` is steered in, as on main. When an `initial_message` setup prompt runs first, the value is recorded after that setup turn, because the setup prompt reads the same steer mailbox. - Before the value is recorded, the steer decision uses the listener's own channel classification. A channel the listener knows is not a DM is always written in channel form, so the channel rule applies at once, exactly as on main. A DM, or a channel whose type is not resolved, takes the cancel-and-restart path until the value is recorded. ### Related issue Follow-up to #6132 (merged). Found in review after merge. No separate issue. ### Testing - New tests drive DM messages through the listener's real steer decision with a running turn. A second top-level DM message is steered natively with no cancel. A DM thread reply during a top-level DM turn is not steered and takes cancel-and-restart. - Direct tests of the steer rule cover channel and DM cases: same thread, different thread, top-level after top-level, and top-level mixed with thread replies. - With the DM rule disabled, the DM rule test and the top-level DM listener test fail. - Listener tests also cover a DM turn whose prompt was written as a channel (top-level follow-up is not steered) and a DM follow-up that arrives before the value is recorded (not steered). - Production-path tests dispatch a real turn through `dispatch_pending` and `run_prompt_task` to an agent that holds at `session/new` or `session/prompt`, then send a follow-up through the real listener. A top-level DM follow-up during the prompt or during `session/new` is steered with no cancel. A DM follow-up before an `initial_message` setup turn is not steered. A same-thread channel follow-up during setup is steered with no cancel. - Mutation checks: each fails its test. They are: not recording the value in `run_prompt_task`, passing a detached value from dispatch, not recording it before session setup, recording it before an `initial_message` turn, and removing the known-channel rule for the unrecorded case. - `cargo test -p buzz-acp` passes on the rebased head, with the timing-sensitive `acp::tests` run alone (`--test-threads=1`; this PR does not change them, and they fail only under full parallel load on this laptop). `cargo clippy -p buzz-acp --tests -D warnings` and `just desktop-tauri-clippy` are clean. --------- Signed-off-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>