buzz-acp: subscribe=all floods agent context with system/control events (typing indicators, presence, NIP-29 admin)
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
## Summary
`buzz-acp --subscribe all` subscribes with `kinds: None`, i.e. **every event in the channel is forwarded into the agent's model context** — including system/control traffic such as kind:20002 typing indicators, presence updates, and NIP-29 admin events (9000–9022). Prompt engineering can stop the agent from *replying* to these, but the tokens are already spent and latency already added. Non-content events should be filtered at the runtime layer, not left to the prompt.
## Current behavior
In `crates/buzz-acp/src/config.rs`, `SubscribeMode::All` builds channel filters with no kind restriction:
```rust
SubscribeMode::All => {
for ch in &target_channels {
result.insert(*ch, ChannelFilter {
kinds: config.kinds_override.clone(), // None unless BUZZ_ACP_KINDS is set
require_mention: false,
});
}
}
```
Contrast with `SubscribeMode::Mentions`, which ships a curated default (`KIND_STREAM_MESSAGE`, `KIND_WORKFLOW_APPROVAL_REQUESTED`, `KIND_STREAM_REMINDER`). `All` has no equivalent content-sensible default, so in a busy channel the model receives:
- kind:20002 typing indicators (ephemeral — relays don't even store these per NIP-01 20000–29999)
- presence / membership churn (NIP-29 9000–9022)
- reactions, pins, moderation events, etc.
Each becomes part of the prompt (with `context_limit` history), inflating token usage and response latency on every turn.
## Motivating production story
We run a small multi-agent setup on our own deployment: a "requirement recorder" agent (built-in `buzz-agent` + GLM) with `subscribe=all`, working alongside a Codex coding agent and a separate QA agent in the same channel.
The recorder agent **prematurely closed a requirement** because it mistook Codex's *self-reported* PASS for the independent QA PASS. We tightened its prompt: it may only close after seeing the QA agent's *own* precise `[QA-RESULT] PASS` message published *after* `[CODEX-RESULT-ACCEPTED]`.
Prompt-level guardrails like this work, but they fight against the noise: system events dilute the exact signal the agent must track, every turn costs tokens we didn't need to spend, and the extra context adds latency in a workflow that is already multi-hop. Filtering non-content events at the harness would make prompts shorter, cheaper, faster, and safer.
## Proposal
Give `subscribe=all` a content-aware default instead of `kinds: None`. Options, in increasing invasiveness:
1. **Exclude ephemeral kinds by default** (20000–29999 per NIP-01). Typing indicators are virtually never useful model input, and relays don't persist them anyway. Smallest, safest change.
2. **Curated default kind list for `All`** mirroring what `Mentions` already does (stream messages v1/v2, forum posts, workflow kinds, reminders) — still overridable via `BUZZ_ACP_KINDS`.
3. **A dedicated flag**, e.g. `--subscribe-filter content|all`, making the behavior explicit and discoverable.
Happy to implement whichever direction maintainers prefer.
## Workaround (for anyone hitting this today)
Set `BUZZ_ACP_KINDS` explicitly (e.g. `9,40002,40003,45001,45003`) to restrict what `subscribe=all` receives. It works, but requires every operator to hand-curate kind lists and to know the kind registry — a sensible runtime default would remove that burden.
Contributor guide
Assessment
This issue has not been assessed yet.