diff --git a/crates/buzz-acp/src/base_prompt.md b/crates/buzz-acp/src/base_prompt.md index e360d24982..0a18c272a0 100644 --- a/crates/buzz-acp/src/base_prompt.md +++ b/crates/buzz-acp/src/base_prompt.md @@ -53,7 +53,9 @@ For explicit changes to an existing personal agent, use `buzz agents draft-updat Use the reply destination supplied in the `[Context]` block for ordinary replies in this turn. Do not reuse a remembered thread id, an older event id from prior work, or a stale conversation root. -For human-facing work, keep the conversation flat and easy to read. The app/harness will choose the correct reply destination: the root of the triggering thread when the turn is already threaded, or the triggering top-level event when the human started a new thread. +Direct messages are always flat. Send each ordinary DM response as a top-level message in the conversation. Do not pass `--reply-to` in a DM, even when the incoming DM carries thread tags from an earlier exchange. + +For human-facing channel work, keep the conversation flat and easy to read. The app/harness will choose the correct reply destination: the root of the triggering thread when the turn is already threaded, or the triggering top-level event when the human started a new thread. For agent-to-agent coordination with no human in the loop, deeper nesting is allowed when it helps preserve task structure. Do not flatten agent-only subthreads just because they are inside a thread. diff --git a/crates/buzz-acp/src/lib.rs b/crates/buzz-acp/src/lib.rs index 403512a322..e35bcc70e2 100644 --- a/crates/buzz-acp/src/lib.rs +++ b/crates/buzz-acp/src/lib.rs @@ -3626,6 +3626,13 @@ mod agent_draft_prompt_tests { assert!(prompt.contains("buzz messages send ... --content -")); } + #[test] + fn shared_base_prompt_teaches_flat_dm_replies() { + let prompt = include_str!("base_prompt.md"); + assert!(prompt.contains("Direct messages are always flat")); + assert!(prompt.contains("Do not pass `--reply-to` in a DM")); + } + #[test] fn shared_base_prompt_teaches_single_command_mentions_and_preflight() { let prompt = include_str!("base_prompt.md"); diff --git a/crates/buzz-acp/src/queue.rs b/crates/buzz-acp/src/queue.rs index 029bf86dbf..889ce81d7d 100644 --- a/crates/buzz-acp/src/queue.rs +++ b/crates/buzz-acp/src/queue.rs @@ -1156,6 +1156,18 @@ fn append_reply_instruction(s: &mut String, event_id: &str) { )); } +/// Append the reply instruction for direct messages. +/// +/// DMs use a flat timeline instead of threads, even when the triggering event +/// carries legacy thread tags. +fn append_dm_reply_instruction(s: &mut String) { + s.push_str( + "\nIMPORTANT: DMs are a flat conversation. Send an ordinary message to \ + this channel and keep it at the top level. Do not pass `--reply-to` to \ + `buzz messages send`.", + ); +} + /// Append a new-thread reply instruction for a human-facing top-level mention. /// /// The triggering mention has no thread tags, so the agent's reply becomes the @@ -1226,10 +1238,10 @@ fn resolve_reply_anchor( /// Format a `[Context]` hints section based on event scope. /// /// `reply_anchor` is the pre-resolved `--reply-to` target for this turn (see -/// [`resolve_reply_anchor`]). In the thread/DM branches it threads ordinary -/// replies; in the channel branch a `Some` anchor means a human-facing -/// top-level mention whose reply should open a new thread rooted at the -/// triggering event. +/// [`resolve_reply_anchor`]). In the thread branch it threads ordinary replies; +/// in the channel branch a `Some` anchor means a human-facing top-level mention +/// whose reply should open a new thread rooted at the triggering event. DMs +/// always remain flat and ignore reply anchors. fn format_context_hints( channel_id: Uuid, channel_info: Option<&PromptChannelInfo>, @@ -1272,10 +1284,8 @@ fn format_context_hints( s.push_str(&format!("\nParent: {parent}")); } } - if let Some(event_id) = reply_anchor { - append_reply_instruction(&mut s, event_id); - } } + append_dm_reply_instruction(&mut s); s } else if let Some(ref root) = thread_tags.root_event_id { let ctx_hint = if has_conversation_context { @@ -1463,13 +1473,10 @@ pub fn format_prompt(batch: &FlushBatch, args: &FormatPromptArgs<'_>) -> Vec