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
4 changes: 3 additions & 1 deletion crates/buzz-acp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,9 +248,11 @@ Forum event kinds:

> **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.

After startup, confirm the flag in the journal: `mention_filter=off` means the `#p` gate is disabled; `mention_filter=on` under `subscribe=Mentions` means only @mentions wake the agent (#4228).

## How It Works

1. **Startup** — Spawns N agent subprocesses (default 1), sends ACP `initialize` to each, connects to the relay with NIP-42 auth.
1. **Startup** — Spawns N agent subprocesses (default 1), sends ACP `initialize` to each, connects to the relay with NIP-42 auth. Logs a one-line config summary (relay, pubkey, subscribe mode, **`mention_filter=on|off`**, dedup, respond_to, …) so operators can confirm `--no-mention-filter` took effect from the journal alone.
2. **Channel discovery** — Queries the relay REST API for accessible channels, subscribes to each.
3. **Event loop** — Listens for @mention events (kind 9 with the agent's pubkey in a `#p` tag). Events queue per channel.
4. **Prompting** — When events are pending and no prompt is in flight for that channel, drains all queued events for the oldest channel into a single batched prompt via ACP `session/prompt`.
Expand Down
45 changes: 43 additions & 2 deletions crates/buzz-acp/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,11 @@ pub struct CliArgs {
#[arg(long, env = "BUZZ_ACP_CHANNELS", value_delimiter = ',')]
pub channels: Option<Vec<String>>,

#[arg(long, env = "BUZZ_ACP_NO_MENTION_FILTER")]
#[arg(
long,
env = "BUZZ_ACP_NO_MENTION_FILTER",
help = "Disable the #p mention gate (logged as mention_filter=off in the startup summary)"
)]
pub no_mention_filter: bool,

#[arg(long, env = "BUZZ_ACP_CONFIG", default_value = "./buzz-acp.toml")]
Expand Down Expand Up @@ -1108,6 +1112,9 @@ impl Config {
}

/// Human-readable summary (no secrets).
///
/// Includes `mention_filter=on|off` so `--no-mention-filter` deployments are
/// distinguishable from gated `subscribe=mentions` in journal logs (#4228).
pub fn summary(&self) -> String {
let respond_to_detail = match &self.respond_to {
RespondTo::Allowlist => {
Expand All @@ -1123,7 +1130,7 @@ impl Config {
format!(" allowed_respond_to=[{}]", modes.join(","))
};
format!(
"relay={} pubkey={} agent_cmd={} {} mcp_cmd={} idle_timeout={}s max_turn={}s agents={} heartbeat={}s subscribe={:?} dedup={:?} meh={:?} ignore_self={} context_limit={} max_turns_per_session={} presence={} typing={} memory={} model={} permission_mode={} {}{}",
"relay={} pubkey={} agent_cmd={} {} mcp_cmd={} idle_timeout={}s max_turn={}s agents={} heartbeat={}s subscribe={:?} mention_filter={} dedup={:?} meh={:?} ignore_self={} context_limit={} max_turns_per_session={} presence={} typing={} memory={} model={} permission_mode={} {}{}",
self.relay_url,
self.keys.public_key().to_hex(),
self.agent_command,
Expand All @@ -1134,6 +1141,7 @@ impl Config {
self.agents,
self.heartbeat_interval_secs,
self.subscribe_mode,
if self.no_mention_filter { "off" } else { "on" },
self.dedup_mode,
self.multiple_event_handling,
self.ignore_self,
Expand Down Expand Up @@ -2454,6 +2462,39 @@ channels = "ALL"
);
}

#[test]
fn test_summary_mention_filter_on_by_default() {
let config = test_config(SubscribeMode::Mentions);
let s = config.summary();
assert!(
s.contains("mention_filter=on"),
"default config should show mention_filter=on, got: {s}"
);
}

#[test]
fn test_summary_mention_filter_off_when_no_mention_filter() {
let mut config = test_config(SubscribeMode::Mentions);
config.no_mention_filter = true;
let s = config.summary();
assert!(
s.contains("mention_filter=off"),
"--no-mention-filter should show mention_filter=off, got: {s}"
);
}

#[test]
fn test_summary_mention_filter_distinguishes_no_mention_filter() {
let mut gated = test_config(SubscribeMode::Mentions);
let mut ungated = test_config(SubscribeMode::Mentions);
ungated.no_mention_filter = true;
assert_ne!(
gated.summary(),
ungated.summary(),
"gated and ungated mentions mode must produce distinct summary lines"
);
}

#[test]
fn test_validate_allowlist_valid_entries() {
let entries = vec!["ab".repeat(32), "cd".repeat(32)];
Expand Down