MÓDULO 04 · CONCEITO 07 DE 14

Actor model

Hewitt (1973), Erlang/OTP, Akka, Orleans, Pony. Estado isolado, mensagens assíncronas, supervisão hierárquica — e por que "let it crash" é uma filosofia de concorrência, não capricho.

Tempo de leitura ~22 min Pré-requisito Conceitos 04, 05 e 06 Próximo Locks, mutex e condições de corrida

Em 1973, Carl Hewitt e seus colaboradores no MIT — Peter Bishop e Richard Steiger — publicaram um artigo de doze páginas com título ambicioso: A Universal Modular Actor Formalism for Artificial Intelligence. Hewitt buscava um modelo computacional capaz de lidar com sistemas inteligentes distribuídos: agentes que se comunicam, decidem localmente, e cooperam sem coordenação central. A inspiração veio de três fontes — o lambda calculus de Church (1936), as mensagens em Smalltalk-72 de Alan Kay, e a noção emergente de processos cooperantes de Dijkstra. O resultado foi o actor model: uma teoria de computação onde a unidade primária é o actor, uma entidade que recebe mensagens, pode criar outros actors, alterar o próprio comportamento, e enviar mensagens em resposta.

Por uma década e meia, o modelo permaneceu como construção acadêmica. A virada veio em 1986, quando Joe Armstrong e equipe no Computer Science Lab da Ericsson, na Suécia, começaram a projetar Erlang para construir sistemas de telefonia tolerantes a falhas. A demanda era extrema: centrais telefônicas que atendessem milhões de chamadas com uptime medido em "five nines" (99,999% — cinco minutos de downtime por ano), com atualizações de software sem desligar o serviço, com falhas de hardware tratadas em milissegundos sem perder ligações em curso. Threads, locks e exception handling tradicionais não bastavam. Armstrong escolheu o actor model — não por elegância acadêmica, mas porque era a única abstração que parecia escalar para o requisito.

Erlang foi liberada como open source em 1998 e virou base de sistemas que a maior parte do mundo usa sem saber: WhatsApp (suportando bilhões de mensagens por dia em 2026 com pequena equipe), RabbitMQ (broker de mensagens onipresente), Discord (originalmente, em Elixir/BEAM), CouchDB, Klarna. Akka (2009) trouxe o modelo para JVM. Orleans (2011, Microsoft Research) criou a noção de "virtual actors" para .NET, usado em Halo, Skype, e Azure. Pony (2015) explorou type system que garante ausência de data race em tempo de compilação. Em 2026, actor model é a fundação de virtualmente todo sistema massivamente distribuído tolerante a falhas em produção.

Este conceito mostra a teoria de Hewitt, sua materialização prática em Erlang/Akka/Orleans, o pensamento de "let it crash" e supervisão hierárquica como inversão do reflexo defensivo, e o contraste com CSP (visto no conceito anterior). É denso porque o modelo exige reorientação mental — quem vem de OOP tradicional precisa internalizar que actors não são objetos com locks, são processos com identidade.

O actor — três regras essenciais

Hewitt formalizou o actor com três axiomas. Um actor, em resposta a uma mensagem, pode:

Três axiomas curtos, consequências profundas. Como o actor processa uma mensagem por vez, não há concorrência interna — qualquer estado dentro do actor é manipulado sequencialmente. Não precisa de locks. Como mensagens são assíncronas, o emissor não bloqueia esperando processamento. Como actors têm identidade (endereço), a comunicação é dirigida — você manda mensagem para esse actor, não para um canal compartilhado.

Compare com CSP. No conceito anterior, channels eram cidadãos primários e processos eram anônimos do ponto de vista do canal. No actor model, actors são cidadãos primários e canais não existem — cada actor tem seu próprio mailbox privado. Você não manda mensagem para "o canal de pedidos", manda para "o actor do pedido 12345". Essa diferença parece sutil; tem consequências profundas em design e em como o sistema escala em distribuição.

