Skip to content

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

Open
Bartok9 wants to merge 3 commits 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 3 commits into
block:mainfrom
Bartok9:bartok9/offline-agent-mention-notice

Conversation

@Bartok9

@Bartok9 Bartok9 commented Jul 21, 2026

Copy link
Copy Markdown

Refs #1743

When an offline bot is @mentioned, emit a best-effort channel system notice instead of silent loss. Offline gating uses explicit ["mention", pubkey] only (never bare structural reply-author p). Intentional @mentions now emit parallel p + mention from SDK build_message, desktop composer/reply/edit, mobile send, and workflow_sink.

Tests: SDK parallel tags; relay extract/select offline matrix.

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

Bartok9 added a commit to Bartok9/buzz that referenced this pull request Jul 23, 2026
Brad review on block#2296: collecting all `p` tags false-positives when
buildReplyTags adds a structural reply-author `p` without an @mention.

- Extract mentions via tag kind "mention" only (desktop MENTION_REFERENCE_TAG)
- Dedup/skip malformed; ignore bare `p` tags
- Expand unit matrix for structural-p / online / offline / human / dedup
- Document presence as reachability warning, not delivery receipt

Refs block#1743

Signed-off-by: Bartok9 <danielrpike9@gmail.com>
@Bartok9
Bartok9 force-pushed the bartok9/offline-agent-mention-notice branch from 359dce5 to d2cb235 Compare July 23, 2026 17:20
@Bartok9

Bartok9 commented Jul 23, 2026

Copy link
Copy Markdown
Author

Revision pushed (Brad blocking review)

Head is now rebased on main and gates offline-agent notices on explicit mention tags only, not all p tags.

Evidence

  • Extract: explicit_mention_pubkeys_from_tags in crates/buzz-relay/src/handlers/event.rs — iterates tags where t.kind().to_string() == "mention", lowercases, dedupes, skips empty/malformed. Bare p tags are ignored (so structural reply-author p from desktop buildReplyTags cannot false-trigger).
  • Notify path still uses pure select_offline_mentioned_bots + best-effort spawn after dispatch (presence/emit failures log and return; original message already accepted).
  • Docs comment: presence = reachability warning, not a delivery receipt. E2E timing remains Why did four simple agent replies take 13–31 seconds? #2386.

Test matrix (unit)

cargo test -p buzz-relay offline_ --lib
cargo test -p buzz-relay explicit_mention --lib

All passed locally:

  • structural p only → empty mention set
  • explicit mention offline bot → selected once
  • explicit online bot / human-only → no offline selection
  • duplicate/case-variant/malformed mention → dedup/ignore
  • (async failure path documented: notify returns early on presence/DB/emit error without touching ingest result)

Relation to #1862

Focused relay-side notice on explicit mention tags. Desktop on main already emits ["mention", pubkey] via mergeOutgoingTagsWithReferenceMentions / MENTION_REFERENCE_TAG in useMentionSendFlow.ts. This intentionally does not re-implement cameronhotchkies’s full NOTICE-toast desktop stack in #1862 — complimentary seam, not claiming that work.

cc @BradGroux

@BradGroux

Copy link
Copy Markdown

The structural-p false-positive fix is correct, and the revised reachability wording is accurate. One blocker remains: the new explicit-signal gate does not cover the original agent-to-agent path yet.

buzz messages send resolves an authored @name and passes that pubkey to buzz_sdk::build_message (CLI path). The SDK emits only ["p", pubkey] for those mentions (builder, message call). The revised relay deliberately ignores every p tag and considers only custom mention tags (new gate). That means the issue's agent-A → offline-agent-B reproduction still produces no candidate and no warning.

The Desktop reference is narrower than the revision comment suggests: mergeOutgoingTagsWithReferenceMentions is used for the special “send without inviting” non-member-human path (call site). Ordinary Desktop mentions remain p tags. Mobile also emits ordinary resolved mentions as p tags (send path); its custom mention tag is likewise reserved for excluded non-member humans (composer path).

The added unit tests also stop at extraction and selection. They do not exercise signed event ingest through the spawned side effect, observe exactly one persisted/fanned-out system notice, or inject presence/emit failures to prove the original message remains accepted.

To close this cleanly:

  1. Establish one explicit-mention signal across the real authored-message producers, at minimum Buzz CLI/SDK and the ordinary Desktop/Mobile composer paths, while keeping structural reply-author p tags excluded.
  2. Add an ingest-to-visible-notice regression using a real message builder: structural reply only → none; offline explicit agent → exactly one; online agent/human → none; duplicate/malformed inputs safe; presence/emit failure → original accepted.
  3. Update the PR body’s current p-tag/client-agnostic claims to match the final contract.
  4. Run the repository-required full just test and just ci gates for this relay change.

