Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions crates/buzz-acp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,9 @@ Start with **N=2** for most deployments. Increase if queue depth grows under loa

## Forum Channels

By default, the ACP harness subscribes to stream message kinds (9, 46010, 40007). To receive forum events, opt in with `--kinds` and disable the mention filter (forum posts don't @mention agents):
By default, the ACP harness subscribes to mentioned stream messages, workflow approval requests, reminders, forum posts, and forum comments (kinds 9, 46010, 40007, 45001, and 45003). The default mention filter still applies, so unmentioned forum events are ignored.

To receive every forum post, comment, and vote—including events that do not mention the agent—opt in to kind 45002 and disable the mention filter:

**CLI flags:**
```bash
Expand All @@ -246,7 +248,7 @@ Forum event kinds:
- **45002** — Vote on a post or comment
- **45003** — Comment reply on a forum post

> **Note:** Without `--no-mention-filter` (or `require_mention = false`), the default `subscribe=mentions` mode filters events that don't @mention the agent — forum posts will be invisible.
> **Note:** `--no-mention-filter` (or `require_mention = false`) is only needed for unmentioned events. Without it, the default `subscribe=mentions` mode still receives forum posts and comments that mention the agent.

## How It Works

Expand Down
8 changes: 8 additions & 0 deletions crates/buzz-acp/src/base_prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ When in doubt, prefer the reply destination explicitly supplied in `[Context]`.

All replies and delegations — including task assignments to other agents — go to the **same channel where you were tagged** (use the channel UUID from `[Context]`). Never post responses or assignments to a different channel unless the user explicitly requests it.

### Channel Creation

When creating a channel, use `buzz channels create --type stream` unless the owner explicitly requests a forum. Forum channels are a preview feature and may be hidden for owners who have not enabled them; when the request is ambiguous, ask before using `--type forum`.

### Forum Channels

Forum channels are not stream channels, and the reply kind must match the thread root. Before replying, inspect the supplied `Thread root kind` in `[Context]`; only if the kind is unavailable, fetch the root with `buzz messages thread --channel <UUID> --event <root-id>` before choosing a kind. Use the stream default kind `9` for replies beneath kind-`9`, legacy kind-`40002`, reminder kind-`40007`, kind-`40008` diff, and workflow approval kind-`46010` stream roots, even if the channel also hosts forum posts. For a new forum thread, send kind `45001`: `buzz messages send --channel <UUID> --kind 45001 --content "..."`. Only beneath a kind-`45001` forum root, send replies as kind `45003` with the supplied `--reply-to <event-id>`. Never send kind `45003` beneath stream roots.

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 Keep the fallback root lookup in the triggering channel

When thread context is unavailable and a crafted event in channel A references a root in another readable channel B, this newly recommended fallback returns B's root because cmd_get_thread builds its root filter with only ids and limit (crates/buzz-cli/src/commands/messages.rs:418-421), without #h. The agent can consequently choose the kind from B and attempt a cross-channel reply, which the relay rejects for a mismatched parent. Scope the fallback root query to the supplied channel before directing agents to rely on it.

AGENTS.md reference: AGENTS.md:L146-L147

Useful? React with 👍 / 👎.


### General

- Respond promptly to @mentions. Be direct — no preamble. Name what you did, what you found, or what you need.
Expand Down
26 changes: 24 additions & 2 deletions crates/buzz-acp/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1236,7 +1236,8 @@ pub fn resolve_channel_filters(
rules: &[SubscriptionRule],
) -> HashMap<Uuid, ChannelFilter> {
use buzz_core::kind::{
KIND_STREAM_MESSAGE, KIND_STREAM_REMINDER, KIND_WORKFLOW_APPROVAL_REQUESTED,
KIND_FORUM_COMMENT, KIND_FORUM_POST, KIND_STREAM_MESSAGE, KIND_STREAM_REMINDER,
KIND_WORKFLOW_APPROVAL_REQUESTED,
};

let target_channels: Vec<Uuid> = if let Some(ref overrides) = config.channels_override {
Expand All @@ -1256,6 +1257,8 @@ pub fn resolve_channel_filters(
let kinds = config.kinds_override.clone().unwrap_or_else(|| {
vec![
KIND_STREAM_MESSAGE,
KIND_FORUM_POST,
KIND_FORUM_COMMENT,
Comment thread
loganj marked this conversation as resolved.
KIND_WORKFLOW_APPROVAL_REQUESTED,
KIND_STREAM_REMINDER,
]
Expand Down Expand Up @@ -1338,7 +1341,8 @@ pub fn resolve_dynamic_channel_filter(
rules: &[crate::filter::SubscriptionRule],
) -> Option<ChannelFilter> {
use buzz_core::kind::{
KIND_STREAM_MESSAGE, KIND_STREAM_REMINDER, KIND_WORKFLOW_APPROVAL_REQUESTED,
KIND_FORUM_COMMENT, KIND_FORUM_POST, KIND_STREAM_MESSAGE, KIND_STREAM_REMINDER,
KIND_WORKFLOW_APPROVAL_REQUESTED,
};

// In Mentions/All mode, if the operator explicitly constrained channels
Expand All @@ -1361,6 +1365,8 @@ pub fn resolve_dynamic_channel_filter(
kinds: Some(config.kinds_override.clone().unwrap_or_else(|| {
vec![
KIND_STREAM_MESSAGE,
KIND_FORUM_POST,
KIND_FORUM_COMMENT,
KIND_WORKFLOW_APPROVAL_REQUESTED,
KIND_STREAM_REMINDER,
]
Expand Down Expand Up @@ -1507,11 +1513,27 @@ mod tests {
assert!(f.require_mention, "mentions mode requires mention");
let kinds = f.kinds.as_ref().expect("should have kinds");
assert!(kinds.contains(&buzz_core::kind::KIND_STREAM_MESSAGE));
assert!(kinds.contains(&buzz_core::kind::KIND_FORUM_POST));
assert!(kinds.contains(&buzz_core::kind::KIND_FORUM_COMMENT));
assert!(kinds.contains(&buzz_core::kind::KIND_WORKFLOW_APPROVAL_REQUESTED));
assert!(kinds.contains(&buzz_core::kind::KIND_STREAM_REMINDER));
}
}

#[test]
fn test_dynamic_mentions_mode_default_kinds_include_forum_events() {
let config = test_config(SubscribeMode::Mentions);
let filter = resolve_dynamic_channel_filter(&config, Uuid::new_v4(), &[])
.expect("dynamic channel should be subscribed");
let kinds = filter.kinds.expect("mentions mode should constrain kinds");

assert!(kinds.contains(&buzz_core::kind::KIND_STREAM_MESSAGE));
assert!(kinds.contains(&buzz_core::kind::KIND_FORUM_POST));
assert!(kinds.contains(&buzz_core::kind::KIND_FORUM_COMMENT));
assert!(kinds.contains(&buzz_core::kind::KIND_WORKFLOW_APPROVAL_REQUESTED));
assert!(kinds.contains(&buzz_core::kind::KIND_STREAM_REMINDER));
}

#[test]
fn test_mentions_mode_custom_kinds() {
let mut config = test_config(SubscribeMode::Mentions);
Expand Down
52 changes: 47 additions & 5 deletions crates/buzz-acp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ use std::time::Duration;
use acp::{AcpClient, EnvVar, McpServer};
use anyhow::Result;
use buzz_core::kind::{
KIND_MEMBER_ADDED_NOTIFICATION, KIND_MEMBER_REMOVED_NOTIFICATION, KIND_STREAM_MESSAGE,
KIND_STREAM_REMINDER, KIND_WORKFLOW_APPROVAL_REQUESTED,
KIND_FORUM_COMMENT, KIND_FORUM_POST, KIND_MEMBER_ADDED_NOTIFICATION,
KIND_MEMBER_REMOVED_NOTIFICATION, KIND_STREAM_MESSAGE, KIND_STREAM_REMINDER,
KIND_WORKFLOW_APPROVAL_REQUESTED,
};
use buzz_core::observer::{
decrypt_observer_payload, encrypt_observer_payload, OBSERVER_FRAME_TELEMETRY,
Expand Down Expand Up @@ -1442,6 +1443,8 @@ async fn tokio_main() -> Result<()> {
kinds: config.kinds_override.clone().unwrap_or_else(|| {
vec![
KIND_STREAM_MESSAGE,
KIND_FORUM_POST,
KIND_FORUM_COMMENT,
KIND_WORKFLOW_APPROVAL_REQUESTED,
KIND_STREAM_REMINDER,
]
Expand Down Expand Up @@ -3038,15 +3041,29 @@ fn spawn_failure_notice(
content: String,
) {
if let Some(rest) = rest_client {
let thread_tags = batch
let (thread_tags, triggering_kind, triggering_event_id) = batch
.events
.last()
.map(|be| queue::parse_thread_tags(&be.event))
.map(|be| {
(
queue::parse_thread_tags(&be.event),
be.event.kind.as_u16() as u32,
Some(be.event.id),
)
})
.unwrap_or_default();
let rest = rest.clone();
let channel_id = batch.channel_id;
tokio::spawn(async move {
pool::post_failure_notice(&rest, channel_id, &thread_tags, &content).await;
pool::post_failure_notice(
&rest,
channel_id,
&thread_tags,
triggering_kind,
triggering_event_id,
&content,
)
.await;
});
}
}
Expand Down Expand Up @@ -3641,6 +3658,31 @@ mod agent_draft_prompt_tests {
.contains("add them explicitly with `buzz channels add-member` only when authorized"));
assert!(prompt.contains("never changes membership automatically"));
}

#[test]
fn shared_base_prompt_defaults_channel_creation_to_stream() {
let prompt = include_str!("base_prompt.md");
assert!(prompt.contains("buzz channels create --type stream"));
assert!(prompt.contains("unless the owner explicitly requests a forum"));
assert!(prompt.contains("may be hidden for owners who have not enabled them"));
assert!(prompt.contains("ask before using `--type forum`"));
}

#[test]
fn shared_base_prompt_distinguishes_forum_kinds_from_stream_messages() {
let prompt = include_str!("base_prompt.md");
assert!(prompt.contains("Forum channels are not stream channels"));
assert!(prompt.contains("kind `45001`"));
assert!(prompt.contains("kind `45003`"));
assert!(prompt.contains("stream default kind `9`"));
assert!(prompt.contains(
"legacy kind-`40002`, reminder kind-`40007`, kind-`40008` diff, and workflow approval kind-`46010` stream roots"
));
assert!(prompt.contains("inspect the supplied `Thread root kind` in `[Context]`"));
assert!(prompt.contains("only if the kind is unavailable"));
assert!(prompt.contains("buzz messages thread --channel <UUID> --event <root-id>"));
assert!(prompt.contains("Never send kind `45003` beneath stream roots"));
}
}

fn default_heartbeat_prompt() -> String {
Expand Down
Loading
Loading