Multiparty Session Types for Safe Runtime Adaptation in an Actor Language (Extended version)

Human fallibility, unpredictable operating environments, and the heterogeneity of hardware devices are driving the need for software to be able to adapt as seen in the Internet of Things or telecommunication networks. Unfortunately, mainstream programming languages do not readily allow a software component to sense and respond to its operating environment, by discovering, replacing, and communicating with components that are not part of the original system design, while maintaining static correctness guarantees. In particular, if a new component is discovered at runtime, there is no guarantee that its communication behaviour is compatible with existing components. We address this problem by using multiparty session types with explicit connection actions, a type formalism used to model distributed communication protocols. By associating session types with software components, the discovery process can check protocol compatibility and, when required, correctly replace components without jeapordising safety. We present the design and implementation of EnsembleS, the first actor-based language with adaptive features and a static session type system, and apply it to a case study based on an adaptive DNS server. We formalise the type system of EnsembleS and prove the safety of well-typed programs, making essential use of recent advances in non-classical multiparty session types.


Introduction
The era of single monolithic stand-alone computers has long been replaced by a landscape of heterogeneous and distributed computers and software applications. Technologies such as the IoT [56], self-driving cars [55], or autonomous networks [7] bring the new challenge of needing to successfully operate in face of ever-changing environments, technologies, devices, and human errors, necessitating the need to adapt. Here, we define dynamic self-adaptation-hereafter referred to as adaptation-as the ability of a software component to sense and respond to its operating environment, by discovering, replacing, and communicating with other software components at runtime that are not part of the original system design [6,52]. There are many examples of adaptive systems, as well as the mechanisms of adaptation they leverage, such as discovery [37], modularisation [27], dynamic code loading and migration [12,24]. Commercially, Steam's in-home streaming system 1 enables video games to dynamically transfer their input/output across a range of devices. Academically, RE X [50] enables software to self-assemble predefined components, using machine learning to reconfigure the software in response to environmental changes. Despite strong interest in adaption and substantial work on the mechanisms of adaptation, current programming languages either lack the capabilities to ensure that adaptation can be achieved safely and correctly, or they check correctness dynamically, resulting in runtime overheads which may not be acceptable for resource-constrained devices.
Specifically, if an adaptive system discovers new software components at runtime, these components must interact with the system in a purposeful manner. In concurrent and distributed systems, such interaction goes beyond a simple function call / return expressed with standard types and type systems: interaction involves complex communication protocols that constrain the sequence and type of data exchanged. For example, knowing that two components communicate integers and strings does not describe if or when they will be sent or received. In spite of growing interest in the topic, for example, the recent formation of the United Nations group considering creative adaptation 2 , mainstream programming languages do not support the specification and verification of communication protocols in concurrent and distributed systems. In turn, errors are discovered late in the development process and potentially after deployment.
Even where all components are known statically, communication safety cannot be guaranteed: as an example, the RE X system's programming language specifies sequential call / return interfaces for components, but not communication protocols for concurrent components. The adaptation in the Steam in-home streaming system is even more limited, being restricted to detection of input/output devices from a set of compatible possibilities. In both cases, the adaptive aspects of the software have been defined and designed ahead of time, as opposed to being composed on-demand at runtime, leaving no scope for extending the system via runtime discovery and replacement.
This situation brings us to a key research question:

RQ:
Can a programming language support static (compile-time) verification of safe runtime dynamic self-adaptation, i.e., discovery, replacement and communication?
The problem of static verification of safe communication is addressed by multiparty session types [29][30][31]. Multiparty session types (MPSTs) are a type formalism used to specify the type, direction and sequence of communication actions between two or more participants. Session types guarantee that software conforms to predefined communication protocols, rather than risking errors manifesting themselves at runtime.
There is already some work in the literature on adaptation and session types, but it does not answer our research question. We discuss related work in §6, but in brief, the state-of-the-art has some combination of the following limitations: theory for a formal model such as the π-calculus [11,13,18,19], rather than a real-world programming language; omission of some aspects of adaptation, such as runtime discovery [32]; or verification by runtime monitoring [21,47,48], as opposed to static checking.
To answer our research question, we implement EnsembleS, the first actor language leveraging MPSTs to provide compile-time verification of safe dynamic runtime adaptation: we can statically guarantee that a discovered actor will comply with a communication protocol, and guarantee that replacing an actor's behaviour (e.g., to fix a bug) will not jeapordise communication safety. Key to our approach is the combination of the actor paradigm [28], for its process addressability and explicit message passing, with explicit connection actions [32] in multiparty session types, which allow discovered actors to be invited into a session.
Dynamic Topologies Participants can connect and disconnect during a session. This builds on the more recent idea of explicit connection actions [32].

