Status: DraftLicense: CC-BY-NC-SA-4.0Last edit: 2026-03-08

A simple actor definition

This article offers a minimal, practical definition of actors aimed at specification and implementation. It clearly separates protocol from implementation. A stopwatch actor serves as a concrete, illustrative example.

Introduction

Building correct software requires a clear specification of what the system must do and an implementation that shows how to do it [1]. The Actor Model provides a powerful abstraction for concurrent and distributed systems by treating computation as asynchronous message-passing between independent entities.

Objective

  • Provide a clean, minimal definition of an actor
  • Illustrate it with a stopwatch example
  • Describe a practical method for defining new actor types

Context

Actor system

An actor system is an abstraction over a network of machines. It models computation as asynchronous message-passing between actors.

Actor

An actor is a concurrent computational entity with:

  • a unique, unforgeable address,
  • private state,
  • a script that defines how it reacts to messages.
Actor :≡ Address × State × Script

Address

An address is an opaque, unforgeable identifier that names an actor. Addresses can only be obtained through creation or by receiving them in messages.

Message

A Message is formed by an address and a payload. The payload is an immutable piece of data.

Message :≡ Address × Payload

State

State is the private memory of the actor.

Script

A scripti.e. the analog of a program — defines how the actor reacts to each incoming message using the following core operations:

- spawn  : Script State → Address
- self() : Address (the executing actor own address)
- ■ (halt the actor)
- send   : Message → ∅
- become : Script State → ∅

send

send(message) instructs the system — i.e. the runtime — to deliver the payload to the actor at the given address. The operation is non-blocking: the sender continues execution immediately. There are no guarantees of delivery or ordering.

In order to simplify notations, we introduce α#msg(param …) to mean "send the message <:msg, param …> to the actor α". In other words:

α#msg(param …) :≡ send(<α, <:msg, param …>>)

become

become(script state) replaces the current behavior and state. The new script and state will be used to process the next message.

spawn

spawn(script state) creates a new actor and returns its address. The newly created actor starts executing the given script with state immediately. The returned address is the only way to communicate with the new actor.

receive

receive blocks until a message arrives, then pattern-matches it against clauses and executes the corresponding expression.

receive
  pat₁ ↦ expr₁
  pat₂ when guard ↦ expr₂
  …
receive
  "hello" ↦ "world"
  x when x > 0 ↦ "#{x} is strictly positive"

Theory and practice

In our definition, receive is presented as a blocking operation that halts the actor’s execution until a message arrives. It is a practical concession rather than a theoretically pure one.

In the strict Actor Model, actors are entirely reactive. They do not contain an explicit "blocking loop" that pulls messages from a queue; rather, the runtime pushes messages to the actor, invoking its current behavior. The actor reacts, designates its next behavior via become, and goes dormant.

However, building complex control flows solely with reactive state transitions can be ergonomically challenging. Consequently, mature industrial implementations — such as Erlang and Akka — blend the Actor Model with sequential processing.

They provide an explicit, blocking receive construct that pulls messages from a mailbox. Our definition adopts this pragmatic approach, acknowledging that the ability to write sequential, suspendable code is critical for the usability of an actor system in practice.

Protocol

From a receiving actor point of view — i.e. the server — a protocol defines the possible sequences of incoming messages from a client and the server expected responses. A coprotocol is the dual of a protocol.

A protocol may be defined using a specialized language called Session Types [4], [5]. For instance, given:

health :≡ GET /health: !{ "health": "ok" } . end
hello  :≡ GET /hello: !"world" . end
server :≡ &{ health, hello }

then the protocol server has branch named health. The branch health may be interpreted as: "If I receive the HTTP request GET /health from a client, then I should answer with a JSON string that maps to { "health": "ok" } which terminates the protocol." The associated coprotocol is:

health :≡ GET /health: ?{ "health": "ok" } . end
hello  :≡ GET /hello: ?"world" . end
client :≡ ⊕{ health, hello }

Note that there is a deviation compared to the official notation. For instance, we let the reader translate ?{ "health": "ok" } to "I expect to receive a string that can be mapped to the JSON value { "health": "ok" }".

Example

The example shows how to define a stopwatch and the different steps that hold for all actor subtypes definitions.

Protocol

A stopwatch is an actor that satisfies this protocol:

  • Starts counting seconds : ℕ from 0 upon creation.
  • Replies to <:read, sender> with <self(), seconds>.
  • Halts (■) if it receives any other message.

Assumption

We assume the existence of an actor osc that satisfies the following protocol:

  • if osc receives <:subscribe, addr>, then addr receives <:tick, osc> every second.
  • if osc receives <:unsubscribe, addr>, then osc stops sending ticks to addr.

Script

script(osc) :≡
  osc#subscribe(self())
  loop :≡
    seconds : Nat ↦
      receive
        <:read, sender> ↦
          payload :≡ <self(), seconds>
          send <sender, payload>
          become loop seconds

        <:tick, osc> ↦
          become(loop (seconds + 1))

        _ ↦ ■
  loop(0)

Constructor

sw : Stopwatch :≡ spawn(script(osc) 0)

Proof

sw is a stopwatch — i.e. it is an actor that satisfies the stopwatch protocol. Proof by induction on the number of messages processed by sw.

  • Base case (0 messages): Actor starts in state loop(0) after subscribing. Count = 0 and alive. Holds.
  • Inductive step: Assume the actor is in state loop(n) and alive. On next message:
    • <:read, sender>: replies <self, n> and stays in loop(n).
    • <:tick, osc>: becomes loop(n+1).
    • else: ■.

The protocol is preserved in all cases. By induction, sw satisfies the stopwatch protocol.

Actor sub-type

How to define some kind of actors?

Actor sub-type

  1. What: Define the protocol.
  2. How: Define how to build an actor.
  3. Proof: Prove that the protocol is satisfied.

In practice, note that the proof is usually approximated by tests.

Stopwatch interface

The following helpers provide a convenient way to create and use stopwatches:

Stopwatch$mk : Oscillator → Stopwatch :≡
  osc ↦ spawn(script(osc) 0)

Stopwatch$read : Stopwatch → ℕ :≡
  sw ↦
    sw#read(self())
    receive
      <sw, seconds> ↦ seconds

Client invocation (an actor itself) example:

sw1 :≡ Stopwatch$mk(osc)
sw2 :≡ Stopwatch$mk(osc)
t1  :≡ Stopwatch$read(sw1)
t2  :≡ Stopwatch$read(sw2)
print("t1: #{t1} seconds")
print("t2: #{t2} seconds")

Parallel execution

A smart compiler or runtime may execute independent parts in parallel:

∥
  - sw1 :≡ Stopwatch$mk(osc)
    t1  :≡ Stopwatch$read(sw1)
    print("t1: #{t1}")

  - sw2 :≡ Stopwatch$mk(osc)
    t2  :≡ Stopwatch$read(sw2)
    print("t2: #{t2}")

Generic protocols

The Stopwatch$read function works with any actor that satisfies the stopwatch protocol. This highlights that interfaces in actor systems are defined by protocols rather than concrete types. This enables greater flexibility and composition.

New actor sub-type

New actor types can be introduced using the following template:

* <Name>
:PROPERTIES:
:TYPE: 0672475a-1dfa-4aa0-93d5-add1c1c995ad
:ID: <UUID>
:END:

<Informal: what does it represent? Why is it needed?>

** Protocol

<Description of accepted messages and responses.>

** mk : 🞎 ⇢ Name

<Constructor description.>

** <msg> : Name Args ⇢ Reply

<Message interface description.>

We use dashed arrows to indicate that replies are not guaranteed, the recipient may crash or be struck by lightning, instead of plain arrows that would have had indicated functions.

Result

We have provided:

  • a minimal yet usable definition of actors
  • a clear separation between protocol and implementation
  • a concrete stopwatch example with proof
  • a practical three-step workflow for constructing new actor types

Discussion

This definition of the Actor Model achieves its primary goals: it is minimal, pedagogical, and maintains a clear separation between protocol (specification) and implementation. The inductive proof technique shown with the stopwatch example provides a practical way to reason about correctness.

