It's common to want to send events in Matrix which relate to existing events - for instance, reactions, edits and even replies/threads.
This proposal is one in a series of proposals that defines a mechanism for events to relate to each other. Together, these proposals replace MSC1849.
- This proposal defines a standard shape for indicating events which relate to other events.
- MSC2675 defines APIs to let the server calculate the aggregations on behalf of the client, and so bundle the related events with the original event where appropriate.
- MSC2676 defines how users can edit messages using this mechanism.
- MSC2677 defines how users can annotate events, such as reacting to events with emoji, using this mechanism.
- MSC3267 defines how events can make a reference to other events.
- MSC3389 defines changes to the redaction algorithm, to preserve the type and target id of a relation.
This proposal introduces the concept of relations, which can be used to associate new information with an existing event.
A relationship is an object with a field rel_type
, which is a string describing the type of relation,
and a field event_id
, which is a string that represents the event_id of the target event of this relation.
The target event must exist in the same room as the relating event is sent.
Both of those fields are required. An event is said to contain a relationship if its content
contains
a relationship with all the required fields under the m.relates_to
key. If any of these conditions is not met,
clients and servers should treat the event as if it does not contain a relationship.
Servers should reject events not meeting these conditions with an HTTP 400 error when
they are received via the client-server API.
Here's a (partial) example of an event relating to another event:
{
"content": {
"m.relates_to": {
"rel_type": "m.replace",
"event_id": "$abc:server.tld"
}
}
}
All the information about the relationship lives under the m.relates_to
key.
If it helps, you can think of relations as a "subject verb object" triple,
where the subject is the relation event itself; the verb is the rel_type
field of the m.relates_to
and the object is the event_id
field.
We consciously do not support multiple different relations within a single event, in order to keep the API simple. This means that if event A relates to event B in two different ways you would send two events to describe the two relations, rather than bundling them into a single event. Another MSC, like MSC 3051, can propose a change to add support for multiple relations if it turns out that this would facilitate certain use cases.
Relations do not yet replace the reply mechanism currently defined in the spec.
Any values for rel_type
should abide the
general guidelines for identifiers.
The rel_type
property determines how an event relates to another and can be used
by clients to determine how and in what context a relation should be displayed.
MSC 2675 proposes to also interpret the rel_type
server-side.
It is left up to the discretion of other MSCs building on this one whether they introduce
rel_type
s that are specific to their use case or that can serve a broad range
of use cases. MSCs may define additional properties on the relation object for a given rel_type
.
Currently, a few rel_type
s are already proposed. Here's a non-exhaustive list:
Related events are normal Matrix events, and can be sent by the normal /send
API.
The server should postprocess relations if needed before sending them into a room, as defined by the relationship type. For example, a relationship type might only allow a user to send one related message to a given event.
Relations are received like other non-state events, with /sync
,
/messages
and /context
, as normal discrete Matrix events. As explained
in the limitations, clients may be unaware of some relations using just these endpoints.
MSC2675 defines ways in which the server may aid clients in processing relations by aggregating the events.
Events with a relation may be redacted like any other event.
MSC3389 proposes that the redaction algorithm should preserve the type and target id of a relation.
However, event relationships can still be used in existing room versions, but the user experience may be worse if redactions are performed.
We have a problem with resynchronising relations after a gap in federation: We have no way of knowing that an edit happened in the gap to one of the events we already have. So, we'll show inconsistent data until we backfill the gap.
- We could write this off as a limitation.
- Or we could make ALL relations a DAG, so we can spot holes at the next relation, and go walk the DAG to pull in the missing relations? Then, the next relation for an event could pull in any of the missing relations. Socially this probably doesn't work as reactions will likely drop off over time, so by the time your server comes back there won't be any more reactions pulling the missing ones in.
- Could we also ask the server, after a gap, to provide all the relations which
happened during the gap to events whose root preceded the gap.
- "Give me all relations which happened between this set of forward-extremities when I lost sync, and the point i've rejoined the DAG, for events which preceded the gap"?
- Would be hard to auth all the relations which this api coughed up.
- We could auth them based only the auth events of the relation, except we lose the context of the nearby DAG which we'd have if it was a normal backfilled event.
- As a result it would be easier for a server to retrospectively lie about events on behalf of its users.
- This probably isn't the end of the world, plus it's more likely to be
consistent than if we leave a gap.
- i.e. it's better to consistent with a small chance of being maliciously wrong, than inconsistent with a guaranteed chance of being innocently wrong.
- We'd need to worry about pagination.
- This is probably the best solution, but can also be added as a v2.
- In practice this seems to not be an issue, which is worth complicating the s-s API over. Clients very rarely jump over the federation gap to an edit. In most cases they scroll up, which backfills the server and we have all the edits, when we reach the event before the gap.
Based solely on this MSC, relations are only received as discrete events in
the timeline, so clients may only have an incomplete image of all the relations
with an event if they do not fill gaps (syncs with a since token that have
limited: true
set in the sync response for a room) in the timeline.
In practice, this has proven not to be too big of a problem, as reactions (as proposed in MSC 2677) tend to be posted close after the target event in the timeline.
A more complete solution to this has been deferred to MSC2675.
Shape of
"content": {
"m.relates_to": {
"m.reference": {
"event_id": "$another:event.com"
}
}
}
versus
"content": {
"m.relates_to": {
"rel_type": "m.reference",
"event_id": "$another:event.com"
}
}
The reasons to go with rel_type
is:
- This format is now in use in the wider matrix ecosystem without a prefix, in spite of the original MSC 1849 not being merged. This situation is not ideal but we still don't want to break compatibility with several clients.
- We don't need the extra indirection to let multiple relations apply to a given pair of events, as that should be expressed as separate relation events.
- If we want 'adverbs' to apply to 'verbs' in the subject-verb-object triples which relations form, then we apply it as mixins to the relation data itself rather than trying to construct subject-verb-verb-object sentences.
- We decided to not adopt the format used by
m.in_reply_to
as it allows for multiple relations and is hence overly flexible. Also, the relation type ofm.in_reply_to
is also overly specific judged by the guidelines forrel_type
s laid out in this MSC. Having replies use the same format as relations is postponed to a later MSC, but it would likely involve replies adopting the relation format with a more broadly usefulrel_type
(possibly them.reference
type proposed in MSC3267), rather than relations adopting the replies format.
pik's MSC441 has:
Define the JSON schema for the aggregation event, so the server can work out which fields should be aggregated.
"type": "m.room._aggregation.emoticon",
"content": {
"emoticon": "::smile::",
"msgtype": "?",
"target_id": "$another:event.com"
}
These would then be aggregated, based on target_id, and returned as annotations on
the source event in an aggregation_data
field:
"content": {
...
"aggregation_data": {
"m.room._aggregation.emoticon": {
"aggregation_data": [
{
"emoticon": "::smile::",
"event_id": "$14796538949JTYis:pik-test",
"sender": "@pik:pik-test"
}
],
"latest_event_id": "$14796538949JTYis:pik-test"
}
}
}