From a24b44368cb1362b382cbde33b4f1ccf463dabe7 Mon Sep 17 00:00:00 2001 From: iroiro147 Date: Sat, 1 Aug 2026 06:40:44 +0530 Subject: [PATCH] fix(buzz-acp): never deliver ephemeral events to agent subprocesses (#3649) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When an agent runs subscribe=all (or any wildcard rule) with no curated BUZZ_ACP_KINDS allowlist, the resolved rule set admits every kind on the wire — including kind 20001 presence updates, kind 20002 typing indicators, and kind 24200 observer frames. Every keystroke in a channel emits dozens of these ephemeral events, and each one woke the agent subprocess into a full model turn: cost per keystroke for the operator and a false "agent is typing" signal in Desktop for whoever is watching. Add a design-level invariant to the shared delivery filter crates/buzz-acp/src/filter.rs, match_event: ephemeral events (20000–29999) are never delivered to an agent subprocess, regardless of any subscription rule that might otherwise admit them. Ephemeral events are transient by definition — they are not actionable work and must not wake an agent process. Closes #3649. Signed-off-by: Claude Signed-off-by: iroiro147 --- crates/buzz-acp/src/filter.rs | 58 +++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/crates/buzz-acp/src/filter.rs b/crates/buzz-acp/src/filter.rs index 43edd969dd..617eb7a374 100644 --- a/crates/buzz-acp/src/filter.rs +++ b/crates/buzz-acp/src/filter.rs @@ -373,6 +373,26 @@ pub async fn match_event( ) -> Option { let filter_ctx = FilterContext::from_event(event, channel_id); + // Ephemeral events (kind 20000–29999) are never delivered to the agent, + // regardless of any subscription rule that might otherwise admit them. + // + // When an agent runs `subscribe=all` (or a wildcard `Config` rule, or an + // `All` mode with no explicit `BUZZ_ACP_KINDS` allowlist) with no curated + // kind list, these events would otherwise wake the agent subprocess on + // every keystroke: a human composing one message emits dozens of typing + // indicators (kind 20002) and presence updates (kind 20001), each of + // which starts a model turn at cost per keystroke and flashes Desktop's + // "agent is typing" badge. Observer frames (kind 24200) are caller-local + // telemetry on the same channel and belong to the same exclusion. + // + // Per the design intent behind `SubscribeMode::All`, agents want all + // *actionable* kinds, not ambient per-keystroke noise — which these + // ephemeral events are. Failing closed here (never wake on any ephemeral + // kind) keeps the wildcard subscription safe to use. + if buzz_core::kind::is_ephemeral(event.kind.as_u16() as u32) { + return None; + } + for (index, rule) in rules.iter().enumerate() { // 1. Channel scope check. if !rule.channels.matches(&channel_id) { @@ -681,6 +701,44 @@ mod tests { assert!(result.is_none()); } + /// #3649: ephemeral events (kind 20000–29999 — typing, presence, observer + /// frames) must never wake an agent subprocess, even when a wildcard rule + /// would otherwise admit them. A `subscribe=all` agent sees the whole + /// event stream; without a guard, each keystroke fires a model turn. + #[tokio::test] + async fn test_match_event_ephemeral_never_wakes_agent_on_wildcard() { + let channel_id = any_channel(); + // A wildcard rule (empty kinds = match any kind) — the shape an + // `All` subscription with no curated list resolves to. + let rules = vec![make_rule( + "all-kinds", + ChannelScope::All("all".into()), + vec![], // wildcard: no kind filter + false, + None, + None, + )]; + + // Typing indicator (20002), presence update (20001), and observer + // frame (24200) — every ephemeral kind must be rejected with no match. + for ephemeral_kind in [20001u32, 20002, 24200] { + let event = make_event(ephemeral_kind, "typing..."); + let result = match_event(&event, channel_id, &rules, "").await; + assert!( + result.is_none(), + "ephemeral kind {ephemeral_kind} must never match any rule" + ); + } + + // Control: a non-ephemeral kind (kind 9, stream message) matches the + // same wildcard rule — the guard only rejects ephemeral kinds. + let event = make_event(9, "hello"); + let matched = match_event(&event, channel_id, &rules, "") + .await + .expect("non-ephemeral kind 9 must match the wildcard rule"); + assert_eq!(matched.rule_index, 0); + } + #[test] fn test_channel_scope_all() { let scope = ChannelScope::All("all".into());