princípio orientador

Em actor model, identidade é central. Cada actor é uma unidade lógica autônoma com estado próprio e endereço observável. Pensar em actors é pensar em "quem faz o quê, e quem fala com quem" — não em "quais dados estão compartilhados". O modelo mental é mais próximo de microsserviços (cada serviço com identidade, com fronteira) do que de objetos OOP (que tipicamente compartilham heap).

Mailbox — onde mensagens esperam

Cada actor tem um mailbox: uma fila privada de mensagens recebidas mas ainda não processadas. Quando outro actor envia mensagem, ela vai para o mailbox do destinatário e o emissor segue seu trabalho. Em algum momento, o actor destinatário processa a mensagem (chamando seu método de tratamento atual), termina, e pega a próxima do mailbox.

A natureza FIFO do mailbox parece simples, mas há decisões de design importantes. Mailbox bounded oferece backpressure natural — quando enche, novos sends bloqueiam ou falham. Mailbox unbounded aceita qualquer quantidade, mas pode crescer indefinidamente se o actor não der vencimento ao volume, eventualmente esgotando memória. Akka oferece ambos como mailbox types configuráveis. Erlang tradicionalmente usa unbounded, com a contraparte de que a programação cuidadosa é necessária para evitar overload.

Algumas implementações oferecem priority mailbox: mensagens marcadas como prioritárias pulam para frente da fila. É útil em sistemas onde mensagens de controle (shutdown, status check) precisam ser processadas antes de mensagens de trabalho em backlog. Erlang implementa isso com receive seletivo — você pode pegar mensagens da fila por padrão, não necessariamente em ordem.

Erlang — actor model em produção desde 1986

Erlang é a referência canônica de actor model em produção. A sintaxe parece estranha para quem vem de C-family, mas os conceitos são limpos. Veja um actor contador minimal:

%% contador.erl
-module(contador).
-export([start/0, incrementar/1, ler/1]).

start() ->
    spawn(fun() -> loop(0) end).

loop(N) ->
    receive
        incrementar      -> loop(N + 1);
        {ler, From}      -> From ! {valor, N}, loop(N);
        parar            -> ok
    end.

incrementar(Pid) -> Pid ! incrementar.
ler(Pid)         -> Pid ! {ler, self()},
                    receive {valor, V} -> V end.

Pontos a notar. spawn cria um novo actor (em Erlang chamado de process, mas é o que outras linguagens chamam de actor). ! é o operador de envio de mensagem (Pid ! mensagem envia para o processo identificado pelo PID). receive aguarda mensagens do mailbox, fazendo pattern matching para decidir qual cláusula executa. loop(N + 1) é a forma idiomática de "mudar o estado": chamada recursiva com novo estado, sem mutação de variável. Estado é capturado em parâmetro de função, não em campo de objeto.

A BEAM (a VM de Erlang) implementa preempção de actors com precisão notável. Cada actor tem um budget de "reductions" (instruções) e é preempted quando excede. Na prática, isso significa que um actor em loop apertado não bloqueia outros — coisa que goroutines em Go até 1.14 não garantiam. A BEAM também faz garbage collection por actor (não global), tornando GC pauses imperceptíveis em sistemas com milhões de processos. Esses detalhes de implementação são parte do que torna Erlang capaz de sustentar 99,999% uptime.

"Let it crash" — a inversão do reflexo defensivo

Programadores treinados em Java, C# e Python carregam um reflexo: defenda contra falhas dentro da função. try/catch em todo lugar, validações em entrada, branchings para tratar casos exóticos. O resultado é código onde 60% das linhas tratam casos que talvez nunca aconteçam, e que esconde a lógica de negócio entre verificações. Joe Armstrong, no livro Programming Erlang, articulou a alternativa: "let it crash".

