Skip to content

fix(relay): p-tag a one-to-one DM's agent partner on new messages - #2475

Draft
rileycrane wants to merge 3 commits into
mainfrom
opus/r0-dm-agent-p-tag
Draft

rileycrane wants to merge 3 commits into
mainfrom
opus/r0-dm-agent-p-tag

Conversation

@rileycrane

@rileycrane rileycrane commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

A hosted agent (one that a provider runs, not this device) wakes only for a message whose p tag names it. The desktop app added p tags only for explicit @mentions, so a plain desktop DM to a hosted agent never woke it. The mobile app already tags a DM's partner.

This PR adds ["p", <agent>] to new messages and thread replies in a one-to-one DM whose other member is an agent.

  • The partner comes from the existing unmentionedRecipients(channelId).
  • "Agent" uses the profile's existing isAgent signal: an owner attestation (auth tag) or a declared agent flag.
  • The tag goes through the existing mentionTags. Deduplication with explicit mentions, the 32-recipient cap and validateMentions all still apply.
  • A mention normally needs a connected live stream, so the app knows its membership list is current. The DM partner is exempt from that one check: they are addressed by every message in the DM, so a plain message still goes into the send queue while the stream reconnects. The cached membership must still list them, and before publishing, the app still reads the membership fresh and refuses the message if the partner has left. Anyone else mentioned still needs a connected stream.
  • A human partner is never tagged. A p tag counts as a mention, so a person would be notified when they don't expect it.
  • Edits, reactions, group DMs and channels are unchanged.

directMessages.open now loads a one-to-one partner's profile before it returns. Without this, the first message sent from New Message had no tag: the New Message picker reads people outside the shared profile cache, and opening the DM confirmed membership only. If the profile read fails, the DM still opens and that message is sent without the tag, as before this PR.

Related: #2478 adds the Bestie tab for hosted agents.

Notes for review

  • Push notifications. The relay sends at most one push per device per event. The new tag names the agent, not the sender, so the sender isn't notified of their own message. We checked this by reading the relay and mobile push code, not with a live test.

Testing

  • messages-dm-agent.test.ts (new): an agent partner is tagged on send and on reply. A human partner is not tagged. An explicit mention is not tagged twice. Group DMs and channels are unchanged. The cap still applies.
  • mentions.test.ts: on a real session with the live stream reconnecting, a plain DM message to an agent is queued and published with the tag, a message to a partner who has left is refused before publishing, and a mention of anyone else still requires a connected stream. The first case fails without the fix.
  • direct-messages.test.ts: starts with an empty profile cache, then runs open → first send on a real session. An agent partner gets the tag and a human partner doesn't.
  • Full vitest run: all pass except browser-host/media-preparation.integration.test.mjs (tiled HEIC dimensions, local ffmpeg). It fails without this change too.
  • typecheck and lint are clean.
  • Manual check with a hosted agent: a plain desktop DM with no @mention wakes it, and it replies.

🤖 Generated with Claude Code

rileycrane and others added 2 commits November 4, 2026 16:29
A hosted agent is woken only by a `p` tag naming it, but the desktop app
tagged only explicit @mentions, so a plain DM to a hosted agent woke nothing.
Add a `p` tag for the other member of a 1:1 DM when its profile is an agent,
on new messages and thread replies. Human partners are never tagged (a `p`
tag would bypass their mute); edits, reactions and group DMs are unchanged.

Co-authored-by: Opus 5.5 Max <d9fc2aa400ad81a2f289921a1720811e5d922c43e079f37241d4be458d8d92aa@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Signed-off-by: Riley Crane <rileycrane@squareup.com>
New Message reads people outside the shared profile cache, and opening a
DM confirmed membership only. A first plain message to a hosted agent
therefore had no `p` tag and did not wake it. `directMessages.open` now
ensures the partner's profile for a one-to-one DM before it returns, so
`send` can classify the partner. A failed read still opens the DM.

Adds a session-level open → first-send regression for agent and human
partners, and makes the test relay filter discovery by kind.

Co-authored-by: Opus 5.5 Max <d9fc2aa400ad81a2f289921a1720811e5d922c43e079f37241d4be458d8d92aa@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Riley Crane <rileycrane@squareup.com>
@rileycrane
rileycrane force-pushed the opus/r0-dm-agent-p-tag branch from e79f5ab to aba44f5 Compare November 5, 2026 00:36
@rileycrane
rileycrane marked this pull request as ready for review November 5, 2026 00:51
@rileycrane
rileycrane requested a review from a team as a code owner November 5, 2026 00:51

@kazemek21 kazemek21 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed the PR and went through the Nexus review-tracker notes. Approving with the non-blocking reconnect comment below.

Posted by Codex (gpt-6.1-sol) on behalf of kylez

tags: [
["h", channelId],
...mentionTags(channelId, mentions),
...mentionTags(channelId, withDmAgents(channelId, mentions)),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Preserve plain DM queueing while the live stream reconnects

When an established two-member DM has a cached agent profile and its live stream is reconnecting, this implicit recipient reaches validateMentions, which throws because liveSnapshot.status is not connected. A plain send/reply previously passed an empty mention list and entered the durable outbox; now it fails before enqueueing with a mention-specific error even though the user added no mention. Could we preserve durable enqueueing for this implicit recipient while retaining fresh membership validation before publication? A session-level reconnect regression case would cover the distinction.

Posted by Codex (gpt-6.1-sol) on behalf of kylez

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Good catch, thanks. Fixed in 474f80d. A one-to-one DM's partner is now exempt from only the "connected live stream" precondition in validateMentions, so a plain message to an agent goes into the outbox while the stream reconnects. The cached roster must still list the partner, and publication still runs the strong roster read in preflightMentions, so a partner who has left is refused before dispatch. Mentions of anyone else still need a connected stream. New session-level cases in mentions.test.ts cover the reconnect queueing (which fails without the fix), the refusal of a partner who has left, and the unchanged check for other recipients.

…onnects

A one-to-one DM's agent partner is now p-tagged on every new message, so
the "connected live stream" check for mentions also blocked a plain
message from entering the outbox while the stream reconnected. Exempt
only the DM partner from that precondition: the cached roster must still
list them, and publication still reads the roster fresh and refuses a
partner who has left.

Co-authored-by: Opus 5.5 Max <d9fc2aa400ad81a2f289921a1720811e5d922c43e079f37241d4be458d8d92aa@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Riley Crane <rileycrane@squareup.com>
@rileycrane
rileycrane marked this pull request as draft November 5, 2026 05:57
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