Actors are lightweight, stateful objects that communicate using messages. Each actor has:
- A unique address that other actors use to send it messages.
- A mailbox where incoming messages queue up and wait to be processed.
- Internal state that only this actor can access or modify.
- Behavior, logic that defines how this actor reacts to each message it receives.
- Send messages to other actors (asynchronously, without waiting for responses).
- Create new actors and delegate work to them.
- Update its internal state which may affect how future messages are handled.
No shared state Each actor’s state is completely private. Other actors cannot directly access or modify it. This eliminates entire classes of concurrency bugs like race conditions and deadlocks. Actors can only influence each other by sending messages. Message Processing While many actors run in parallel across the system, each individual actor processes only one message at a time. Messages wait in the actor’s mailbox and are handled sequentially. This means you never need to worry about two threads modifying an actor’s state simultaneously; it simply can’t happen. Asynchronous message passing When Actor A sends a message to Actor B, it doesn’t have to wait for B to process it or respond. The message is queued in B’s mailbox, and A can continue with its work immediately. This non-blocking communication allows actors to work concurrently without waiting on each other. Location Transparency Sending or receiving a message from another actor looks the same whether that other actor is running on the same machine or across the network. This makes it natural to distribute work and scale horizontally.
In Autonomy:
- Simple actors are called workers.
- Agents are intelligent autonomous actors that use large language models.
- Both follow the actor model.
Workers
Workers are Autonomy’s implementation of actors. Autonomy’s actor runtime is implemented in Rust and exposed to Python code using theNode class. All nodes in a zone can create encrypted, mutually authenticated,
secure communication channels with other nodes.
Here’s a simple worker that echoes messages back:
- Define a worker class with
handle_message(). - When
Node.start(main)is called, it turns themainfunction itself into a worker that can send and receive messages like other workers. This is whymainis able to communicate withgreeter. - Start the
Greeterworker with a unique name (greeter). - Send a message from
mainto thegreeterworker. - The
greeterworker processes the message and replies. - The
mainworker receives the reply.
Messages
Message Types Messages must be strings. This is because the messaging layer transmits messages across the network, and strings are simple, universal, and work everywhere.- Sender sends message to worker by name
- Message is queued if worker is busy
- Worker processes messages one at a time
- Worker can optionally reply
- Sender receives reply (if waiting)
State
Stateful Workers Workers can maintain state across messages. This is safe because workers process one message at a time:- Session management
- Connection pooling
- Rate limiting
- Caching
- Accumulating results
__init__() for one-time setup:
Communication
Message Patterns
Fire and Forget Send a message without waiting for a reply:Error Handling
Timeout Errors Symptoms:RuntimeError with “timeout” in the message
Possible causes:
- Worker doing heavy computation (increase timeout)
- Worker crashed (check logs)
- Network issues (for distributed workers)
- Worker stuck on previous message
Distribution
Architecture
Nodes and Pods One of the most powerful features: workers can run on different machines and communicate seamlessly.clones to create multiple machines running the same container:
autonomy.yaml
Implementation
Node Discovery Discover and connect to remote nodes:images/main/main.py
Zone.nodes()discovers all nodes matching a filter- Start workers on each remote node
- Send messages to workers by name—they run on different machines!
- Messages are automatically routed across the network
Complete Multi-Node Example
Here’s a complete working example that demonstrates communication between nodes running on different pods. Architecture:autonomy.yaml
images/runner/main.py
images/client/main.py
images/runner/Dockerfile
images/client/Dockerfile
- Use
Zone.nodes(node, filter="runner")to discover nodes by pod name prefix - Implement a retry loop to wait for nodes to become available
- Messages are automatically routed across pods and machines
- Workers on remote nodes are accessed the same way as local workers
- The
filterparameter matches against pod names (e.g., “runner” matches “runner-pod”)
Patterns
Echo Worker - Simple message echo:Operations
Monitoring
Listing Workers - Monitor active workers across your system:Troubleshooting
Worker Not Responding Symptoms:send_and_receive() times out
Solutions:
- Check worker was started successfully:
await node.list_workers() - Increase timeout for complex operations
- Check logs for errors in worker’s
handle_message() - Verify message format (must be a string)
TypeError when sending messages
Cause: Trying to send non-string messages
Solution:

