Realtime
Realtime is implemented as a Feathers v6 SSE subsystem inside apps/api. It is mounted through the existing Mastra API server route at /realtime/sse; it is not a Mastra adapter and not a separate app.
Current Runtime
Section titled “Current Runtime”apps/apiowns the Feathers app and Better Auth handshake.apps/webowns the Feathers client plugin and same-origin/api/realtime/sseproxy.- Redis pub/sub fans events across API replicas through
REALTIME_REDIS_CHANNEL. - Normal local
bun run devuses the Docker Redis service frombun run docker:up, so browser and agent-browser verification exercise the Redis-backed fanout path. A single-process non-production stack may publish directly through the in-process Feathers runtime only when Redis is absent and background connector runtimes are disabled. Production, customer-cluster, and multi-replica runtimes still require Redis fanout. - Shared contracts live in
packages/contracts/src/realtime/events.ts. - Pinia stores are the durable browser-side sinks for connector, Library job/readiness, shell, chat/sidebar, and notification realtime state.
- The Feathers runtime owns one app-level
publishlistener plus per-connection subscriber registry entries. Tests prove subscribers are removed on abort, body cancel, and runtime stop. - Listener-warning fixes must not weaken realtime privacy. User events are filtered before delivery through server-derived channel membership; the browser must never receive another user’s private event and rely on client-side filtering to hide it.
- Recent chat sidebar and command-palette state comes from the shared chat summaries store; those compact surfaces do not refetch just because the route changes.
- Shared-with-me sidebar state comes from the shared-chat summaries store and refreshes from the recipient’s user-scoped chat-share grant and revocation notifications.
- Route-backed browse pages use the shared pending-update banner when realtime changes would move visible rows; compact surfaces such as the chat sidebar and notification menu remain live.
Event Flow
Section titled “Event Flow”- API route handlers, queue workers, or schedulers persist domain state.
- API publishes a typed
RealtimeEventEnvelope. - Redis fans the event to every API replica. Only the documented explicit no-Redis, single-process development mode sends directly to Feathers.
- The Feathers runtime delivers the event only to server-owned channels such as
user:${userId}. - The web realtime plugin validates the inbound payload with contract schemas.
- Pinia store actions merge the update and trigger snapshot refresh on connect/reconnect.
User-targeted events must stay on server-resolved user channels. Browsers never request a channel join, and shared-chat notifications only refresh the recipient’s compact shared-chat snapshot.
Security Rules
Section titled “Security Rules”- No unauthenticated SSE connection.
- Browser clients do not choose channels, users, groups, or resources.
- Better Auth session helpers derive the user id during the Feathers handshake.
- Listener or fanout optimizations must keep user-scoped authorization on the server side.
- Internal targets are not sent to clients.
- Safe global/system events are allowlisted.
- There is no Socket.io compatibility path and no old event-name alias layer.
Current Event Families
Section titled “Current Event Families”| Family | Events |
|---|---|
| Connectors | connector-status-updated, connector-disconnect-started, connector-disconnect-failed |
| Chat sidebar and queue | chat-thread-created, chat-thread-updated, chat-thread-deleted, chat-turn-queue-updated |
| Chat turn lifecycle | chat-turn-started, chat-turn-completed, chat-turn-interrupted |
| HITL | chat-hitl-updated |
| Library jobs | library-job-updated |
| Notifications | notification-created |
| Safe global status | system-status-updated |
Active assistant token streaming remains on the chat stream path. Do not route response deltas through Feathers.
Current Limits
Section titled “Current Limits”- Provider-backed Library upload readiness is implemented through
library-job-updated. CanonicalLibraryJobrows own job status/readiness;Uploadrecords store upload-session/provider metadata. API publishes after persisted upload/indexing state changes. - Connector settings changes that toggle indexed mode proactively reconcile provider-backed upload readiness for the connector, keeping open Library pages on the Library job realtime path. Indexed-mode toggles delete only vector/indexed artifacts and preserve Library items, Upload records, and provider file metadata.
- Library job snapshots are recovered over HTTP on initial load and reconnect. Snapshots include the compact Library readiness summary so missed-event recovery updates counts/status as well as job rows. The Library store guards both event and snapshot timestamps so an older recovery response cannot move a job backward or resurrect a hidden non-actionable upload row. The web app must not use steady-state interval polling for upload readiness.
- Library-owned import, delete, and reindex producers use the same
LibraryJobrealtime/snapshot path. Parser/extraction progress and batch maintenance producers are still planned; keep those producer checkboxes unchecked in canonical specs until their async workflows exist.
Notifications
Section titled “Notifications”Notifications are durable, user-scoped API records. The API derives the user from Better Auth; clients cannot supply user or channel targets.
GET /notificationslists the authenticated user’s notifications with pagination andreadStatus=all|read|unread.POST /notifications/:id/read,POST /notifications/:id/unread,POST /notifications/mark-all-read, andDELETE /notifications/:idmutate only records owned by the authenticated user.- The public DTO uses
readAt: string | nulland code-based copy; the frontend renders the English text. - Current producers are connector run completion, connector sync/indexing failures, shared-chat grants, and shared-chat revocations.
- Connector completion notifications are limited to first/connect and manual runs that changed content. Successful runs create one combined notification after terminal success: updated and removed counts come from sync, searchable counts come from indexing, and live-only/indexing-off connectors record that searchable chat indexing is off. Automatic completion and no-change completion notifications are intentionally suppressed.
- Connector failure notifications suppress repeated identical issues for the same connector so continuing errors do not spam the user.
- Shared-chat grant notifications are created only for server-resolved newly granted viewers and link to the same shared-chat route used by the sidebar/history surfaces. Shared-chat grant removal and full share revocation notifications target former viewers without a shared-chat link, so open workspaces refresh shared-with-me state and remove revoked metadata.
- The full notifications page uses URL-backed
page/readStatusparams and passes pagination state to the sharedAppPageShellpagination slot, matching the all chats page behavior and keeping pagination bottom-aligned through authenticated-layout route meta. - Realtime
notification-createdevents carry the validated notification DTO and are merged by the Pinia notifications store. - The header unread count and notification row unread chip use a shared presentation mapping. Failed connector sync/indexing notifications use the
erroraccent; successful connector and chat-share lifecycle notifications use the default primary accent. - Mastra does not provide a native user-notification API in the installed version. Future Mastra background task lifecycle hooks should publish through the same app-owned notification service.
Browse Page Refresh Banner
Section titled “Browse Page Refresh Banner”/notifications and /chats are route-backed browse pages. They show a shared “Load latest” banner when realtime events indicate newer rows are available, then refresh the HTTP snapshot after the user clicks it. This avoids changing row order or removing a row while someone is trying to click an item or row action.
The chat sidebar, command-palette recent chats, Shared with me sidebar, and notification menu are compact live surfaces. They read Pinia store state directly and use targeted snapshot refresh on connect/reconnect or user-scoped notification events instead of route-change reloads or interval polling.
Library Job Readiness
Section titled “Library Job Readiness”Provider-backed uploads are Library-owned jobs because users wait for readiness in Library and in focused chat, not only on connector dashboard rows.
POST /uploadsandPUT /uploads/:id/filepersist upload/session metadata and canonical Library job state, then publishlibrary-job-updated.- Connector embedding ingestion writes durable metadata/vectors first, then reconciles matching pending uploads and publishes the resulting Library job state.
- Uploads tied to a connector without indexed ingestion enabled reconcile terminally instead of staying pending, but do not create Library attention or inline Library contents status rows because indexing was intentionally left off.
- Toggling indexed mode back on makes new provider-backed uploads eligible for visible Library job readiness; old terminal non-actionable uploads are not silently reprocessed without a retry/reindex flow. Existing Library items remain visible across the off/on toggle.
- Ordinary manual connector sync progress remains connector status, not per-Library-item status. It updates open Library pages only when the sync changes pending provider-backed upload readiness or ready Library item counts.
GET /libraries/:id/jobsis the snapshot route for Library job and compact readiness recovery.- Library
itemCountcounts only ready/focusable Library items. Pending uploads affectactiveJobCount/updating; failed and actionable not-indexed uploads affectattention. - The Library detail page, Library index, focus picker, and focused-chat Sidekick consume the shared Library job/readiness store and the same workspace status-chip presentation used by connector status rows. Library detail renders one contents list: active, failed, and actionable not-indexed jobs appear inline, while ready jobs collapse into the durable Library item row they created.
- Durable notifications are not created for routine upload progress; use the Library job state for in-context readiness.
- Import, delete, and reindex jobs use the same status row presentation and stale-event handling. The web app does not add a new user action just to display these statuses.
Verification Commands
Section titled “Verification Commands”bun test packages/contracts/src/realtime/events.test.tsbun test packages/contracts/src/notifications/contracts.test.tscd apps/api && bun run test:connectors:realtimecd apps/web && bun run test -- test/nuxt/ChatSummariesRealtime.nuxt.spec.ts test/nuxt/SharedChatSummariesStore.nuxt.spec.ts test/nuxt/RealtimeNotificationSharedChats.nuxt.spec.ts test/nuxt/UseConnectorStatusTransportRealtime.nuxt.spec.ts test/nuxt/NotificationsStore.nuxt.spec.ts test/nuxt/NotificationCenterButton.nuxt.spec.ts test/nuxt/NotificationListItem.nuxt.spec.ts test/nuxt/NotificationCopy.nuxt.spec.tscd apps/web && bun run test -- test/nuxt/LibraryJobsStore.nuxt.spec.ts test/nuxt/LibraryDetailRoute.nuxt.spec.ts test/nuxt/LibraryIndexRoute.nuxt.spec.ts test/nuxt/ConnectorResourcePicker.nuxt.spec.ts test/nuxt/ChatSidekickSections.nuxt.spec.tsUse agent-browser to verify connector updates, Library upload readiness, chat/sidebar lifecycle, and notification menu/page behavior when local fixtures are available.
Canonical Sources
Section titled “Canonical Sources”docs/features/realtime/spec.mddocs/features/realtime/decisions/ADR-001-feathers-v6-realtime-transport.mdpackages/contracts/src/realtime/events.tspackages/contracts/src/libraries/contracts.tspackages/contracts/src/notifications/contracts.tsapps/api/src/library/apps/api/src/uploads/providerUploadService.tsapps/api/src/notifications/apps/api/src/realtime/apps/web/app/plugins/realtime.client.tsapps/web/app/stores/authenticated/storeLibraries.tsapps/web/app/stores/authenticated/storeChatSummaries.tsapps/web/app/stores/authenticated/storeSharedChatSummaries.tsapps/web/app/stores/authenticated/storeNotifications.tsapps/web/app/components/notifications/apps/web/app/utils/notificationPresentation.tsapps/web/app/composables/useRealtimeClient.tsapps/web/app/composables/useRealtimeSubscription.ts