Skip to content

feat(relay): notify channel when an offline agent is @mentioned#2296

Open
Bartok9 wants to merge 1 commit into
block:mainfrom
Bartok9:bartok9/offline-agent-mention-notice
Open

feat(relay): notify channel when an offline agent is @mentioned#2296
Bartok9 wants to merge 1 commit into
block:mainfrom
Bartok9:bartok9/offline-agent-mention-notice

Conversation

@Bartok9

@Bartok9 Bartok9 commented Jul 21, 2026

Copy link
Copy Markdown

Refs #1743

Problem

When agent A @mentions agent B and B is offline (no active WebSocket subscription), the relay fan-out (dispatch_persistent_event_inner) delivers nothing to B and the mention is silently lost — no signal to the sender, the channel, or any human. As reported in #1743 (filed by agent Ferret, co-authored with Timpani), this caused a real 30-minute silent stall when Timpani delegated to an offline Fizz. Any agent-to-agent workflow that delegates via @mention is fragile when the target may be offline.

Why it matters

Agents are first-class citizens in Buzz. Silent mention loss breaks agent-to-agent delegation with no fallback at the agent layer (the send call returns success regardless of whether anyone is subscribed). At minimum, the mention must not vanish without a trace.

What this changes (Option 1 from the issue — relay system notice)

After a kind:9 (KIND_STREAM_MESSAGE) message is dispatched, the relay:

  1. Collects p-tagged pubkeys on the message.
  2. Intersects them with bot members of the community (human recipients read history on reconnect; only agents can be "offline" in a way the sender can't otherwise see).
  3. Checks presence via get_presence_bulk.
  4. For each mentioned bot with no presence key, emits a relay-signed system notice into the channel:
    { "type": "agent_mention_undelivered", "agent_pubkey": "<hex>",
      "agent_name": "<name>", "message": "<name> is offline and may not see this mention." }

This is client-agnostic (works for desktop, mobile, third-party NIP-29 clients) because it lives at the relay.

Design / safety

  • Best-effort, spawned side-effect. Runs after the triggering write commits; never adds latency to ingest ack and never fails the event. Presence/DB lookup errors are logged and skipped.
  • Reuses emit_system_message (kind:40099) — no new event-kind machinery.
  • Only agents flagged. Human users are never reported; online agents (any presence status) are never reported.
  • No auto-start / no ack protocol. Deliberately excludes Option 2 (relay auto-start) and Option 4 (delivery receipts) — larger changes that can layer on top of this seam.
  • Pure gating logic extracted to select_offline_mentioned_bots for unit testing without DB/redis.
  • New buzz_agent_mention_undelivered_total metric for observability.

How to test

cargo test -p buzz-relay offline_mentioned_bots   # 2 passed
cargo clippy -p buzz-relay                          # clean
cargo fmt -p buzz-relay --check

Unit tests cover: only offline bot members are selected (online agents and non-bot humans excluded), and empty result when all mentioned are online or non-bots.

Manual: start two agents in a channel, stop one, @mention it → a system notice appears in the channel naming the offline agent.

Signed-off-by DCO.

When a channel message @mentions an agent (bot member) that has no active
WebSocket subscription, the relay fan-out silently delivers nothing to it and
the mention is lost with no signal to the sender, channel, or any human
observer (issue block#1743 — a real 30-minute silent stall between two agents).

After a kind:9 (KIND_STREAM_MESSAGE) message is dispatched, this checks any
`p`-tagged pubkeys that are bot members against presence. For each mentioned
bot with no presence key, it emits a relay-signed system notice
(agent_mention_undelivered) into the channel: "<agent> is offline and may not
see this mention." Human recipients and online agents are never flagged.

- Best-effort, spawned side-effect: never adds latency to dispatch and never
  fails the triggering event; presence/DB errors are logged and skipped.
- Reuses the existing emit_system_message helper (kind:40099).
- Pure gating logic extracted to select_offline_mentioned_bots with unit tests.

Refs block#1743

Signed-off-by: Bartok9 <danielrpike9@gmail.com>
@BradGroux

Copy link
Copy Markdown

Blocking evidence against current head 359dce52e5adbc9035f4a554f7f10e8431ee8b39: the implementation conflates structural reply routing with an explicit @mention.

Wire-format evidence

Current Desktop reply construction always begins with a structural author tag:

const tags: string[][] = [
  ["p", authorPubkey],
  ["h", channelId],
];

See buildReplyTags. Explicit composer mentions are added as additional p tags, but the wire event does not let this PR distinguish them by reading all p tags.

This PR's ingest hook collects every p tag on a kind-9 event, intersects that list with bot members, checks presence, and emits agent_mention_undelivered for absent bots. Therefore this ordinary flow produces a false warning:

  1. an agent authors a message;
  2. the agent goes offline;
  3. a human replies to that message without typing an @mention;
  4. buildReplyTags adds the agent's structural author p tag;
  5. this PR reports that the human explicitly mentioned an offline agent.

PR #1862 received this exact review finding and moved toward explicit mention metadata. This PR should not reintroduce the ambiguity through a different notification surface.

Required correction

Use an unambiguous explicit-mention signal rather than treating all p tags as mentions. Then add an ingest-to-visible-notice test matrix covering:

  • structural reply-author p tag only → no offline-mention notice;
  • explicit mention of an offline agent → exactly one notice;
  • explicit mention of an online agent → no notice;
  • human p recipient → no agent notice;
  • duplicate/malformed tags → deduplicated or ignored safely;
  • presence lookup or system-message emission failure → original message remains accepted.

Please also document the guarantee accurately: presence is a useful reachability warning, not a delivery receipt proving that a particular harness subscription admitted the event.

Separate end-to-end response timing evidence is tracked in #2386; this comment is about notification correctness.

@Bartok9

Bartok9 commented Jul 22, 2026

Copy link
Copy Markdown
Author

Thanks — this is a fair and precise catch. You're right that intersecting all p tags with bot members conflates structural reply-author routing (buildReplyTags always emitting the author p) with an explicit composer @mention, so an ordinary human reply to an offline agent would trip a false agent_mention_undelivered.

I'll rework this to key off an unambiguous explicit-mention signal rather than the raw p-tag set, aligned with the direction #1862 took toward explicit mention metadata (not reusing the structural author tag). I'll add the ingest→visible-notice test matrix you listed:

  • structural reply-author p only → no notice
  • explicit mention of offline agent → exactly one notice
  • explicit mention of online agent → no notice
  • human p recipient → no agent notice
  • duplicate/malformed tags → dedup/ignore
  • presence-lookup / system-message emission failure → message still accepted

And I'll fix the docs to frame presence as a reachability warning, not a delivery receipt. E2E timing stays scoped to #2386. Will push the revision.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants