-
Notifications
You must be signed in to change notification settings - Fork 411
MSC4357: Live Messages via Event Replacement #4357
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
# MSC4357: Live Messages via Event Replacement | ||
|
||
## Introduction | ||
|
||
This proposal introduces **Live Messages** for Matrix: recipients can see a message evolve in near real time while it is | ||
being typed. The user experience is similar to streaming answers from LLMs (token-by-token) and to Simplex.chat's | ||
mutable "chat items", but without introducing a new event type. Instead, we rely on Matrix's existing edit mechanism | ||
(`m.replace` on `m.room.message`). Non-supporting clients degrade gracefully: they see a normal message which was edited | ||
a few times and always end up with the final content. | ||
|
||
Motivation includes more natural conversation flow, better perceived responsiveness, and resilience in unstable | ||
environments (e.g., live reporting): every chunk that was sent remains stored, so the last known state is preserved even | ||
if the sender disconnects prematurely. | ||
|
||
## Proposal | ||
|
||
A Live Message is a session of high-frequency edits applied to a single persistent `m.room.message`. The session starts | ||
when the user enables "live mode" and ends when the user finalizes the message. We introduce an **unstable marker** in | ||
event content to identify a live session and specify client/server behavior that reuses existing primitives. | ||
|
||
### Event identification | ||
|
||
- **Initial event**: a normal `m.room.message` whose `content` **MUST** include the unstable key | ||
`"org.matrix.msc4357.live": {}` (empty JSON object). This marks the start of a live message session. | ||
- **Update events**: each update **MUST** be a `m.room.message` with `m.relates_to.rel_type = "m.replace"` and | ||
`m.relates_to.event_id` referencing the initial event; its `m.new_content` **MUST** contain the *full* updated body | ||
(and formatted body if applicable). | ||
- **Final update**: when the user completes the message, the last update **SHOULD NOT** include the live marker. The | ||
absence of the live marker in the aggregated content signals session completion. | ||
|
||
### Client behavior (sending) | ||
|
||
- **Activation**: when the user toggles live mode, clients provide a clear UI affordance (e.g., lightning icon). | ||
- **First send**: upon a natural boundary (e.g., finishing a word) or a short delay, send the initial message with the | ||
live marker and retain its `event_id` for subsequent `m.replace`. | ||
- **Periodic updates**: send updates every ~2–3 seconds and/or on natural boundaries. Avoid per-keystroke updates. Each | ||
update replaces the entire content (`m.new_content` reflects the complete current text). | ||
- **Completion**: on explicit send or mode exit, send a final update without the live marker. After this, do not send | ||
further updates for this session. | ||
Comment on lines
+38
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it allowed to transition back from completion into live mode? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I intend completion to be terminal for that message. Once the final update (without the live marker) is sent, the live session is over. Further changes are normal edits; resuming live mode should be done by starting a new live message. Allowing a message to re‑enter live mode risks confusing clients (especially non‑supporting ones) that have already treated it as finalized. I’ll add a normative MUST‑NOT to make this explicit. |
||
- **Rate limiting**: clients **MUST** avoid flooding and respect server guidance; batching is encouraged. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does batching mean combining several updates into a single event replacement? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By batching I mean coalescing local interim changes and emitting a single |
||
|
||
### Client behavior (receiving) | ||
|
||
- When the initial event contains the live marker, supporting clients **SHOULD** render it in a "live" state (e.g., | ||
subtle animation / icon). Updates targeting the same message via `m.replace` **SHOULD** update the displayed text in | ||
place, without showing a separate "(edited)" marker or spamming notifications. When a final update arrives without the | ||
live marker, the client **SHOULD** transition the message to a normal finalized state. | ||
- Non-supporting clients treat the flow as "message followed by edits", ending with the correct final content. | ||
|
||
### Server behavior | ||
|
||
- No new endpoints or types are required. Homeservers store and federate the initial message and its `m.replace` updates | ||
as usual. Existing aggregation behavior applies. Homeservers **MAY** apply rate limits to high-frequency edits and/or | ||
offer retention policies for rooms with heavy live usage. | ||
|
||
## Room-level control: `m.room.live_messaging` (optional) | ||
|
||
To allow administrators and room moderators to control the feature, we introduce a room state event: | ||
|
||
- **Type**: `m.room.live_messaging` (unstable: `org.matrix.msc4357.live_messaging` until accepted) | ||
- **State key**: `""` | ||
- **Content**: | ||
``` json | ||
{ | ||
"enabled": true | ||
} | ||
``` | ||
|
||
### Semantics: | ||
|
||
- If absent, default is enabled (unless server policy states otherwise). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IIUC the enforcement can only happen on the client side. This means clients would need a way to discover such global server policy? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the draft I wrote “default is enabled (unless server policy states otherwise)”, which is ambiguous without a standard way to advertise that policy. I propose to clarify that the normative control here is room‑level via m.room.live_messaging; clients MUST honor it. Servers MAY reject live‑marked events in unencrypted rooms as a local policy, but there is no standardized discovery for a global ban, so it’s out of scope for this MSC. I’ll remove/adjust the “unless server policy states otherwise” wording to avoid implying a discovery mechanism that doesn’t exist |
||
- If `enabled: false`, clients **MUST NOT** offer live mode in this room. | ||
- Servers **MAY** enforce rejection of events that carry live markers in rooms where it is disabled. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is only possible in unencrypted rooms because There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yep, feature still works end‑to‑end (supporting clients decrypt, detect the marker, and render progressively), but enforcement and special aggregation are client‑side only in E2EE. I’ll add an explicit “E2EE considerations” subsection stating that server‑side enforcement is only feasible in unencrypted rooms. |
||
|
||
## Backwards compatibility | ||
|
||
Because live sessions are realized as normal edits, older clients retain a consistent view: they see a message which was | ||
edited. Users always end up with the final text. Supporting clients provide a richer in-place progressive rendering. | ||
|
||
## Security and abuse considerations | ||
|
||
- **Spam / flooding**: A malicious client could emit updates too frequently. Mitigations include server rate-limits, | ||
client batching, and room-level disabling via `m.room.live_messaging`. This is not a new class of risk compared to | ||
rapid normal edits; existing controls apply. | ||
- **Privacy**: Live updates reveal intermediate drafts. This is a user-experience choice; clients should clearly label | ||
the mode and allow users to opt out. The content remains subject to the same E2EE/federation properties as standard | ||
messages. | ||
- **Storage**: Live sessions produce more events. Deployments can rely on retention policies and rate-limits. | ||
|
||
## Alternatives | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Conceptually this proposal sounds similar to #3489 (live location sharing). That one uses a state event and reference relations though. Replacements strike me as a more intuitive solution though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree the concept is similar to MSC3489 in that both stream incremental state. For live text, I believe |
||
|
||
- **Ephemeral chunks** (`m.ephemeral...`): reduces storage, but loses data on disconnect and degrades UX on older | ||
clients (they see nothing until the final send). Not recommended. | ||
- **New relation type** (e.g., `m.live_update`): adds semantic clarity but provides limited practical benefits over | ||
`m.replace` and worsens compatibility; clients can already infer live sessions via the marker and frequency. | ||
|
||
## Use cases | ||
|
||
- Natural real-time conversations. | ||
- Live reporting in unstable networks (maximize delivered information). | ||
- Streaming responses from bots/LLMs (token-by-token rendering inside Matrix). | ||
- Customer support scenarios where reduced response latency improves user experience. | ||
- Real-time collaborative environments where immediate feedback is valuable. | ||
|
||
## Unstable identifiers | ||
|
||
- Content marker: `"org.matrix.msc4357.live": {}` | ||
- Room state: `org.matrix.msc4357.live_messaging` | ||
|
||
After acceptance, the stable equivalents **MAY** be standardized if needed. | ||
|
||
## Implementations | ||
|
||
- **Client (qualifying)**: TBD — a reference client implementing live mode UI, periodic `m.replace`, in-place rendering, | ||
and room state enforcement. | ||
- **Bot/AS (optional)**: a demo streaming LLM output as live updates. | ||
|
||
## Conclusion | ||
|
||
This MSC introduces Live Messages with minimal protocol surface by reusing `m.replace`. It preserves compatibility, | ||
improves perceived responsiveness, and supports important real-time scenarios. With optional room-level control and | ||
existing server safeguards (rate-limits, retention), it provides a pragmatic, adoptable path to streaming-like UX in | ||
Matrix. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implementation requirements: