Software Development

The Actor Model’s Long Lineage: From Hewitt’s 1973 Paper to Kotlin Coroutines

Every “actor” you’ve used, Erlang’s, Akka’s, Kotlin’s, descends from a single 1973 formalism. What survived the trip from language primitive to library is not the same in each case.

It’s common to hear the actor model described as “Erlang’s concurrency thing,” with Akka and its imitators treated as ports of an Erlang idea to other platforms. That framing skips a generation. The actor model didn’t originate with Erlang at all. It comes from a 1973 artificial intelligence paper by Carl Hewitt, Peter Bishop, and Richard Steiger, written more than a decade before Erlang existed, and Erlang itself arrived at similar ideas independently rather than by implementing Hewitt’s paper directly. Tracing the lineage properly, from a formal model, to a language built around it, to a library bolted onto languages that were never designed for it, shows exactly which guarantees survive each step and which ones quietly become the programmer’s responsibility instead of the runtime’s.

The 1973 Formalism

Hewitt, Bishop, and Steiger’s “A Universal Modular ACTOR Formalism for Artificial Intelligence” was not a systems paper about concurrency for its own sake. It was a position paper for the 1973 International Joint Conference on Artificial Intelligence, proposing actors as a single universal primitive underneath everything, control structures, data structures, even AI reasoning itself. The paper explicitly credits Alan Kay’s Smalltalk and Flex work as an influence, and the actor it describes is defined by exactly three capabilities: it can create new actors, it can send messages to actors it knows about, and it can designate how it will behave the next time it receives a message. Nothing else is a primitive. Everything else in a computation, in Hewitt’s formulation, is built out of those three moves.

Two consequences follow directly from that definition, and both turn out to matter enormously once the model becomes real software decades later. First, actors never share mutable state; the only way to affect another actor is to send it a message, never to reach into its memory. Second, message delivery is asynchronous, an actor sends a message and continues, it does not block waiting for the message to be received or processed.

Erlang: The Model Becomes a Language

Joe Armstrong, Robert Virding, and Mike Williams built Erlang at Ericsson starting in 1986, aiming to solve a very concrete problem, telecom switching software that could not be allowed to go down. They arrived at a design built entirely around lightweight, isolated processes communicating only by message passing, and multiple retrospective accounts note that this converges conceptually with Hewitt’s 1973 actor formalism despite having been developed independently for a different purpose. In Erlang, the actor is not a design pattern you opt into, it is the only unit of concurrency the language gives you. There is no shared-memory alternative sitting next to it.

Because the actor is a language-level primitive, the Erlang runtime, the BEAM virtual machine, can enforce guarantees that a library bolted onto another language simply cannot. Every Erlang process gets its own private heap and its own garbage collector, so a collection pause in one process never stops any other process in the system. A newly spawned process starts at roughly 327 words of memory, according to Erlang’s own official documentation, cheap enough that spawning a million of them is an ordinary benchmark rather than a stunt. On top of process isolation, Erlang’s OTP framework layers supervision trees and a “let it crash” philosophy: a process is allowed to fail outright, and a dedicated supervisor process, not the crashing process itself, decides how to recover, restart, or escalate the failure.

Approximate memory overhead per unit of concurrency, log scale. Erlang and Akka figures are from their own official documentation; the JVM native thread figure is HotSpot’s default 64-bit stack size.

Akka: The Model Becomes a Library

Jonas Bonér started Akka in 2009 explicitly because he wanted Erlang’s approach to concurrency and fault tolerance available on the JVM, where Java and Scala developers were still managing shared-memory threads and locks by hand. Akka’s actors keep the parts of the model that a library running inside an existing language runtime can still enforce by convention: each actor has a private mailbox, processes one message at a time, and Akka layers supervisor hierarchies on top, deliberately modeled on Erlang’s, to handle failure by restarting or stopping child actors rather than letting an exception propagate uncontrolled.

What Akka cannot do is change the ground it’s standing on. The JVM itself has no concept of an isolated, per-actor heap, every Akka actor’s state lives on the same shared heap as everything else in the process, garbage collected together. Nothing in the JVM stops one actor’s code from directly touching another actor’s fields; the isolation Erlang’s runtime enforces mechanically, Akka can only ask you to respect by not writing code that reaches across actor boundaries. It is convention backed by documentation and API design, not a guarantee the underlying platform makes impossible to violate. Akka’s own materials cite roughly 2.5 million actors per gigabyte of heap, remarkably lightweight for a JVM library, but that efficiency comes from actors sharing a small pool of real OS threads through Akka’s dispatcher, not from each actor getting anything resembling Erlang’s dedicated per-process heap and scheduler slice.

Kotlin Coroutines: The Model Becomes a Pattern

Kotlin’s coroutines library shipped an actor coroutine builder, a coroutine paired with a Channel that serves as its mailbox, giving you the surface shape of an actor, a single point of message entry, sequential processing, without any of the surrounding machinery. There is no supervision hierarchy. There is no isolated failure handling beyond ordinary coroutine cancellation. There is no scheduler-level guarantee about how the actor’s work is isolated from anything else running in the same process. It is, structurally, a convenience wrapper around a channel and a loop.

The kotlinx.coroutines project itself eventually conceded the point. The actor builder was marked with the @ObsoleteCoroutinesApi annotation, and its own documentation states plainly that it will become obsolete once a more complete actor implementation exists, a replacement that, years later, still has not shipped as a first-class API. The library’s 0.26 release, which introduced structured concurrency as Kotlin’s primary concurrency model, deprecated standalone builders like the original actor function in favor of scoped, structured alternatives. What began as “Kotlin has actors too” quietly became “Kotlin has a channel with a for-loop that we used to call an actor.”

PropertyErlang (language primitive)Akka (library on the JVM)Kotlin coroutines (deprecated builder)
Isolated per-actor heapYes, enforced by the BEAMNo, shared JVM heapNo, shared JVM heap
Supervision hierarchyYes, built into OTPYes, modeled on Erlang’sNo
Preemptive scheduling of actorsYes, BEAM schedulers use reduction countingCooperative, via dispatcher thread poolCooperative, via the coroutine dispatcher
“Let it crash” failure semanticsYes, a first-class runtime featureApproximated through supervisor strategiesNo equivalent; failures are ordinary exceptions
Status todayCore language featureActively maintained libraryMarked obsolete by its own maintainers
The actor model’s path from formalism to language to library to a deprecated convenience API.

What actually survives the trip to a library

  • The mailbox. Every implementation here preserves the idea of a single, ordered inbox per actor, this is cheap to build on any platform and rarely gets cut.
  • Sequential message processing. One actor handles one message at a time everywhere in this lineage, since it’s the easiest guarantee to keep without runtime support.
  • What tends to disappear first is failure isolation. Supervision and “let it crash” depend on the runtime actually stopping a fault from spreading, and that’s exactly what a library running inside a general-purpose language cannot fully guarantee.
  • Memory isolation is the hardest guarantee to port. It requires a dedicated per-actor heap and garbage collector, which only a purpose-built runtime like the BEAM provides.

What We Learned

The actor model didn’t start as a concurrency mechanism at all, it started as Carl Hewitt’s 1973 attempt at a universal computational primitive for artificial intelligence, built on nothing but actors that create other actors, send messages, and decide how to handle the next one. Erlang, arriving at similar ideas independently more than a decade later, turned that formalism into a language where isolation, preemptive scheduling, and supervised failure are enforced by a purpose-built runtime, the BEAM. Akka carried the mailbox and the supervision philosophy onto the JVM, but as a library it can only ask actors to respect isolation the shared heap doesn’t actually enforce.

Kotlin’s coroutine-based actor builder went further still, keeping only the mailbox and sequential processing, and its own maintainers eventually marked it obsolete because a channel with a loop was never really the same guarantee Hewitt described in 1973. The lineage is real, but each step down from language primitive to library is a step where the runtime does less and the programmer’s discipline is asked to do more.

Eleftheria Drosopoulou

Eleftheria is an Experienced Business Analyst with a robust background in the computer software industry. Proficient in Computer Software Training, Digital Marketing, HTML Scripting, and Microsoft Office, they bring a wealth of technical skills to the table. Additionally, she has a love for writing articles on various tech subjects, showcasing a talent for translating complex concepts into accessible content.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button