- Authors: Daniel Bluhm, Sam Curren, Daniel Hardman
- Status: ACCEPTED
- Since: 2018-10-01
- Status Note: Implemented broadly in Indy, but not yet elsewhere.
- Start Date: 2018-08-03
- Tags: concept
Definition of the message @id field and the ~thread decorator.
Referring to messages is useful in many interactions. A standard method of adding a message ID promotes good patterns in message families. When multiple messages are coordinated in a message flow, the threading pattern helps avoid having to re-roll the same spec for each message family that needs it.
Message IDs are specified with the @id attribute, which comes from JSON-LD. The sender of the message is responsible for creating the message ID, and any message can be identified by the combination of the sender and the message ID. Message IDs should be considered to be opaque identifiers by any recipients.
- A short stream of characters matching regex
[-_./a-ZA-Z0-9]{8,64}
(Note the special semantics of a dotted suffix on IDs, as described in the message tracing HIPE proposal) - Should be compared case-sensitive (no case folding)
- Sufficiently unique
- UUID recommended
{
"@type": "did:example:12345...;spec/example_family/1.0/example_type",
"@id": "98fd8d72-80f6-4419-abc2-c65ea39d0f38",
"example_attribute": "stuff"
}
The following was pulled from this document written by Daniel Hardman and stored in the Sovrin Foundation's protocol
repository.
Message threading will be implemented as a decorator to messages, for example:
{
"@type": "did:example:12345...;spec/example_family/1.0/example_type",
"@id": "98fd8d72-80f6-4419-abc2-c65ea39d0f38",
"~thread": {
"thid": "98fd8d72-80f6-4419-abc2-c65ea39d0f38",
"pthid": "1e513ad4-48c9-444e-9e7e-5b8b45c5e325",
"sender_order": 3,
"received_orders": {"did:sov:abcxyz":1}
},
"example_attribute": "example_value"
}
The ~thread
decorator is generally required on any type of response, since
this is what connects it with the original request.
A thread object has the following fields discussed below:
thid
: The ID of the message that serves as the thread start.pthid
: An optional parentthid
. Used when branching or nesting a new interaction off of an existing one.sender_order
: A number that tells where this message fits in the sequence of all messages that the current sender has contributed to this thread.received_orders
: Reports the highestsender_order
value that the sender has seen from other sender(s) on the thread. (This value is often missing if it is the first message in an interaction, but should be used otherwise, as it provides an implicit ACK.)
Because multiple interactions can happen simultaneously, it's important to
differentiate between them. This is done with a Thread ID or thid
.
The Thread ID is the Message ID (@id
) of the first message in the thread. The
first message may or may not declare the ~thread
attribute block; it
does not, but carries an
implicit thid
of its own @id
.
It is desirable to know how messages within a thread should be ordered. However, it is very difficult to know with confidence the absolute ordering of events scattered across a distributed system. Alice and Bob may each send a message before receiving the other's response, but be unsure whether their message was composed before the other's. Timestamping cannot resolve an impasse. Therefore, there is no unified absolute ordering of all messages within a thread--but there is an ordering of all messages emitted by a each participant.
In a given thread, the first message from each party has a sender_order
value
of 0, the second message sent from each party has a sender_order
value of 1,
and so forth. Note that both Alice and Bob use 0 and 1, without regard
to whether the other party may be known to have used them. This gives a
strong ordering with respect to each party's messages, and it means that
any message can be uniquely identified in an interaction by its thid
,
the sender DID and/or key, and the sender_order
.
In an interaction, it may be useful for the recipient of a message to
know if their last message was received. A received_orders
value
addresses this need, and could be included as a best practice to help
detect missing messages.
In the example above, if Alice is the sender, and Bob is identified by
did:sov:abcxyz
, then Alice is saying, "Here's my message with
index 3 (sender_order
=3), and I'm sending it in response to your message
1 (received_orders: {<bob's DID>: 1}
. Apparently Alice has been more chatty than
Bob in this exchange.
The received_orders
field is plural to acknowledge the possibility of multiple
parties. In pairwise
interactions, this may seem odd. However, n-wise
interactions are possible (e.g., in a doctor ~ hospital ~ patient n-wise
relationship). Even in pairwise, multiple agents on either side may introduce other
actors. This may happen even if an interaction is designed to be 2-party (e.g., an
intermediate party emits an error unexpectedly).
In an interaction with more parties, the received_orders
object has a key/value pair
for each actor
/sender_order
, where actor
is a DID or a key for an agent:
"received_orders": {"did:sov:abcxyz":1, "did:sov:defghi":14}
Here, the received_orders
fragment makes a claim about the last sender_order
that the sender observed from did:sov:abcxyz
and did:sov:defghi
. The sender of
this fragment is presumably some other DID, implying that 3 parties are participating.
Any parties unnamed in received_orders
have an undefined value for received_orders
.
This is NOT the same as saying that they have made no observable contribution to the
thread. To make that claim, use the special value -1
, as in:
"received_orders": {"did:sov:abcxyz":1, "did:sov:defghi":14, "did:sov:jklmno":-1}
As an example, Alice is an issuer and she offers a credential to Bob.
- Alice sends a CRED_OFFER as the start of a new thread,
@id
=98fd8d72-80f6-4419-abc2-c65ea39d0f38,sender_order
=0. - Bob responds with a CRED_REQUEST,
@id
=<uuid2>,thid
=98fd8d72-80f6-4419-abc2-c65ea39d0f38,sender_order
=0,received_orders:{alice:0}
. - Alice sends a CRED,
@id
=<uuid3>,thid
=98fd8d72-80f6-4419-abc2-c65ea39d0f38,sender_order
=1,received_orders:{bob:0}
. - Bob responds with an ACK,
@id
=<uuid4>,thid
=98fd8d72-80f6-4419-abc2-c65ea39d0f38,sender_order
=1,received_orders:{alice:1}
.
Sometimes there are interactions that need to occur with the same party, while an existing interaction is in-flight.
When an interaction is nested within another, the initiator of a new interaction
can include a Parent Thread ID (pthid
). This signals to the other party that this
is a thread that is branching off of an existing interaction.
As before, Alice is an issuer and she offers a credential to Bob. This time, she wants a bit more information before she is comfortable providing a credential.
- Alice sends a CRED_OFFER as the start of a new thread,
@id
=98fd8d72-80f6-4419-abc2-c65ea39d0f38,sender_order
=0. - Bob responds with a CRED_REQUEST,
@id
=<uuid2>,thid
=98fd8d72-80f6-4419-abc2-c65ea39d0f38,sender_order
=0,received_orders:{alice:0}
. - Alice sends a PROOF_REQUEST,
@id
=<uuid3>,pthid
=98fd8d72-80f6-4419-abc2-c65ea39d0f38,sender_order
=0. Note the subthread, the parent thread ID, and the resetsender_order
value. - Bob sends a PROOF,
@id
=<uuid4>,thid
=<uuid3>,sender_order
=0,received_orders:{alice:0}
. - Alice sends a CRED,
@id
=<uuid5>,thid
=98fd8d72-80f6-4419-abc2-c65ea39d0f38,sender_order
=1,received_orders:{bob:0}
. - Bob responds with an ACK,
@id
=<uuid6>,thid
=98fd8d72-80f6-4419-abc2-c65ea39d0f38,sender_order
=1,received_orders:{alice:1}
.
All of the steps are the same, except the two bolded steps that are part of a nested interaction.
Threads reference a Message ID as the origin of the thread. This allows any message to be the start of a thread, even if not originally intended. Any message without an explicit ~thread
attribute can be considered to have the following ~thread
attribute implicitly present.
"~thread": {
"thid": <same as @id of the outer message>,
"sender_order": 0
}
A message that contains a ~thread
block with a thid
different from the outer
message @id
, but no sender_order
is considered an implicit reply. Implicit replies
have a sender_order
of 0
and an received_orders:{other:0}
. Implicit replies should only be
used when a further message thread is not anticipated. When further messages in the
thread are expected, a full regular ~thread
block should be used.
Example Message with am Implicit Reply:
{
"@id": "<@id of outer message>",
"~thread": {
"thid": "<different than @id of outer message>"
}
}
Effective Message with defaults in place:
{
"@id": "<@id of outer message>",
"~thread": {
"thid": "<different than @id of outer message>"
"sender_order": 0,
"received_orders": { "DID of sender":0 }
}
}
- Message Packaging document from Sovrin Foundation Protocol Repo
- Very brief summary of discussion from Agent Summit on Decorators
Why should we not do this?
- Implement threading for each message type that needs it.
If you're aware of relevant prior-art, please add it here.
- Using a wrapping method for threading has been discussed but most seemed in favor of the annotated method. Any remaining arguments to be made in favor of the wrapping method?
The following lists the implementations (if any) of this RFC. Please do a pull request to add your implementation. If the implementation is open source, include a link to the repo or to the implementation within the repo. Please be consistent in the "Name" field so that a mechanical processing of the RFCs can generate a list of all RFCs supported by an Aries implementation.
Name / Link | Implementation Notes |
---|---|
Aries Cloud Agent - Python | Contributed by the government of British Columbia. |
Aries Static Agent - Python | Useful for cron jobs and other simple, automated use cases. |
Connect.Me | Free mobile app from Evernym. Installed via app store on iOS and Android. |
Verity | Commercially licensed enterprise agent, SaaS or on-prem. |
Aries Protocol Test Suite |