Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions proposals/4357-live-messages.md
Copy link
Member

Choose a reason for hiding this comment

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

Implementation requirements:

  • Client (sending)
  • Client (receiving)

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
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it allowed to transition back from completion into live mode?

Copy link
Author

Choose a reason for hiding this comment

The 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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Does batching mean combining several updates into a single event replacement?

Copy link
Author

Choose a reason for hiding this comment

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

By batching I mean coalescing local interim changes and emitting a single m.replace update at a bounded cadence (e.g. every ~2–3s or at natural boundaries like end‑of‑word), rather than sending an event per keystroke. Each update still contains the full current content in m.new_content; there’s no special “multi‑update container”. I’ll tighten the wording to make this explicit.


### 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).
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Author

Choose a reason for hiding this comment

The 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.
Copy link
Contributor

Choose a reason for hiding this comment

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

This is only possible in unencrypted rooms because org.matrix.msc4357.live is in content which is encrypted.

Copy link
Author

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Author

@DonPrus DonPrus Sep 16, 2025

Choose a reason for hiding this comment

The 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 m.replace on m.room.message is the most intuitive and compatibility‑friendly primitive: older clients already understand “edits” and will converge to the final content; supporting clients can render the intermediate states seamlessly. It also avoids adding a new event type purely for live typing and reuses the well‑understood edit aggregation path (per MSC2676 / spec).


- **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.