buzz-acp: no at-most-once reply fence keyed on triggering event id (in-memory per-process dedup lets duplicate daemons / reconnect replay / restart double-reply)
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
## Summary
`buzz-acp` deduplicates events only **within a single running process**, and only **while a prompt is in-flight** (`EventQueue.in_flight_channels` + `DedupMode` Drop/Queue in `crates/buzz-acp/src/queue.rs`). Pool/runtime state is explicitly never persisted across restart or respawn (`crates/buzz-acp/src/pool.rs`: "never persisted, gone on restart/respawn"). There is no persisted or cross-process record of "this agent already replied to triggering event X."
As a result, any time two independent processors observe the same mention, **both reply**:
- two daemons for the same (agent, community) — see #5759;
- a daemon that reconnects and re-fetches via `since` filters (`relay.rs`) after the reply was already sent;
- a restart/respawn that replays recent backfill.
We observed a live **triple-reply to a single mention** on a hosted relay. #5759 is the concrete spawn trigger in our case.
## Why this is separate from #5759
#5759 fixes the *cause* (do not spawn or keep a duplicate daemon). This issue is the *belt to that suspenders*: an at-most-once send fence makes a duplicate reply structurally impossible regardless of how many processors fire, and it also covers the reconnect-replay and restart paths that daemon reaping alone does not.
## Proposed fix
Before emitting a reply turn for a triggering event, gate on (agent_pubkey, triggering_event_id):
- **Cheap first cut:** check the relay / local cache for an existing kind-9 from this agent whose `e` tag references the triggering event, and skip the turn if present.
- **Durable layer:** persist processed triggering-event-ids (bounded, per agent) so the gate survives restart/respawn and is shared across a single host's daemons.
Race note: two daemons can both pass a relay-read check inside the propagation window, so the persisted/shared gate is the reliable layer and the relay-read is only a cheap first cut.
## Environment
- Relay: hosted `*.communities.buzz.xyz`
- `buzz-acp` from current `main`
- Observed: a managed agent replying 3x to one `@mention` in a private channel, after a second community was added to the same Desktop.
Contributor guide
Research direction
Start by reading crates/buzz-acp/src/queue.rs for in-memory deduplication, crates/buzz-acp/src/pool.rs for restart behavior, and relay.rs for since-based refetching. Define how the triggering event id is fenced across daemons and restarts, then verify duplicate processors, reconnect replay, and restart backfill produce at most one reply.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- backend, distributed-systems
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100