I also ran the existing Buzz SDK mention regression at this exact head; it passed and confirms the current builder emits one deduplicated p tag.

@Bartok9

Bartok9 commented Jul 23, 2026

Copy link
Copy Markdown
Author

Confirmed, and thanks for the precise trace — this is a real gap, not wording. I over-corrected. Verified locally:

  • buzz_sdk::build_messagemention_tags emits only ["p", <hex>] for authored @name mentions (crates/buzz-sdk/src/builders.rs L187-L199), so the CLI path you cite produces no mention tag.
  • The custom mention tag on Desktop (mergeOutgoingTagsWithReferenceMentions) and Mobile (compose_bar.dart) is reserved for the excluded non-member-human "send without inviting" case, not ordinary resolved mentions.
  • Net effect: my revised relay gate ignores every p and only reads mention, so the issue's agent-A → offline-agent-B reproduction yields no candidate and no notice — exactly the regression you describe.

Plan to close cleanly, matching your four points:

  1. One explicit-mention contract at the producers. Add an explicit resolved-mention signal (["mention", <hex>]) alongside the existing p tag in buzz_sdk::build_message for CLI/SDK authored mentions, and emit the same for ordinary Desktop/Mobile composer mentions — while keeping the structural reply-author p from buildReplyTags out of that signal. Relay continues to gate on the explicit signal only, so both the false-positive (structural reply) and the false-negative (agent-to-agent) are covered by one contract.
  2. Ingest-to-visible-notice regression with a real builder (not just extraction/selection): structural reply-only → none; offline explicit agent → exactly one persisted/fanned-out notice; online agent / human-only → none; duplicate/malformed → safe; presence/emit failure injected → original message still accepted.
  3. Rewrite the PR body's p-tag / client-agnostic claims to state the final contract accurately.
  4. Run the required just test and just ci gates for the relay change before re-requesting review.

One design check before I push, to avoid a second round: do you prefer the explicit signal as a new mention tag emitted in parallel with p (keeps p for delivery/notification fan-out, adds mention purely as the intent marker), or would you rather the relay derive "explicit" by excluding the resolved reply-author p and treat remaining p tags as mentions (no producer wire change)? I lean toward the explicit mention tag since it's unambiguous across CLI/Desktop/Mobile and forward-compatible, but I'll follow your call on the wire contract.

Bartok9 added a commit to Bartok9/buzz that referenced this pull request Jul 24, 2026
Brad review on block#2296: collecting all `p` tags false-positives when
buildReplyTags adds a structural reply-author `p` without an @mention.

- Extract mentions via tag kind "mention" only (desktop MENTION_REFERENCE_TAG)
- Dedup/skip malformed; ignore bare `p` tags
- Expand unit matrix for structural-p / online / offline / human / dedup
- Document presence as reachability warning, not delivery receipt

Refs block#1743

Signed-off-by: Bartok9 <danielrpike9@gmail.com>
@Bartok9
Bartok9 force-pushed the bartok9/offline-agent-mention-notice branch from d2cb235 to af2451b Compare July 24, 2026 13:17
Bartok9 added 3 commits July 24, 2026 12:39
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>
Brad review on block#2296: collecting all `p` tags false-positives when
buildReplyTags adds a structural reply-author `p` without an @mention.

- Extract mentions via tag kind "mention" only (desktop MENTION_REFERENCE_TAG)
- Dedup/skip malformed; ignore bare `p` tags
- Expand unit matrix for structural-p / online / offline / human / dedup
- Document presence as reachability warning, not delivery receipt

Refs block#1743

Signed-off-by: Bartok9 <danielrpike9@gmail.com>
Contract for block#2296 / block#1743: intentional authored @mentions emit
["mention", pubkey] alongside ["p", pubkey] from SDK build_message,
desktop ordinary composer + reply tags + edit path, mobile send, and
workflow_sink. Relay offline-agent notices stay gated on mention tags
only (never bare structural p). Tests cover parallel tags + structural-p
non-trigger.

Signed-off-by: Bartok9 <danielrpike9@gmail.com>
@Bartok9
Bartok9 force-pushed the bartok9/offline-agent-mention-notice branch from af2451b to abe7a6b Compare July 24, 2026 16:49
@Bartok9

Bartok9 commented Jul 24, 2026

Copy link
Copy Markdown
Author

Ready for re-review at abe7a6b5.

Wire contract: intentional @mentions emit parallel ["p", hex] + ["mention", hex]. Offline-agent notice stays gated on mention tags only (structural reply-author p alone never counts). Covered in SDK/builders, desktop composer+reply+edit, mobile send, workflow_sink + relay unit matrix.

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