EnsembleS: basic language features
1 type Isnd is interface(out integer output) 2 type Ircv is interface(in integer input) An EnsembleS actor has its own private state and a single thread of control expressed as a behaviour clause, which is repeated until explicitly told to stop. Every actor executes within a stage, which represents a memory space. Actors do not share state, but instead communicate via message passing along half-duplex, simply-typed channels. Fig. 3 shows a simple EnsembleS program which defines, instantiates and connects two actors, one of which sends linearly increasing values to the other. The program defines two interfaces Isnd and Ircv, declaring an output and input channel respectively. The boot clause (lines [19][20][21][22][23] is executed first and creates an instance of each actor (lines [20][21], using the appropriate constructor (lines 7 and 13, respectively). This creates and begins executing new threads for each actor, which follow the logic of the relevant behaviour clause. Next, the boot clause binds the actor's channels together (line 22, discussed later in § 3.3). Once bound, the sender actor sends the contents of value on its channel, increments it, and goes back to the beginning of its behaviour loop (lines 8-11). The receiver actor waits for a message, binds the message to data, displays it, and returns to the top of its behaviour loop (lines [14][15][16][17][18]. EnsembleS inherits Ensemble's native support for runtime software adaptation actions [26]: We extend the StMungo [40] tool to generate EnsembleS template code that supports session types. Fig. 4 shows an overview of the actor template code generation from a global session type, and Fig. 5 shows an example of the generated code. First, a developer defines a global session type in Scribble [57] (Fig. 4, first stage). The Scribble tool checks that the protocol is well-formed and valid according to MPST theory and projects the global protocol into local protocols for each participant (Fig. 4, second stage). For each local protocol, the StMungo tool produces (Fig. 4, third stage) i) the session type, ii) the interface and type definitions, and iii) the actor template. The generated code is parsed by the EnsembleS compiler, producing executable code (Fig. 4, fourth stage).
Let us now look at the Buy1 local protocol, given in Fig. 1. Following the code generation process in Fig.4, the EnsembleS template items i), ii) and iii) for Buy1 correspond respectively to the code blocks starting in lines 3, 14, and 24 in Fig. 5.
The Buy1 local protocol is translated as an EnsembleS session type in Fig. 5 (lines 3-12). It shows a sequence of send and receive actions (lines 4-6), followed by a choice at Buy2 (lines 7-12), which determines the next set of communication actions.
Following session type specifications, EnsembleS channels define both the payload type and the session that this channel expects to interact with (lines 14-21, Fig. 5). The EnsembleS compiler uses this information to ensure that the session of each channel matches the session associated with the actor it is connected to.
An actor may follow a session type (line 24, Fig. 5). This tells the EnsembleS compiler that the logic within the behaviour clause of that actor must follow the communication protocol defined in the session.
It is important to note that the code generation in Fig. 4 is optional and the EnsembleS typechecker is independent of this process.

Channel connections: static and dynamic
If an actor follows a session type, then its channel connections must be 1-1. This is the standard linearity requirement for session types: if there are multiple senders on one channel, then their messages can interfere and it is not possible to statically check that the session is followed correctly. EnsembleS avoids this problem by using a single channel for each message type between each pair of participants. For example, in Fig. 1, each of the three actors communicates strings and integers with both of the other actors. Because channels are unidirectional, each actor therefore has 8 channels: 2 to send strings and 2 to send integers to both other actors, and similarly 4 channels for receiving. Static connections. When using session types with static topologies, and all actors in the session are known from the beginning of the application, EnsembleS provides the establish topology statement to create the connections between the specified session actors (line 22, Fig. 3; line 51, Fig. 5). A compile-time error is generated if the topology is ill-defined (e.g., if the sessions do not compose or if the channels do not match).

Figure 6 Session type-based discovery
EnsembleS supports runtime discovery of local or remote actor instances. As an example, in a sensor network, it may be desirable to connect to a sensor which has a battery level above a certain threshold. The EnsembleS query language allows the user to define a query on non-functional properties (such as battery level, signal strength, or name), as well as the channels exposed by an actor's interface. This ensures that any discovered actor has the correct number and type of channels, and satisfies user's preferences. To ensure that the discovered actor also obeys a declared protocol, EnsembleS uses session types in the discovery process. The green box in Fig. 6 shows how a session is used in the actor discovery process, and the yellow box shows how such actors are connected together. Runtime discovery does not appear in the session because it does not affect the behaviour of an application directly.
EnsembleS also supports the replacement of executing actors, much like the hot-code swapping in Erlang [12]. The new actor must present the same interface as it takes over the channels of the actor being replaced at the location it was executing. Replacement happens at the beginning of an actor's behaviour loop. Replacement has many uses, such as updating, changing, or extending some of the functionalities of existing software, and is particularly