A ideia é simples e radical. Dentro de um actor, você não tenta tratar todas as possíveis falhas. Você assume os caminhos esperados e escreve código limpo. Quando algo inesperado acontece — input mal-formado, conexão perdida, NPE — o actor crasha. Não tenta se recuperar. Outro actor (o supervisor) detecta o crash e decide o que fazer: reiniciar o actor crashed, escalar para um supervisor superior, ou desligar a árvore inteira.

Por que isso funciona? Três razões. Primeira: dividir responsabilidades. Lógica de negócio fica no actor; lógica de falha fica no supervisor. Cada um faz uma coisa bem. Segunda: o estado do actor é resetado no restart — sem state corrompido pelo erro. Terceira: o sistema todo, em vez de uma instância individual, vira o foco de robustez. Você não tenta fazer cada processo perfeito; tenta fazer o sistema tolerante a processos imperfeitos.

%% supervisor genérico em OTP
init(_Args) ->
    SupFlags = #{strategy => one_for_one,
                 intensity => 5, period => 10},
    ChildSpecs = [
        #{id => contador,
          start => {contador, start, []},
          restart => permanent,
          shutdown => 5000,
          type => worker}
    ],
    {ok, {SupFlags, ChildSpecs}}.

O supervisor declara: "se o actor 'contador' crashar, reinicie ele". Estratégia one_for_one diz "reinicie só o crashed"; alternativas são one_for_all (reinicie todos os filhos) e rest_for_one (reinicie o crashed e os subsequentes). intensity e period definem limite: se mais que 5 restarts em 10 segundos, escala para o pai. Toda a árvore de processos do sistema vira uma supervision tree, onde cada nível tem política definida de tratamento de falha.

Comparação com defensive programming tradicional

Considere processar uma mensagem em um servidor. Em estilo defensivo (Java tradicional):

void processar(Mensagem m) {
    try {
        if (m == null) return;
        if (m.getCorpo() == null) return;
        try {
            var resultado = parser.parse(m.getCorpo());
            if (resultado != null) {
                processador.processar(resultado);
            }
        } catch (ParseException e) {
            logger.error("parse falhou", e);
            return;
        } catch (ProcessamentoException e) {
            logger.error("processamento falhou", e);
            return;
        }
    } catch (Throwable t) {
        logger.error("inesperado", t);
    }
}

Em estilo "let it crash" (Erlang/Akka):

