Questions and Answers
The Syndicated Actor Model (SAM) claims to enable conversations between software actors. Is this just a metaphor? Or is it anthropomorphic gloss to obscure details which wouldn’t hold up under examination? The question & answer (Q&A) protocol is a prime example of communication using what is arguably a literal application of human conversational concepts. By deconstructing Q&A we can examine the basic layering of SAM.
Protocol
In Q&A actors publish questions and observe corresponding answers. Q&A, like all Syndicate interactions, is expressed in the Preserves data language and uses a <q>
record with a single field and an <a>
record with two fields. The first field of either record is a request item and the second field of the answer record is usually a response wrapped in either an ok
, or an error
record. A canonical description of the protocol exists as a Preserves schema.
Using a factorial operation as an example, an actor might publish the question <q <factorial 12>>
and observe an answer of <a <factorial 12> <ok 479001600>>
. The question <q <factorial -1>>
might yield an answer of <a <factorial -1> <error "not a natural number">>
.
Q&A clearly fits in the publish and subscribe pattern (pub/sub), however the Syndicate protocol is not a pub/sub protocol. Pub/sub within SAM is provided by intermediary entities that implement the dataspace protocol on top of the Syndicate protocol. Actors that converse by Q&A do not necessarily implement the dataspace protocol, as will be explained later on.
Example
In pseudocode a consumer of factorials could be implemented as:
1
2
3
4
5
6
7
8
let n = 12
assert(<q <factorial $n>>)
onAssert(<a <factorial $n> <ok ?r>>) {
print("factorial $n: $r")
exit()
}
And a recursive factorial producer:
1
2
3
4
5
6
7
8
9
10
11
onAssert(<q <factorial ?x>>) {
if x == 1 {
assert(<a <factorial 1> <ok 1>>)
} else if x > 1 {
onAssert(<ok <factorial ($x -1)> <ok ?y>>) {
assert(<a <factorial $x> <ok ($x * $y)>>)
}
} else {
assert(<a <factorial $x> <error "invalid argument">>)
}
}
Using unbounded linear recursion is usually a bad idea, but this shows that the <a>
for a <q>
can be arrived at by iterative Q&A.
Underlayer
By dissecting the Q&A protocol we can observe how the Syndicate protocol works and where some optimisations can be applied.
HTTP
In human terms, questions and answers are a request and response protocol. One of the most ubiquitous forms of request and response in interprocess communication would be HTTP which makes it a good reference point for comparison.
To summarise an HTTP GET interaction:
- A user-agent opens a bi-directional stream to a web-server.
- The user-agent sends into the stream a message containing the GET verb and an identifier for the resource to be gotten.
- The server processes the request.
- The web-server sends some framing information and a representation of the resource back to the other side of the stream.
- One of the two parties closes the stream.
As HTTP APIs proliferate it has become increasingly difficult to distinguish cases where TCP provides a transport for HTTP and where HTTP provides framing for TCP.
Syndicate protocol
The Syndicate protocol differs in that instead of streams there are actors with inboxes. Messages can be addressed to actors and delivered to their inboxes but messages do not identify a sender. If a message is sent with an expectation of a response, then that message must contain a return reference for the actor receiving it.
Addressing and references within SAM are made using a capability model. SAM references address entities that are reachable via the mailbox of the actor that contains them. Syndicate refrences are stateful in that they are bound to the lifetime of the entities they refer to. References may also be attenuated to reject or alter certain communication patterns.
Sending a return reference within the body of every message isn’t necessary if the two parties make a stateful exchange of messages. The Syndicate protocol provides stateful messages as assertions. An assertion is a message containing content and a handle, and a retraction is a message containing a handle that invalidates a previously sent assertion. If there exists an active assertion with a reference to an actor entity, that entity is reachable. When all assertions of references to an entity are retracted, that entity becomes unreachable.
The assert-and-retract form of messages is so useful that the Syndicate protocol inverts the assertion as a special case, and defines a SAM message as a SAM assertion that is made with a retraction immediately following it.
A GET interaction could be reimagined using the Syndicate protocol like this:
- A assertion arrives in the mailbox of a web-server:
<GET «resource-id» «response-ref»>
. - The server processes the request.
- The webserver asserts a response to the reference bundled with the request:
<ok «resource-representation»>
. - The web-server elects to update a resource-representation with a retract followed by another assert to the peer.
- The original GET request assertions is retracted.
- The web-server retracts its assertions to the peer.
This only explains the server side of the interaction. The actor model avoids an omniscient view, even in a hypothetical scenario, and uses the perspective of actors to observe and describe the world. In the simple GET example it could be the same user agent-actor sending the message to the web-server and receiving the reply, but the web-server cannot make that assumption given the information that it has.
Dataspace protocol
The dataspace protocol is how the pub/sub pattern is realised within SAM and the Q&A protocol is layered upon the dataspace protocol. Dataspaces are entities that receive and retain arbitrary assertions and messages with an exception for an assertion of <Observe «pattern» «observer»>
. An <Observe>
assertion registers an reference to an observer with a pattern, and for any matching assertion made to the dataspace the tuple of captures made by the pattern is asserted to the observer.
A consumer of a factorial would assert the following to a dataspace:
- The question:
<q <factorial 12>>
. - An observe pattern that captures within
<a>
:<Observe <group <rec a> {0: <group <rec factorial> {0: <lit 12>}> 1: <bind <_>>}> «consumer-ref»>
.
A producer of factorials would assert:
- An observe pattern that captures within
<q>
:<Observe <group <rec q> {0: <group <rec factorial> {0: <bind <_>>}>}> «producer-ref»>
. - The answer to observed
<q>
:<a <factorial 12> <ok 479001600>>
.
Datasubspace
Q&A protocol is good for high-level interaction between actors but is not without overhead. First, there is dataspace entity that must maintain a registry of observers and copies of the <q>
and <a>
assertions. Second, recursive Q&A uses a stack of assertion handlers that must be unwound on retraction of <q>
assertions.
Actors that service queries without consulting others can be optimized to use bare Syndicate instead. In the case of factorials the producer could not register any observations and only respond to assertions of <factorial 𝑛 «result-ref»>
with assertions of <factorial 𝑛 «result»>
to «result-ref»
.
Rewrites
Entities that do not speak dataspace protocol can be registered as Q&A entities within a dataspace using capability attenuation. Attenuation is adding rules to Syndicate capabilties that are run against every assertion and message. Attenuation rules, or caveats, implement macaroon-style attenuation of authority but they are also useful as a minimal processing language that is common across all Syndicate implementations.
Following the factorial example, an observation could be made using the dataspace pattern:
1
<group <rec q> {0: <group <rec factorial> {0: <bind <_>>}>}>
which captures 𝑛 from <q <factorial 𝑛>>
. A simple factorial entity as previously described would be registered as the observer but with the caveat:
1
<rewrite <arr [<bind SignedInteger>]> <rec factorial [<ref 0> «result-ref»]>>
on the entity capability which rewrites the assertion of [𝑛]
from the pattern capture at the dataspace to an assertion of <factorial 𝑛 «result-ref»>
. Within that caveat the «result-ref» is a capability of the dataspace attenuated with the caveat
1
<rewrite <rec factorial [<bind SignedInteger> <bind <_>>]> <rec a [<rec factorial [<ref 0>]> <ref 1>]>>
which rewrites <factorial 𝑛 𝑟>
to <a <factorial 𝑛> 𝑟>
.
In this way the logic within the factorial producer can be minimized and its Q&A interaction realised externally using only caveat rules.
Comparison of factorial implementations
Different techniques for implementing factorial can be visually analysed by rendering protocol traces. In each case there are separate actors for dataspace, consumer, and producers so that interactions are explicitly transactional. Where actor boundaries exist it is generally the case that interaction would be the same if each actor were isolated by process or hardware boundaries.
Recursive
Without looking at the details it is obvious that the recusive factorial is the wrong implementation. Close examination shows that determining the factorial of 5 requires 27 turns of SAM protocol before halting.
Loop
Determining an arbitrary factorial using a loop internal to an assertion handler only requires 9 turns of SAM protocol.
Loop with attenuation
Using a dataspace naïve factorial actor still runs 9 turns before halting but requires less handler state. This is visible as less “facet- stop” actions during teardown (this trace rendering tool should be improved to better track lifetimes of SAM facets and entities).
Areas for further research
Q&A chaining
Nested Q&A interactions could be chained together using rewrite caveats, this would be particularly useful within the scripting language of the Syndicate-server.
Promises
Q&A aligns neatly with Promise Theory.
Syndicate actors and the agents of Promises theory intersect on the principle of autonomy. To summarise:
- Each actor or agent has a subjective view of reality. Actors may apply their own semantics to interactions with other actors.
- Actors or agents cannot impose behavior on each other.
- Collections of actors or agents can be collapsed into scale-free superagents.
To describe Q&A in the terms of Promises:
- The assertion of
<q 𝑟>
is a publicly declared intent to observe an assertion of<a 𝑟 ?>
. - An assertion of
<a 𝑟 ?>
is a promise that the information therein corresponds to the assertion of<q 𝑟>
. - An assessment of whether a corresponding
<a>
has been published in response to a<q>
can be made to determine if an agent is fulfilling it’s promise to respond to a class of questions. - Actors with public Q&A interfaces can hold private Q&A conversations with other actors to form a superagent.
Applying the formal model of Promise theory to SAM to analyze actor constellations is an open avenue of research, no doubt with additional layers of protocols for describing and assessing actor behavior.