Figure 7
Session type-based replacement useful in embedded systems [33,34]. The existing and new actors must follow the same session type, guaranteeing that replacement will not break existing actor interactions. Fig. 7 shows an example of a main actor searching for actors of type slowA (line 32), and replacing them with new actors of type fastA (lines 35-37). The slowA actors are located by defining a query (line 26) over user-defined properties, which are published (lines [16][17][18]. The discovery process is the same as above, but now the discovered actors are used for replacement rather than just communication.

Implementation
EnsembleS is implemented in C, and supports reference-counted garbage collection and exceltions. Applications are compiled to Java source code, and then to custom Java class files for use with a custom VM [10]. These applications can be executed on the desktop, parallel accelerators (e.g. GPUs), Raspberry Pi, Lego NXT, and Tmote Sky hardware platforms, and use a range of networking technologies.
Compact representations of session types are retained at runtime in order to support discovery. EnsembleS skeleton generation code is based on the StMungo tool [40], which is implemented as an ANTLR listener, and session typechecking is supported by modifying the original Ensemble typechecker to ensure that each communication action is permitted by the actor's declared session type.
Since EnsembleS builds directly on top of the original Ensemble implementation, it inherits Ensemble's runtime system. Performance results can be found in [26].  To illustrate the use of session types for adaptive programming, we consider a real-world case study: the domain name system (DNS). DNS is a hierarchical, globally distributed translation system that converts an internet host name (domain name) into its corresponding numerical Internet Protocol (IP) address [43].
The process begins by transmitting a domain name to one of many well-known root servers. This server either rejects bad requests, or provides the information to contact a zone server. The zone server may know the IP address of the domain name; if not it refers the request to another zone server. This process continues until either the IP address is returned, or the name cannot be found.
To develop an adaptive DNS example, we assume no a priori information about server location, and instead use explicit discovery to find root and zone servers based on session types and server properties. We use an existing Scribble description of DNS as a starting point [21]. To illustrate adaptation we focus on the client who is querying DNS. Fig. 9 shows the session type for the client actor which asks DNS to resolve a domain name. The client first asks for a root server (lines 2-3), and then either is informed that the request is invalid (lines [24][25] or recursively queries zone servers (lines 7-22) until the IP address is found (lines [19][20], or an error is reported (lines [16][17]. Based on this session, StMungo generates EnsembleS types and interface definitions and a skeleton actor. Minimally completing the generated skeleton produces the code in Fig. 8. In this example, discovery is used to locate the root server (lines 21-25, in Fig. 8) and the zone server (line 37). In each case, the session for the relevant server is provided to ensure that the discovered actor follows the expected protocol. When either server is located, the client links with it (lines 27 and 39), enabling communication. When communication with the server is no longer required, the client unlinks explicitly (lines 34, 47, 51, 55, 62).
Although explicit discovery is used at the language level, there is nothing to prevent the implementation of discovery from caching the addresses of the root and zone servers. This does not affect the use of sessions in discovery or the safety they provide, as the type-based guarantees are still enforced. However, this would potentially improve performance of the system. Additionally, if a cached entry becomes stale, the full discovery process can again be used without code modification or degradation in trust.
A version of DNS which uses discovery allows the system to become more flexible and resilient to changing operational conditions, such as topology changes in the servers and their data. Session types ensure compatibility with the discovered actors.

A Core Calculus for EnsembleS
In this section, we provide a formal characterisation of EnsembleS. In doing so, we show that our integration of adaptation with multiparty session types is safe, allowing adaptation while ruling out communication mismatches.
Relationship to implementation. Our core calculus aims to distil the essence of the interplay between adaptation and session-typed communication with explicit connection actions. Therefore, we concentrate on a functional core calculus rather than an imperative one: imperative variable binding serves only to clutter the formalism, and our fine-grain call-byvalue representation can be thought of as an intermediate language.
Interfaces and unidirectional, simply-typed channels in EnsembleS are an implementation artifact: sending on a channel whose type changes is equivalent to sending on multiple channels with different types. Moreover, following theoretical accounts of multiparty session types [14,31,32], instead of having send and receive (resp. connect and accept) operations followed by branching (as done in Mungo and StMungo), we have unified send and receive constructs which communicate a label along with the message payload.
Since session typing is the interesting part of discovery, we omit properties and queries from the formalism; their inclusion is routine. Finally, we concentrate on dynamic topologies with explicit connection actions rather than static topologies since they are important for adaptation and more interesting technically.

Syntax
Definitions. Figure 10 shows the syntax of Core EnsembleS terms and types. We let u range over actor class names and D range over definitions; each definition actor u follows S {M }

Syntax of Session Types
Session Actions α, β :: Values. Since our calculus is inherently effectful, we work in the setting of fine-grain callby-value [41], where we have an explicit static stratification of values and computations and an explicit evaluation order similar to A-normal form [20]. Values V, W describe data that has been computed, and for the sake of simplicity, consist of variables and the unit value.
Other base values (such as integers or booleans) can be encoded or added straightforwardly.

Computations.
The let x ⇐ M in N construct evaluates M , binding its result to x in N . The calculus supports exception handling over a single action L using try L catch M , where M is evaluated if L raises an exception, and labelled recursion using l :: M , stating that inside term M , a process can recurse to label l using continue l. Actions L denote the basic steps of a computation. The return V construct denotes a value.
Concurrency and adaptation constructs. The new u construct spawns a new actor of class u and returns its PID. The self construct returns the current actor's PID. An actor can replace the behaviour of itself or another actor V using replace V with κ. An actor can discover other actors following a session type S using the discover S construct, which returns the PID of the discovered actor.

Session communication constructs.
An actor can connect to an actor W playing role p using connect ℓ(V ) to W as p, sending a message with label ℓ and payload V . An actor can accept a connection from another actor playing role p using accept from which allows an actor to receive a choice of messages; given a message with label ℓ j , the payload is bound to x j in the continuation N j . Once connected, an actor can communicate using the send and receive constructs. An actor can disconnect from p using disconnect from p, and await the disconnection of p using wait p.
Types. Types, ranged over by A, B, include the unit type 1 and process IDs Pid(S); the parameter S refers to the statically-known initial session type of the actor (i.e., the session type declared in the follows clause of a definition). Unlike in channel-based session-typed systems, process IDs themselves need not be linear: any number of actors can have a reference to another actor, but each actor may only be in a single session at a time. PIDs can be passed as payloads in session communications.
Session types. Session types are ranged over by S, T, U and follow the formulation of Hu and Yoshida [32]. A session type can be a choice of actions, written Σ i∈I (α . S), a recursive session type µX.S binding recursion variable X in continuation S, a recursion variable X, a disconnection action #↓p, or the finished session end. The syntax of session types is more liberal than traditional 'directed' presentations in order to allow output-directed choices to send or connect to different roles. Session actions α involve sending (!), receiving (?), connecting (!!), or accepting (??) a message ℓ(A) with label ℓ and type A; or awaiting another participant's disconnection (#↑). As well as disallowing self-communication, following Hu and Yoshida [32], we require the following syntactic restrictions on session types: In the remainder of the paper, we assume that all session types are syntactically valid.
Session correlation. The most general form of explicit connection actions allows a participant to leave and re-join a session, or accept connections from multiple different participants. Such generality comes at a cost, since care must be taken to ensure that the same participant plays the role throughout the session.
To address this session correlation issue, Hu and Yoshida [32] propose two solutions: either augment global types with type-level assertions and check conformance dynamically, or adopt a lightweight syntactic restriction which requires that each local type must contain at most a single accept action as its top-level construct. We opt for the latter, enforcing the constraint as part of our safety property ( §5.4.2), and by requiring that #↓p does not have a continuation. (Note that the behaviour will repeat, so p will be able to accept again after disconnecting). As Hu and Yoshida [32] show, this design still supports the most common use cases of explicit connection actions.
Global types. Traditional MPST works [14,31] use global types to describe the interactions between participants at a global level, which are then projected into local types; projectability ensures safety and deadlock-freedom.
Since we are using explicit connection actions, traditional approaches are insufficiently flexible as they do not account for certain roles being present in certain branches but not others. Following Scalas et al.
[54] and subsequently non-classical MPSTs [53], we instead formulate our typing rules and safety properties using collections of local types.
It is, however, still convenient to write a global type and have local types computed programatically. Global types are defined as follows: Global Actions π :: Global actions π describe interactions between participants: p → q : ℓ(A) states that role p sends a message with label ℓ and payload type A to q. Similarly, p ↠ q : ℓ(A) states that p connects to q by sending a message with label ℓ and payload type A. The disconnection action p#q states that role p disconnects from role q. We can write the OnlineStore example from § 2 as follows: Although projectability in our setting does not necessarily guarantee safety and deadlockfreedom, we show a projection algorithm, adapted from that of Hu and Yoshida [32], in Appendix A. The resulting local types can then be checked for safety ( §5.4.2).

Protocols and Programs.
Terms do not live in isolation; they refer to a set of protocols, and evaluate in the context of an actor. A protocol maps role names to local session types.
▶ Definition 2 (Protocol). A protocol is a set {p i : S i } i mapping role names to session types.
As an example, consider the protocol for the online shop example: We can now consider an implementation of a Store actor, which uses discovery to find a courier. We write receive ℓ(x) from p; M and accept ℓ(x) from p; M as syntactic sugar for receive from p {ℓ(x) → M } and accept from p {ℓ(x) → M } respectively, and write M ; N as syntactic sugar for letx ⇐ M inN for a fresh variable x. We assume the existence of a function lookupPrice, and define CourierType as Store??deliver(String) . Store!ref(Int) . #↓Store.

Figure 11
Typing rules (1) A program consists of actor definitions, protocol definitions, and the 'boot' clause to be run in order to set up initial actor communication.
) of a set of definitions, protocols, and an initial term to be evaluated.
In the context of a program, we write ty(p) to refer to the session type associated with role p as defined by the set of protocols. Given an actor definition actor u follows S {M }, we define sessionType(u) = S and behaviour(u) = M . Figures 11 and 12 show the typing rules for EnsembleS. Value typing, with judgement Γ ⊢ V :A, states that under environment Γ, value V has type A. Judgement ⊢ D states that an actor definition actor u follows S {M } is well-typed if its body is typable under, and fully consumes, its statically-defined session type S. The behaviour typing judgement {S} Γ ⊢ κ states that given static session type S, behaviour κ is well-typed under Γ. Specifically, stop is always well-typed, and M is well-typed if it is typable under and fully consumes S.

Term typing.
The typing judgement for terms {T } Γ | S ▷ M :A ◁ S ′ reads "in an actor following T , under typing environment Γ and with current session type S, term M has type A and updates Exception handling rules

Figure 12
Typing rules (2) the session type to S ′ ". Note that the term typing judgement, reminiscent of parameterised monads [3], contains a session precondition S and may perform some session communication actions to arrive at postcondition S ′ .

Functional rules.
Rule T-Let is a sequencing operation: given a construct let x ⇐ M in N where M has pre-condition S and post-condition S ′ , and where N has pre-condition S ′ and post-condition S ′′ , the overall construct has pre-condition S and post-condition S ′′ .
Following Kouzapas et al.
[40], we formalise recursion through annotated expressions: term l :: M states that M is an expression which can loop to l by evaluating continue l. We take an equi-recursive view of session types, identifying recursive sessions with their unfolding (µX.S = S{µX.S/X}), and assume that recursion is guarded. Rule T-Rec extends the typing environment with a recursion label defined at the current session type. Rule T-Continue ensures that the pre-condition must match the label stored in the environment, but has arbitrary type and any post-condition since the return type and post-condition depend on the enclosing loop's base case.
Actor and adaptation rules. Rule T-New states that creating an actor of class u returns a PID parameterised by the session type declared in the class of u. Rule T-Self retrieves a PID for the current actor, parameterised by the statically-defined session type of the local actor (i.e., the T in the judgement {T } Γ | S ▷ M :A ◁ S ′ ). Rule T-Discover states discover U returns a PID of type Pid(U ). Finally, given a behaviour κ typable under a static session type U , and a process ID with the matching static type Pid(U ), T-Replace allows replacement, and returns the unit type.
Exception handling rules. Figure 12 shows the rules for exception handling and session communication. T-Raise denotes raising an exception; since it does not return, it can have an arbitrary return type and postcondition. Rule T-Try types an exception handler try L catch M which acts over a single action L. If L raises an exception, then M is evaluated instead. Since L only scopes over a single action, the try and catch clauses have the same pre-and post-conditions to allow the action to be retried if necessary.
▶ Remark 4. Following Mostrous and Vasconcelos [45], our try L catch M construct scopes over a single action and is discarded afterwards. We opt for this simple approach since in our setting exceptions are a means to an end, but (at the coast of a more involved type system) we could potentially scope over multiple actions as long as the handler is compatible with all potential exit conditions [23]. We leave a thorough exploration to future work.

Session communication rules.
Rule T-Conn types a term connect ℓ j (V ) to W as p j . Given the precondition is a choice type containing a branch p!!ℓ j (A j ) . S ′ j , and the remote actor reference is W of type Pid(S), the rule ensures that S is compatible with the type of p j , and ensures that the label and payload are compatible with the session type. The session type is then advanced to S ′ j . Rule T-Send follows the same pattern. Given a session type Σ i∈I (p?? Rule T-Wait handles waiting for a participant p to disconnect from a session, requiring a pre-condition of #↑p . S, returning the unit type and advancing the session type to S. Rule T-Disconnect is similar and advances the session type to end.

Operational semantics
We describe the semantics of EnsembleS via a deterministic reduction relation on terms, and a nondeterministic reduction relation on configurations. Figure 13 shows the runtime syntax and the first part of the reduction rules for EnsembleS.

Runtime syntax
Whereas static syntax and typing rules describe code that a user would write, runtime syntax arises during evaluation. We introduce two types of runtime name: s ranges over session names, which are created when a process initiates a session, and a ranges over actor names, which uniquely identify each actor once it has been spawned by new.
Configurations. Configurations, ranged over by C, D, E, represent the concurrent fragment of the language. Like in the π-calculus [42], name restrictions (νn)C bind name n in C, C ∥ D denotes C and D running in parallel, and the 0 configuration denotes the inactive process.
Actors are represented at runtime as a 4-tuple ⟨a, M, σ, κ⟩, where a is the actor's runtime name; M is the term currently evaluating; σ is the connection state; and κ is the actor's current behaviour. A connection state is either disconnected, written ⊥, or playing role p in session s and connected to rolesq, written s[p]⟨q⟩.
Inspired by Mostrous and Vasconcelos [45] and Fowler et al. [22], a zapper thread s[p] indicates that participant p in session s cannot be used for future communications, for example due to the actor playing the role crashing due to an unhandled exception.  To run a program, we place it in an initial configuration.

Runtime syntax
▶ Definition 5 (Initial configuration). An initial configuration for an EnsembleS program with boot clause M is of the form (νa)(⟨a, M, ⊥, stop⟩).

Reduction rules
Term reduction −→ M is standard β-reduction, save for E-TryRaise which evaluates the failure continuation in the case of an exception. We consider four subcategories of configuration reduction rules: actor and adaptation rules; session communication rules; exception handling rules; and administrative rules.

Figure 15
Operational semantics (3) over a single communication action, the top-level frames F, F ′ in each actor are discarded if the communication succeeds. Rule E-Conn handles the case where the connecting actor is already part of a session and behaves similarly to E-ConnInit, without creating a new session name restriction. A connection can fail if an actor attempts to connect to another actor which is terminated or is already involved in a session; in these cases, E-ConnFail raises an exception in the connecting actor. Rule E-Disconn handles the case where an actor b leaves a session, synchronising with an actor a. In this case, the unit value is returned to both callers, and the connection state of b is set to ⊥. Rule E-Comm handles session communication when two participants are already connected to the same session, and is similar to E-Conn. Rule E-Complete garbage collects a session after it has completed and sets the initiator's connection state to ⊥.
Exception handling rules. Exception handling rules allow safe session communication in the presence of exceptions. Rule E-CommRaise states that if an actor is attempting to communicate with a role no longer present due to an exception, then an exception should be raised. We write subj( Rule E-FailS states that if a connected actor encounters an unhandled exception, then a zapper thread will be generated for the current role, the actor will become disconnected, and the current evaluation context will be discarded. Rule E-FailLoop restarts an actor encountering an unhandled exception.
Administrative rules. The remaining rules are administrative: E-LiftM allows term reduction inside an actor; E-Equiv allows reduction modulo structural congruence; E-Par allows reduction under parallel composition; and E-Nu allows reduction under name restrictions.
Configuration equivalence. Reduction includes configuration equivalence ≡, defined as the smallest congruence relation satisfying the axioms in Figure 15. The equivalence rules extend

Figure 16
Runtime typing rules the usual π-calculus structural congruence rules with a 'garbage collection' equivalence, which allows us to discard a session where all participants have exited due to an error.

Metatheory
We now turn our attention to showing that session typing allows runtime adaptation and discovery while precluding communication mismatches and deadlocks.

Runtime typing
To reason about the metatheory, we introduce typing rules for configurations (Fig. 16): the judgement Γ; ∆ ⊢ C states that configuration C is well-typed under term typing environment Γ and runtime typing environment ∆.
Rule T-Pid types actor name restriction (νa)C by adding a PID into the term environment, and extending the runtime typing environment a : S; the linearity of the runtime typing environment therefore means that the system must contain precisely one actor with name a.
Session name restrictions (νs)C are typed by T-Session. We follow the formulation of Scalas and Yoshida [53] which types multiparty sessions using a parametric safety property φ; we discuss safety properties in more depth in Section 5.4.2. Let ∆ ′ be a runtime typing environment containing only mappings of the form s[p i ]⟨q i ⟩:S i . Assuming ∆ does not contain any mappings involving session s and ∆ ′ satisfies φ, the rule states that C is typable under typing environment Γ and runtime typing environment ∆, ∆ ′ . It is sometimes convenient to annotate session ν-binders with their environment, e.g., (νs : ∆ ′ )C.
Rule T-Par types each subconfiguration of a parallel composition by splitting the linear runtime environment. Rule T-Zap types a zapper thread s[p], assuming the runtime environment contains an entry s[p]⟨q⟩:S for any session type S.
Finally, rules T-DisconnectedActor and T-ConnectedActor type disconnected and connected actor configurations respectively. Given an actor with name a and static session type T , both rules require that the typing environment contains a : Pid(T ) and runtime environment contains a : T . Both rules require that the current session type is fully consumed by the currently-evaluating term and that the actor's behaviour should be typable under T .
Rule T-DisconnectedActor requires that the currently-evaluating term must be typable under either T or end, whereas to type a connection state of s[p]⟨q⟩ and current session type S, T-ConnectedActor requires an entry s[p]⟨q⟩:S in the runtime environment.

Preservation
We now prove that reduction preserves typability and thus that actors only perform communication actions specified in their session types. Due to our use of explicit connection actions, classical MPST approaches are too limited for our purposes. Our approach, following that of Scalas and Yoshida [53], is to introduce a labelled transition system (LTS) on local types, and specify a generic safety property based around local type reduction. The property can then refined; in our case, we will later specialise the property in order to prove progress. Figure 17 shows the LTS on runtime typing environments. There are two judgements: ∆ γ − → ∆ ′ , which handles reduction of individual local types, and a synchronisation judgement ∆ ρ = ⇒ ∆ ′ .

Reduction on runtime typing environments.
Rule ET-Conn handles the reduction of role p, where the choice session type contains a connection action q!!ℓ j (A j ) . S ′ j . If q has a statically-defined session type Σ k∈K (p??ℓ k (B k ) . T k ) which can accept ℓ j from from p, and the payload types match, reduction advances p's session type, adds q to p's connected role set, and introduces an entry for q into the environment. The reduction emits a label s:p ↠ q::ℓ j .
Given a role p connected to q with a session choice containing a send or receive action q †ℓ j (A) . S ′ j , rule ET-Act will emit a label s:p †q::ℓ j (A j ) and advance the session type of p. Rule ET-Wait handles the reduction of #↑q . S actions, s[p]⟨r, q⟩:#↑q . S, where p waits for q to disconnect: the reduction emits label p:q#↑ and removes q from p's connected roles.
Similarly, rule ET-Disconn handles disconnection, by emitting label p:q#↓ and removing the entry from the environment. ET-Rec handles recursive types, and the ET-Cong rules handle reduction of sub-environments.
Rule ET-ConnSync states that connection is a synchronisation action, and rules ET-Comm and ET-Disconn handle synchronisation between dual actions in sub-environments, emitting synchronisation labels s:p, q::ℓ and s:p#q respectively. We omit the congruence rules for synchronisation actions. We say that a runtime environment reduces, written ∆ =⇒, if there exists some ∆ ′ such that ∆ =⇒ ∆ ′ .

Safety.
A safety property describes a set of invariants on typing environments which allow us to prove preservation. Since the type system is parametric in the given safety property, we can tweak the property to permit or rule out different typing environments satisfying particular behavioural properties; however, we need only prove type preservation once, using the weakest safety property. Our safety property is different to the safety property described by Scalas and Yoshida [53] in order to account for explicit connection actions.
▶ Definition 6 (Safety Property). φ is a safety property of runtime typing contexts ∆ if: then k ∈ J, q ∈r, p ∈s, and A k = B k .
Clause (1) ensures that communication actions between participants are compatible: if p is sending a message with label ℓ and payload type A to q, and q is receiving from p, then the two roles must be connected, and q must be able to receive ℓ with a matching payload.
Clause (2) states that if p is connecting to a role q with label ℓ, then q should not already be involved in the session, and should be able to accept from p on message label ℓ with a compatible payload type. The requirement that q is not already involved in the session rules out the correlation errors described in Section 5.2.1. Clause (3) handles recursion, and clause (4) requires that safety is preserved under environment reduction.
Concretising the safety property. In order to deduce that a runtime typing environment ∆ is safe, we define φ(∆) = {∆ ′ | ∆ =⇒ * ∆ ′ } and verify that φ is a safety property by ensuring that it satisfies all clauses in Definition 6.

Properties on protocols and programs.
It is useful to distinguish active and inactive session types, depending on whether their associated role is currently involved in a session, and identify the initiator of a session. ▶ Definition 8 (Initiator, unique initiator). Given a protocol P , a role p : S p ∈ P is an initiator if S p = Σ i∈I (α i . S i ), and each α i is a connection action q!!ℓ i (A i ). Role p is a unique initiator of P if inactive(S q ) for all q ∈ P \ {p : S p }.
A protocol is well-formed if it is safe and has a unique initiator.

▶ Definition 9 (Well-formed protocol). A protocol P = {p i : S i } i∈I is well-formed if it has a unique initiator q of type S and safe(s[q]⟨∅⟩:S) for any s.
By way of example, the online shopping protocol is well-formed: Customer is the protocol's unique initiator, and it is straightforward to verify that safe(s[Customer]⟨∅⟩: ty(Customer)). A ◁ end When discussing the metatheory, we only consider configurations defined with respect to a well-formed program. Specifically, we henceforth assume that each actor definition in the system follows a session type matched by a role in a given protocol, assume each role belongs to a single protocol, and assume that all protocols are well-formed.
Given a safe runtime environment, configuration reduction preserves typability; details can be found in Appendix B. We write R ? for the reflexive closure of a relation R.
Preservation shows that each actor conforms to its session type, and that communication never introduces unsoundness due to mismatching payload types.

Progress
We now show a progress property, which shows that given protocols which satisfy a progress property, EnsembleS configurations do not get stuck due to deadlocks.
A final runtime typing environment contains a single, disconnected role of type end, reflecting the intuition that all roles will eventually disconnect from a protocol initiator. So far, we have considered safe protocols, which ensure the absence of communication mismatches. We say that an environment satisfies progress if each active role can eventually perform an action, each potential send is eventually matched by a receive, and non-reducing environments are final. Let roles(ρ) denote the roles referenced in a synchronisation label (i.e., roles(ρ) = {p, q} for ρ ∈ {s:p ↠ q::ℓ, s:p, q::ℓ, s:p#q}).
▶ Definition 13 (Progress (Runtime typing environments)). A runtime typing environment ∆ satisfies progress, written prog(∆), if: The online shopping example satisfies progress, since all roles will always eventually be able to fire an action once connected, and since all roles disconnect, the non-reducing final environment will be of the form s[Customer]⟨∅⟩:end. It is useful to define a configuration context G as the one-hole context G ::= [ ] | (νs)G | G ∥ C. A session consists of a session name restriction and all connected actors and zapper threads. Each well-typed configuration can be written as a sequence of sessions, followed by all disconnected actors.

▶ Definition 15 (Session). A configuration is a session S if it can be written:
An actor is terminated if it has reduced to a value or has an unhandled exception, and has the behaviour stop. An unmatched discover occurs when no other actors match a given session type. An actor is accepting if it is ready to accept a connection. The key session progress lemma establishes the reducibility of each session which does not contain an unmatched discover and is typable under a reducible runtime typing environment. There are several steps to proving Lemma 18. First, we introduce exception-aware reduction on runtime typing environments, which explicitly accounts for zapper threads at the type level, and show that exception-aware environments threads retain safety and progress. Second, we introduce flattenings, which show that runtime typing environments containing only unary output choices can type configurations blocked on communication actions, and that flattenings preserve environment reducibility. Finally, we show that configurations typable under flat, reducible typing environments can reduce. Full details can be found in Appendix B.
We can now show our second main result: in the absence of unmatched discovers, a configuration can either reduce, or it consists only of accepting and terminating actors.
The proof eliminates all failed sessions by the structural congruence rules; shows that the presence of sessions implies reducibility (Lem. 18); and reasons about disconnected actors.
In addition to each actor conforming to its session type (Thm. 11), Theorem 19 guarantees that the system does not deadlock. It follows that session types ensure safe communication.
Theorem 19 assumes the absence of unmatched discovers. This is not a significant limitation in practice, however, as unmatched discovers can be mitigated with timeouts, where a timeout would trigger an exception.

Related Work
Behavioural typing for actors. Mostrous and Vasconcelos [46] present the first theoretical account of session types in an actor language; their work effectively overlays a channel-based session typing discipline on mailboxes using Erlang's reference generation capabilities. Neykova and Yoshida [48] use MPSTs to specify communication in an actor system, implemented in Python. Fowler [21] implements similar ideas in Erlang, with extensions to allow subsessions [17] and failure handling. Neykova and Yoshida [49] later improve the recovery mechanism of Erlang by using MPSTs to calculate a minimal set of affected roles. The above works check multiparty session typing dynamically. We are first to both formalise and implement static multiparty session type checking for an actor language.
Active objects (AOs) [15] are actor-like concurrent objects where results of asynchronous method invocations are returned through futures. Bagherzadeh and Rajan [4] study order types for an AO calculus, which characterise causality and statically rule out data races. In contrast to MPSTs, order types work bottom-up through type inference. Kamburjan et al. [39] apply an MPST-like system to Core ABS [38], a core AO calculus; they establish soundness via a translation to register automata rather than via an operational semantics.
de'Liguoro and Padovani [16] introduce mailbox types, a type system for first-class, unordered mailboxes. Their calculus generalises the actor model, since each process can be associated with more than one mailbox. Their type discipline allows multiple writers and a single reader for each mailbox, and ensures conformance, deadlock-freedom, and for many programs, junk-freedom. Our approach is based on MPSTs and is more process-centric.

Non-classical multiparty session types. MPSTs were introduced by Honda et al. [31].
Classical MPST theories are grounded in binary duality: safety follows as a consequence of consistency (pointwise binary duality of interactions between participants), and deadlockfreedom follows from projectability from a global type.
Unfortunately, classical MPSTs are restrictive: there are many protocols which are intuitively safe but not consistent. Scalas and Yoshida [53] introduced the first non-classical multiparty session calculus. Instead of ensuring safety using binary duality, they define an LTS on local types and safety property suitable for proving type preservation; since the type system is parametric in the safety property (inspired by Igarashi and Kobayashi [35] in the π-calculus), the property can be customised in order to guarantee different properties such as deadlock-freedom or liveness. Hu and Yoshida [32] formalise MPSTs with explicit connection actions via an LTS on types rather than providing a concrete language design or calculus; in our setting, a calculus is vital in order to account for the impact of adaptation constructs. A key contribution of our work is the use of non-classical MPSTs to prove preservation and progress properties for a calculus incorporating MPSTs with explicit connection actions.
Adaptation. None of the above work considers adaptation. The literature on formal studies of adaptation is mainly based on process calculi, without programming language design or implementation. Bravetti et al. [8] develop a process calculus that allows parts of a process to be dynamically replaced with new definitions. Their later work [9] uses temporal logic rather than types to verify adaptive processes. Di Giusto and Pérez [18] define a session type system for the same process calculus, and prove that adaptation does not disrupt active sessions. Later, Di Giusto and Pérez [19] use an event-based approach so that adaptation can depend on the state of a session protocol. Anderson and Rathke [2] develop an MPST-like calculus and study dynamic software update providing guarantees of communication safety and liveness. Differently from our work, they do not consider runtime discovery of software components and do not provide an implementation.
Coppo et al.
[13] consider self-adaptation, in which a system reconfigures itself rather than receiving external updates. They define an MPST calculus with self-adaptation and prove type safety. Castellani et al.
[11] extend [13] to also guarantee properties of secure information flow, but neither of these works have been implemented. Dalla Preda et al.
[51] develop the AIOCJ system based on choreographic programming for runtime updates. Their work is implemented in the Jolie language [44], but they do not consider runtime discovery.
In this work we focus on correct communication in the absence of adversaries, and do not consider security. The literature on security and behavioural types is surveyed by Bartoletti et al. [5] and could provide a basis for future work on security properties.

Conclusion and Future Work
Modern computing increasingly requires software components to adapt to their environment, by discovering, replacing, and communicating with other components which may not be part of the system's original design. Unfortunately, up until now, existing programming languages have lacked the ability to support adaptation both safely and statically. We therefore asked: Can a programming language support static (compile-time) verification of safe runtime dynamic self-adaptation, i.e., discovery, replacement and communication?
We have answered this question in the affirmative by introducing EnsembleS, an actorbased language supporting adaptation, which uses multiparty session types to guarantee communication safety, using explicit connection actions to invite discovered actors into a session. We have demonstrated the safety of our system by proving type soundness theorems which state that each actor follows its session type, and that communication does not introduce deadlocks. Our formalism makes essential use of non-classical MPSTs.
Future work. Currently, in both the theory and implementation, each actor only takes part in a single session. Unlike dynamically-checked implementations of session typing for actors [21,47], this means that a message received by an actor in one session cannot trigger an interaction in another (e.g., the Warehouse example in [47]). A key focus for future work will be to allow actors to partake in multiple sessions.
Currently, EnsembleS discovery and replacement requires type equality. We envisage that we could relax this constraint to subtyping [54] or perhaps bisimilarity on local types to increase expressiveness.
In order to avoid session correlation errors, we require that each role includes at most a single top-level accept construct (c. f. [32]). It would be interesting to investigate the more general setting, which would likely require dependent types.

A Global types and projection
In this section, we show how global types can be projected into the local types used in the core formalism. Unlike in classic MPST works, it is not the case that projection alone guarantees safety and deadlock-freedom in itself. Rather, projection produces local types, which can be validated separately as described in the main body of the paper. This section is mostly a straightforward adaptation of the work of Hu and Yoshida [32] to our setting.

Syntax of types.
Similar to the presentation of local types, the key difference between this presentation of global types and standard MPST systems is that instead of having branching of the form r → s{ℓ i : G i } i , where a single role makes a choice, this presentation of global types provides arbitrary summations of global actions. The standard sending action is therefore p → q : ℓ(A) . G which involves role p sending message ℓ(A) to q.
A key technical innovation is explicit connection actions (p ↠ q : ℓ(A)) which establish a connection between two roles p and q by sending a label ℓ. Conversely, disconnection p#q is a directed disconnection where p disconnects from q, and q waits for p's disconnection.
Instead of assuming that all roles are connected at the start of the the session, in order to communicate, each role must be connected explicitly. This allows communication paths where one party isn't involved (as in Hu and Yoshida's travel agent example).
Projection. Global types can be projected at a role in order to obtain a local type. In standard MPST systems, a global type is well-formed if it is closed, contractive, and projectable at all roles. A well-formed global type is then guaranteed to be deadlock-free.
Define roles(G) to be the set of roles contained within global type G.
The projection function G ↾ Φ r projects global type G at role r, and is parameterised by a set of recursion variables Φ. The inclusion of the Φ parameter allows the pruning of unguarded recursion variables.
Projection on unary choices, recursive types, type variables, and session termination are standard. Disconnection ensures that the disconnecting role is unused in the remainder of the protocol. Now consider the case of projecting n-ary choices for n > 1. Suppose we have Σ i∈I (G i ) ↾ Φ r. If G i ↾ Φ r = end for all G i , then the result of the projection is end (and similarly for a recursion variable X).
Otherwise, there will be some subset J of I such that each L j for j ∈ J is a communication action, and each L k ∈ I \ J projects to either end or a recursion variable. For each j ∈ J, there is a further condition: the choice must consist of all send actions, or all receive actions. If the latter, then the receiver must be the same in all branches. This will ensure syntactically well-formed local types used in the paper.
Unfolding. We define the 1-unfolding of a global type G, unf(G), as unf(µX.G) = unf(G{end/X}) and defined recursively over the other constructs.
Role enabling. In order to ensure that projection produces syntactically well-formed local types, we require global types to satisfy role enabling: intuitively, this means that after a choice occurs, each role must be 'activated' by receiving a message before performing any further communications. In turn, this avoids mixed choice.
Let R range over sets of roles.

Syntactic validity of projected local types.
▶ Definition 20 (Initiator (global type)). We say that p is an initiator of G if p ∈ roles(G) and G ↾ p = Σ i∈I (π i . G i ), where each π i is a connection action. We say that p is the unique initiator of G if it is the only initiator.
Given the above definitions, we can show that the set of projected local types are syntactically valid.

Case E-ConnInit
Since the evaluation context will be discarded, WLOG we proceed assuming that F = [ ].
As b : Pid(U b ) ∈ Γ, we have that T b = U b and therefore that ty(q) = U b .

Case E-FailLoop
Similar to E-Loop.

Case E-LiftM
Immediate by Lemma 22.

Case E-Equiv
Immediate by Lemma 23.

Case E-Par
Immediate by the IH and rules E-Cong1 and E-Cong2.

Case E-Nu
Immediate by the IH, noting that the IH ensures that the resulting environment is also safe. ◀

C.1 Overview
The progress proof requires several steps. We overview them below.
Canonical forms Canonical forms allow us to reason globally about a configuration by putting it in a structured form. Lemma 31 states that any well-typed, closed configuration can be put into canonical form. Exception-aware runtime typing environments Runtime typing environments do not account explicitly for zapper threads. This makes sense when analysing protocols statically to check their safety and progress properties, but is inconvenient when reasoning about configurations. The second step is to introduce exception-aware runtime typing environments which account explicitly for zapper threads and the propagation of exceptions, and an exception-aware runtime typing system (Definition 32). We then show that configurations including zapper threads are typable under the exception-aware runtime typing system (Lemma 36). We refine our notion of environment progress to include the possibility of failed sessions (Definition 39), and show that given a runtime environment ∆ satisfying safety and progress, a derived exception-aware runtime environment Θ also satisfies safety and progress (Lemma 40). Flattenings Hu & Yoshida's formulation of multiparty session types relaxes the directedness constraint for output choices, but this relaxation makes it more difficult to reason about reduction of environments being reflected by configurations.
In this step, we introduce flattenings (Definitions 43 and 44), which restict each top-level output choice to a single option, and show that if a configuration is ready (i.e., all actors are blocked on communication actions), then it is typable under a flattened environment (Lemma 46). We then show that flattenings preserve environment reducibility (Lemma 47). Session progress The penultimate step is to show that sessions can make progress. A key result is Lemma 49, which states that a ready configuration typable under a flat exception-aware typing environment can reduce. The session progress lemma (Lemma 18) follows from Lemma 49 and the previous results. Progress Finally, progress follows from Lemma 18 and case analysis on the disconnected actors.
Term reduction satisfies a form of progress: a well-typed term is a value, can reduce, or is a communication or concurrency construct: ▶ Lemma 28 (Progress (terms)).

C.3 Canonical forms
To help us state a progress property, we consider configurations in canonical form.
Proof sketch. Due to Lemma 23, by induction on typing derivations we can move all actor name restrictions to the top level, followed by all session name restrictions, followed by all connected actors, followed by all zapper threads, followed by all disconnected actors.
Since the typing rules ensure each actor only participates in a single session, we can then group each actor and zapper thread according to its session, in order to arrive at a canonical form. ◀

C.4 Exception-aware runtime typing environments
Due to E-RaiseP, zapper threads expose additional reductions to those allowed by standard environment reduction. In particular, given the presence of a zapper thread s [p], any other participant in the remainder of the session blocked on p can reduce. In order to account for this, we introduce the notion of exception-aware runtime typing environments, environment reduction, and runtime typing. ▶ Definition 32 (Exception-aware runtime typing environments, environment reduction, and runtime typing). An exceptionaware runtime typing environment Θ is defined as follows: The exception-aware runtime typing relation Γ; Θ ⊢ C is defined to be the standard runtime typing relation Γ; ∆ ⊢ C but with rule T-Zap defined as:

Γ; s[p] : ⊢ s[p]
We extend the set of labels ρ with a zapper label s : p q, which states that role q has attempted to interact with a cancelled role p and has become cancelled itself as a result.
The exception-aware environment reduction relation is defined as the rules of the standard runtime typing relation ∆ =⇒ ∆ ′ with the addition of the following rules: ET