However, the stopwatch actor halts immediately upon receiving any unexpected message. While this choice simplifies the model and the proof, it is overly rigid for practical applications. Real-world actor systems typically employ supervision, logging, or graceful degradation rather than unconditional termination on unknown messages.

Additional limitations include:

  • Absence of timeouts on receive, which can lead to deadlocks in more complex scenarios.
  • Lack of structured error handling and supervision hierarchies.
  • The proof technique presented only addresses sequential message processing and does not yet cover fairness, message reordering, or network partitions.
  • The core primitives remain low-level; many applications would benefit from higher-level patterns built on top of them.

These observations open clear paths for future refinements of the model.

Conclusion

We have presented a minimal definition of actors based on five core primitives: send, receive, become, spawn, and . By putting protocols first and supporting them with concrete implementations and proofs, this article offers both a practical methodology and a foundation for building understandable concurrent systems.

The stopwatch example demonstrates that even with very few primitives, one can construct useful, provably correct behaviors. This protocol-centric approach naturally leads to flexible, composable, and evolvable designs.

This work is a first approximation in the spirit of progressive refinement. Future versions may incorporate session types, supervision primitives, timeouts, and tighter integration with type theory.

TODO Protocol negotiation

Actors can learn and negotiate protocols dynamically. This property allows actor systems to evolve into complex, adaptive “living systems” [6], [7], [8], [9].

A protocol may be viewed as a challenge — i.e., a request for the server to "prove that" it can behave according to a certain specification. If the server cannot satisfy the protocol on its own, the client may teach it the required definitions, algorithms, or sub-protocols and help it construct the proof. In effect, the parties negotiate how the server should prove the required property. The client may also decompose the original proposition into smaller sub-propositions and negotiate only those that the server is able to prove. The whole process of reaching such an agreement is what protocol negotiation really means.

Finding a way to make computers perform this kind of sophisticated negotiation automatically (for instance by combining session types with AI and interactive theorem provers such as Lean) would be a fascinating research direction.

Bibliography

[1]
L. Lamport, Specifying systems: The tla+ language and tools for hardware and software engineers. Addison-Wesley Professional, 2002. Accessed: Feb. 13, 2026. [Online]. Available: https://lamport.azurewebsites.net/tla/book.html
[2]
C. Hewitt and H. Baker, “Laws for communicating parallel processes,” MIT Artificial Intelligence Laboratory, AI Working Paper 134A, 1977. Available: https://dspace.mit.edu/bitstream/handle/1721.1/41962/AI_WP_134A.pdf
[3]
C. Hewitt, “Actor model of computation: Scalable robust information systems,” 2010. Available: https://arxiv.org/abs/1008.1459
[4]
S. J. Gay and V. T. Vasconcelos, Session types. Cambridge University Press, 2025. doi: 10.1017/9781009000062.
[5]
G. Tabone and A. Francalanza, “Session types in elixir,” in Proceedings of the 11th acm sigplan international workshop on programming based on actors, agents, and decentralized control, in Agere ’21. New York, NY, USA: Association for Computing Machinery, Oct. 2021, pp. 12–23. doi: 10.1145/3486601.3486708.
[6]
P. Hintjens, “Chapter 6. living systems.” Accessed: Mar. 04, 2026. [Online]. Available: https://hintjens.gitbooks.io/social-architecture/content/chapter6.html
[7]
A. Kay, “Programming and scaling,” Jul. 21, 2011, Talk at Hasso Plattner Institute (HPI), Potsdam. Accessed: Mar. 04, 2026. [Online]. Available: https://www.youtube.com/watch?v=YyIQKBzIuBY
[8]
A. Kay, “Comments in ``there are lots of ``old and fundamental’’ ideas that are not good anymore.’’ (hacker news ama).” Accessed: Mar. 04, 2026. [Online]. Available: https://news.ycombinator.com/item?id=11946935
[9]
A. Kay, “Dr. alan kay on the meaning of ``object-oriented programming’’,” 2003. Accessed: Mar. 04, 2026. [Online]. Available: http://www.purl.org/stefan_ram/pub/doc_kay_oop_en