Notifications
Notifications
Two mechanisms cover "something happened that the user should notice". Mobile push delivers chat messages to a phone even when the app — and the server inside it — is not running, without the push infrastructure ever seeing plaintext. The process helper reports long-running work (agent runs, index passes, imports) as live progress that any device of the account, or any member of a space, can watch and cancel.
Push: the sender encrypts, the receiver decrypts
There is no notification server that reads your messages. When you send a chat message, your server derives the recipient topics, encrypts the payload with a key derived from the space's read key, signs it, and hands opaque ciphertext to the push node. The push node verifies signatures and fans ciphertext out to the FCM / APNs tokens subscribed to those topics. On the phone, the notification extension looks up the space's key in its native keystore and decrypts locally.
sender's any ──encrypt+sign──▶ push node ──ciphertext──▶ FCM / APNs ──▶ phone
│ │
sees only topics decrypts with the
and ciphertext cached space key
any owns the account policy — which topics to hold, when to re-sync subscriptions, the chat hooks that fire on send / mention / read — and delivers the key material clients must cache. Who gets notified is a two-level notifyMode setting (all / mentions / none) per space and per chat, private to the account.
Why it matters. Push is the one path where data leaves the device for a server you do not run. Keeping the payload encrypted under a key only space members can derive means the push provider learns that something arrived for a topic — never who wrote what to whom.
Processes: progress over the event bus
Long-running operations broadcast process.* events on the event bus. The server folds them into an in-memory last-event-wins view behind GET /v1/processes, with heartbeats and staleness expiry so a crashed owner disappears on its own. Cancellation is an event addressed at the owner, who stops and emits the terminal frame. The server's own indexer reports through the same view, which is how a client can explain "search is incomplete because the embedding model is still downloading".
Where each lives
| Concern | Surface |
|---|---|
| register / revoke a mobile push token | POST / GET / DELETE /v1/push/token |
| inspect the account's server-held topics | GET /v1/push/subscriptions |
| per-space notify default | PATCH /v1/spaces/:id/settings {"set":{"notifyMode":"mentions"}} |
| per-chat override | the chat.notifyMode account-scoped property on the chat object |
| receiver-side keys | SpaceInfo.push on GET /v1/spaces, streamed by the live space list |
| register / progress / finish / cancel a process | `POST /v1/processes[/:id/progress |
| live process view | GET /v1/processes; raw frames on GET /v1/events/subscribe?type=process.* |
Desktop notifications for chat are a client concern built from subscriptions and materialized unread counters rather than push — see Chat. ACL and invite events are surfaced in-app, never pushed.