feat(runtime): store targeting metadata on ring entries for filtered replay#32790
Conversation
…replay Targeted events (those with targetCapability, targetClientId, targetInterfaceId, or excludeClientId modifiers) are now buffered in the per-conversation ring with their targeting metadata attached. getReplayWindow accepts an optional ReplaySubscriber descriptor and re-applies the same delivery filter that the live publish() path uses, so reconnecting clients only receive events they would have seen live. This eliminates false-positive seq gaps on reconnect -- previously, targeted events were stamped with a seq but excluded from the ring, creating permanent holes that triggered unnecessary reconcile fetches on the client side. Co-Authored-By: vargas@vellum.ai <vargas@vellum.ai>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 921e75a1be
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (t.targetClientId != null) { | ||
| // Client targeting: bypass conversation filter, deliver only to the | ||
| // named client. | ||
| if ( | ||
| subscriber.type !== "client" || | ||
| subscriber.clientId !== t.targetClientId | ||
| ) { | ||
| return false; | ||
| } | ||
| if ( | ||
| t.targetCapability != null && | ||
| !subscriber.capabilities?.includes(t.targetCapability) | ||
| ) { | ||
| return false; | ||
| } | ||
| return true; |
There was a problem hiding this comment.
Preserve target-client replay across conversation scopes
When targetClientId is set, live fanout deliberately bypasses the subscriber's conversation filter because the target client may be connected to a different conversation than the event's conversationId (as documented in AssistantEventHub.publish). The replay path still stores the entry only in the source conversation's ring and GET /events only drains the ring for the subscriber's requested conversation, so a reconnecting targeted macOS/extension client will not receive the buffered host request unless it happens to subscribe to that source conversation; those requests can still be missed and time out during reconnects.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Valid observation. targetClientId live fanout bypasses the conversation filter (line 325-328 in assistant-event-hub.ts), but the ring is per-conversation and getReplayWindow is only called for the subscriber's requested conversation. So a reconnecting client subscribed to conversation B won't drain conversation A's ring.
This is a pre-existing limitation — before this PR, targetClientId events weren't buffered at all (replayable: false), so they were always lost on reconnect. This PR makes the same-conversation case work correctly; the cross-conversation case remains a gap that would require a fundamentally different approach (per-client ring or a cross-conversation replay index).
In practice, the main cross-conversation targetClientId use case is host-proxy requests (e.g., host_bash_request to a macOS client). Those have their own timeout + retry mechanism via pending-interactions, so a missed replay causes a retry rather than silent loss.
Targeted events (those published with
targetCapability,targetClientId,targetInterfaceId, orexcludeClientId) were previously excluded from the per-conversation ring buffer viareplayable: false. This prevented event leaks on replay but created permanent seq gaps that triggered false-positive reconcile fetches on the client. This PR stores targeting metadata on ring entries and re-applies the same delivery filter at replay time, so reconnecting clients only receive events they would have seen live — no leaks, no false gaps.Prompt / plan
B7 followup item "Targeted-event ring buffering" from #32676.
Test plan
bunx tsc --noEmitclean,bun run lintclean.