processar(Mensagem) ->
    Resultado = parser:parse(Mensagem#mensagem.corpo),
    processador:processar(Resultado).
%% Se algo crasha, supervisor reinicia o actor.
%% Sem try, sem if, sem null check.

A versão Erlang é uma fração do tamanho. Quando algo dá errado, o supervisor reinicia o actor — provavelmente o problema foi uma mensagem corrompida, e a próxima vai funcionar. Se o problema persistir (5 crashes em 10s), o supervisor escala. O sistema, no agregado, é mais robusto que a versão defensiva, porque a versão defensiva pode esconder bugs ao "tratá-los" silenciosamente.

heurística do sênior

"Let it crash" não significa "deixe tudo cair". Significa "deixe o supervisor decidir o que fazer com a falha — não tente prever cada caso dentro do actor". Em sistemas onde você controla a topologia de processos (e tem hot reload, como em Erlang), a estratégia funciona. Em sistemas onde crashar custa caro (perde dados, perde conexão de cliente), ainda há lugar para defensive programming — mas como exceção, não como regra.

Akka — actor model na JVM

Em 2009, Jonas Bonér criou Akka, uma biblioteca para Scala que depois ganhou suporte a Java. O nome vem do mountain Akka na Lapônia sueca (Bonér é sueco) e da homenagem implícita a Erlang. Akka rodava em Lightbend (a empresa de Bonér, antes Typesafe) e virou o veículo de actor model para o ecossistema JVM. Em 2022, Lightbend mudou a licença de Akka 2.7+ para Business Source License, motivando o fork comunitário Apache Pekko (2023) — que mantém compatibilidade e licença Apache 2.0. Em 2026, ambos estão em uso.

// Scala — Akka Typed (estilo moderno)
object Contador {
  sealed trait Mensagem
  case object Incrementar extends Mensagem
  case class Ler(reply: ActorRef[Int]) extends Mensagem

  def apply(): Behavior[Mensagem] = comportamento(0)

  private def comportamento(n: Int): Behavior[Mensagem] =
    Behaviors.receive { (ctx, msg) =>
      msg match {
        case Incrementar    => comportamento(n + 1)
        case Ler(reply)     =>
          reply ! n
          Behaviors.same
      }
    }
}

Akka Typed (introduzido em Akka 2.6, 2019) traz tipagem estática para mensagens — quem usa o actor sabe pelo tipo ActorRef[Mensagem] exatamente que mensagens pode enviar. Isso ataca uma das críticas históricas a actor model: o "any actor can send any message" gerava erros em runtime que poderiam ser detectados em compile time. A integração com o sistema de tipos de Scala/Java fez com que actor model em JVM ganhasse o melhor de dois mundos.

Orleans — virtual actors da Microsoft

Em 2011, a Microsoft Research lançou Orleans, com uma proposta original: virtual actors. Em modelos tradicionais (Erlang, Akka), você cria um actor explicitamente, gerencia seu ciclo de vida, decide quando destruir. Em Orleans, actors ("grains") são tratados como conceitualmente sempre existentes — você os referencia por ID e o runtime resolve onde eles vivem (em qual nó do cluster), criando-os sob demanda e desativando quando ficam idle.

A vantagem é simplicidade conceitual: você não precisa pensar em "este actor existe?" — você sempre pode mandar mensagem para qualquer ID. Orleans alimenta sistemas em escala massiva: Halo 4 e 5 (matchmaking, presença de jogadores), Skype messaging, e Azure Digital Twins. O modelo é particularmente adequado para entidades de negócio com ID estável (usuários, pedidos, dispositivos IoT).

// C# — Orleans
public interface IContadorGrain : IGrainWithStringKey {
    Task IncrementarAsync();
    Task<int> LerAsync();
}

public class ContadorGrain : Grain, IContadorGrain {
    private int n;

    public Task IncrementarAsync() {
        n++;
        return Task.CompletedTask;
    }

    public Task<int> LerAsync() => Task.FromResult(n);
}

// uso
var grain = grainFactory.GetGrain<IContadorGrain>("usuario:1234");
await grain.IncrementarAsync();
var v = await grain.LerAsync();

O grain "usuario:1234" sempre existe conceitualmente. Da primeira vez que você manda mensagem, Orleans aloca em algum silo do cluster. Se o silo cair, Orleans recria o grain em outro silo (com estado restaurado de storage, se persistido). Essa transparência é especialmente poderosa em sistemas distribuídos — você programa como se fosse local, e Orleans cuida do resto.

Pony — type system contra data races

Pony, criada por Sylvan Clebsch em 2015, é a tentativa mais ambiciosa de garantir ausência de data races em compile time via sistema de tipos. Pony usa reference capabilities — cada referência a um objeto carrega permissões (read-only, read-write, isolated, sendable) que o compilador verifica. Você não consegue compilar um programa Pony onde duas threads possam escrever no mesmo objeto sem coordenação.

Em produção, Pony é nicho — adoção pequena, ecossistema limitado. Mas conceitualmente é importante: prova que actor model + type system rico podem dar garantias estáticas que modelos tradicionais não dão. Em 2026, ideias de Pony influenciam Rust async (que tem semelhanças no tratamento de ownership/borrowing) e algumas propostas em desenvolvimento para Swift e Kotlin.

Tell vs Ask — dois patterns de mensagem

Em quase todo framework de actor, há duas formas de enviar mensagem com semânticas distintas:

Tell (fire-and-forget)

actor ! mensagem em Erlang/Akka, actor.tell(msg) em alguns dialetos. A mensagem é colocada no mailbox e o emissor segue. É o padrão idiomático e o que actor model espera por design — mensagens assíncronas, sem retorno direto. Se você precisa de resposta, o actor destinatário inclui um reply-to nas mensagens e envia uma mensagem de volta.

Ask (request-response)

actor ? mensagem em Akka, ou via método utilitário. Espera por uma mensagem de retorno como Future/Promise. É conveniente, mas perigoso: bloqueia (ou suspende) o emissor; se o destinatário travar ou demorar, o emissor sofre. Em sistemas onde latência é crítica, "ask" tipicamente vem com timeout. Em sistemas distribuídos com falhas reais, "ask" deve ser usado com moderação — Joe Armstrong, em palestras, advertia explicitamente contra abuso.

A regra prática é: prefira "tell" (assíncrono) por padrão; use "ask" apenas quando a semântica do problema for genuinamente request-response e você puder absorver a latência. APIs externas, queries em outros serviços, esses são candidatos legítimos. Comunicação interna entre actors do mesmo serviço tipicamente fica melhor com tell + reply-to explícito.

Distribuição transparente — actors locais e remotos

Uma das vantagens estruturais do actor model é location transparency: do ponto de vista do código que envia mensagem, não importa se o actor destinatário está no mesmo processo, em outro processo na mesma máquina, ou em outra máquina no cluster. A interface é a mesma: endereço ! mensagem. O framework cuida de serialização, transporte, reconexão.

Erlang implementa isso desde sempre: nodes Erlang se conectam ao cluster via TCP, e enviar mensagem para um Pid em outro node funciona transparentemente. Akka tem Akka Cluster e Akka Remote. Orleans tem silos coordenados. A consequência prática é que escalar um sistema vira (em primeira aproximação) adicionar mais máquinas — actors migram, replicam, são acessados de qualquer lugar.

A "primeira aproximação" é importante. Distribuição introduz latência de rede, falhas parciais, e a sutileza de que actors remotos podem desaparecer. CAP theorem aplica. Em sistema Erlang real, há detalhes (split brain, network partitions, consenso) que precisam de cuidado. Mas o ponto é que actor model expõe distribuição como variação de localidade, não como novo paradigma — uma propriedade que poucos modelos de concorrência oferecem com a mesma elegância.

O mesmo padrão nas três linguagens

Para concretizar, considere um actor que mantém um estado contábil (saldo) e processa mensagens de incremento, decremento e leitura. Em ambientes onde actor model é nativo, o código fica curto; em ambientes onde precisa biblioteca, fica mais verboso.

C# — Akka.NET (port de Akka para .NET)
using Akka.Actor;

public abstract record SaldoMsg;
public sealed record Depositar(decimal Valor) : SaldoMsg;
public sealed record Sacar(decimal Valor) : SaldoMsg;
public sealed record LerSaldo(IActorRef Reply) : SaldoMsg;

public class SaldoActor : ReceiveActor {
    private decimal saldo;

    public SaldoActor() {
        Receive<Depositar>(m => saldo += m.Valor);
        Receive<Sacar>(m => saldo -= m.Valor);
        Receive<LerSaldo>(m => m.Reply.Tell(saldo));
    }
}

// uso
var system = ActorSystem.Create("banco");
var contador = system.ActorOf(Props.Create<SaldoActor>());
contador.Tell(new Depositar(100m));
contador.Tell(new Sacar(30m));
// pedir saldo via ask:
var saldo = await contador.Ask<decimal>(new LerSaldo(ActorRefs.NoSender));

Akka.NET (mantido por Petabridge) é port idiomático de Akka para .NET. Mensagens como record sealed são padrão moderno. Note que actor processa mensagens uma por vez — não há lock no saldo. Orleans seria ainda mais idiomático em .NET para sistemas distribuídos em produção.

Python — Pykka (actors leves)
import pykka

class SaldoActor(pykka.ThreadingActor):
    def __init__(self):
        super().__init__()
        self.saldo = 0

    def on_receive(self, msg):
        match msg:
            case ("depositar", valor):
                self.saldo += valor
            case ("sacar", valor):
                self.saldo -= valor
            case ("ler",):
                return self.saldo

# uso
ref = SaldoActor.start()
ref.tell(("depositar", 100))
ref.tell(("sacar", 30))
saldo_future = ref.ask(("ler",))
print(saldo_future.get())   # 70
ref.stop()

Pykka oferece actor model em Python sobre threading (também tem Greenlet/asyncio). O actor processa mensagens uma por vez na sua thread interna; estado fica seguro sem lock. Para sistemas distribuídos em Python, Thespian é alternativa. Note que Python não tem actor model nativo — essas bibliotecas o reproduzem com qualidade.

Go — actor caseiro com goroutine + channel
package main

import "fmt"

type cmd interface{ tipo() string }
type depositar struct{ valor float64 }
func (depositar) tipo() string { return "dep" }
type sacar struct{ valor float64 }
func (sacar) tipo() string { return "sac" }
type ler struct{ reply chan float64 }
func (ler) tipo() string { return "ler" }

func saldoActor(in <-chan cmd) {
    saldo := 0.0
    for c := range in {
        switch m := c.(type) {
        case depositar: saldo += m.valor
        case sacar:     saldo -= m.valor
        case ler:       m.reply <- saldo
        }
    }
}

func main() {
    in := make(chan cmd, 100)
    go saldoActor(in)

    in <- depositar{100}
    in <- sacar{30}
    reply := make(chan float64)
    in <- ler{reply}
    fmt.Println(<-reply)   // 70
}

Go não tem actor nativo, mas o padrão "goroutine + channel de comandos" reproduz a semântica de actor: estado interno isolado, mensagens processadas uma por vez. Para sistemas maiores, ProtoActor-Go ou Hollywood oferecem framework completo (com supervisor, distribuição), mas o caseiro cobre boa parte dos casos.

Quando actor model brilha — e quando outros vencem

Actor model é especialmente adequado em três cenários. Primeiro: sistemas com identidade de longa vida — usuários online em jogo, dispositivos IoT em fleet, sessões em messenger. Cada identidade tem estado, e o estado evolui no tempo independente dos outros. Segundo: sistemas que exigem alta tolerância a falhas com supervisão hierárquica — telecom, sistemas de negociação, sistemas embarcados críticos. Terceiro: sistemas distribuídos onde location transparency simplifica escalabilidade — clusters massivos onde nodes vêm e vão.

Actor model é menos natural em três cenários. Primeiro: pipelines de processamento de dados — fluxo é estrutura mais adequada que identidade, e CSP costuma expressar melhor. Segundo: aplicações com estado pequeno e altamente compartilhado — um cache, um contador global, locks tradicionais ou estruturas lock-free são mais simples. Terceiro: APIs CRUD simples sem requisitos de robustez extrema — Spring Boot ou ASP.NET tradicional resolvem com menos cerimônia.

Quando você combina os modelos do módulo até aqui, vê-se um espectro: async/await (cooperativo, single-thread) → CSP (processos sequenciais, canais como primitivos) → actor model (entidades com identidade, mensagens assíncronas) → systems distribuídos (microsserviços com identidade e mensageria de rede). Cada modelo amplia o anterior em um eixo: actor model adiciona identidade explícita ao CSP, e sistemas distribuídos adicionam falhas e latência reais ao actor model. Não é coincidência que arquiteturas baseadas em actor model (Erlang, Akka Cluster, Orleans) sejam mais fáceis de migrar para distribuído do que arquiteturas thread-based — eles já tinham os primitivos certos.

armadilha cultural

Programadores acostumados a OOP frequentemente criam um actor por classe, replicando a granularidade dos objetos. O resultado é um sistema com milhões de actors triviais, todos trocando mensagens, e overhead que mata desempenho. Actor model funciona melhor com granularidade coarser: actor por entidade de negócio (usuário, pedido, dispositivo) — não actor por estrutura de dados. Joe Armstrong recomendava: "se você precisa pensar duas vezes sobre criar um actor, provavelmente não precisa".

Como praticar

  1. Implemente um actor caseiro em Go ou Python. Sem framework. Goroutine ou thread + channel/queue de mensagens, processadas em sequência. Estado privado da função. Adicione "tell" e "ask". Compare a complexidade com equivalente usando mutex.
  2. Construa um sistema de chat com actors. Cada usuário é um actor; salas são actors; mensagens fluem como tells entre eles. Use Akka.NET, Pykka, ou home-built. Adicione supervisor que reinicia actors quando falham. Force uma falha (mensagem mal-formada) e observe o restart.
  3. Compare tratamento de falha entre estilos. Mesmo problema (parser de mensagens recebidas via socket) em duas versões: defensive programming tradicional (try/catch em todo lugar) e let-it-crash com supervisor. Submeta entradas patológicas a ambos. Compare quantidade de código, comportamento sob carga ruim, e quão fácil é adicionar tratamento para um novo tipo de erro.

Referências para aprofundar

  1. livro Programming Erlang — Joe Armstrong (2ª ed., 2013). O livro escrito pelo criador da linguagem. Apresenta actor model com pragmatismo de quem o usou em telecom por décadas. "Let it crash" é articulado aqui pela primeira vez em livro.
  2. livro Designing for Scalability with Erlang/OTP — Francesco Cesarini & Steve Vinoski (2016). Cobre supervision tree, OTP, e padrões de produção em Erlang. Indispensável para entender por que sistemas Erlang sobrevivem onde outros morrem.
  3. livro Akka in Action — Raymond Roestenburg, Rob Bakker, Rob Williams (2ª ed., 2024). Atualizado para Akka Typed e contém boa cobertura de Cluster, Persistence e Streams. Caminho prático para JVM.
  4. livro Reactive Messaging Patterns with the Actor Model — Vaughn Vernon (2015). Catálogo de padrões de design em actor model. Útil mesmo para quem não usa Akka — os padrões aplicam-se a qualquer framework.
  5. paper A Universal Modular Actor Formalism for Artificial Intelligence — Hewitt, Bishop, Steiger (1973). dl.acm.org/doi/10.5555/1624775.1624804 — O paper original. Datado em ambições de IA, intemporal nos princípios do modelo.
  6. paper Making reliable distributed systems in the presence of software errors — Joe Armstrong (PhD thesis, 2003). erlang.org/download/armstrong_thesis_2003.pdf — A tese de Armstrong é um documento extraordinário, mistura de filosofia, design de linguagem, e relatos de Ericsson. Lê-se de capa a capa.
  7. paper Orleans: Distributed Virtual Actors for Programmability and Scalability — Microsoft Research (2014). microsoft.com/en-us/research/publication/orleans-distributed-virtual-actors-for-programmability-and-scalability — Paper explicando o conceito de virtual actors. Boa introdução ao modelo Orleans.
  8. artigo Why is your Akka actor still receiving messages? — Petabridge blog. petabridge.com — Artigos práticos sobre Akka.NET e patterns de actor em produção. Particularmente útil para .NET devs.
  9. docs Erlang/OTP Documentation. erlang.org/doc — Docs oficiais. As seções de OTP design principles e supervision tree são ouro puro mesmo para quem não programa Erlang.
  10. docs Microsoft Orleans Documentation. learn.microsoft.com/en-us/dotnet/orleans — Docs oficiais. Curso de Orleans e exemplos completos. Atualizado para .NET 9 em 2026.
  11. docs Pony Tutorial. tutorial.ponylang.io — Tutorial da linguagem Pony. Vale ler mesmo sem usar Pony — explica reference capabilities com clareza.
  12. vídeo The mess we're in — Joe Armstrong (Strange Loop, 2014). YouTube. Armstrong faz uma palestra que é metade história da computação, metade defesa de actor model. Quarenta minutos brilhantes.