Your ChatAgent works. It stores conversation history, manages state across activations, and publishes events. But now you need something more sophisticated: a multi-agent system where specialized actors collaborate on complex tasks.
Picture an AI customer support system. When a customer sends a message, the ChatAgent shouldn't do everything itself. It should delegate: route the message to a ResponseAgent that generates intelligent replies, while a MemoryAgent tracks conversation context across sessions. Each agent has a single responsibility, isolated state, and clear boundaries.
This is the actor coordination problem. How do actors talk to each other? How do you build hierarchies where parent actors manage child workers? How do peers collaborate without creating tangled dependencies?
The answer is ActorProxy: Dapr's mechanism for actor-to-actor communication.
When one actor needs to invoke methods on another actor, it doesn't call methods directly. Direct method calls would break actor isolation and turn-based concurrency guarantees. Instead, actors communicate through ActorProxy, which routes requests through the Dapr sidecar.
Output:
The ActorProxy.create() call doesn't actually create or activate the target actor. It creates a local proxy object that knows how to route requests. The target actor activates on first method invocation, following virtual actor semantics.
When defining actor methods in Python, use snake_case for the implementation but PascalCase for the @actormethod decorator name:
When invoking via proxy, use the PascalCase name from the decorator:
The parent-child pattern establishes a hierarchy where one actor manages and coordinates others. The parent actor creates child actor IDs, delegates tasks to them, and aggregates results.
Consider a ChatAgent that delegates response generation to a ResponseAgent:
Output (when invoking ChatAgent):
The ResponseAgent handles the actual response generation:
Output (when ResponseAgent activates):
Notice the predictable ID pattern: when ChatAgent with ID user1 needs to delegate, it creates a ResponseAgent with ID response-user1. This convention enables:
Not all actor relationships are hierarchical. Sometimes actors communicate as equals, each maintaining their own domain while collaborating on shared workflows.
Consider a ResponseAgent that queries a MemoryAgent for conversation context:
Now ResponseAgent can query MemoryAgent as a peer:
Output (when ResponseAgent queries MemoryAgent):
Here's a complete example showing parent-child and peer-to-peer patterns working together:
Output (complete flow):
A common question: "What if the target actor doesn't exist when I call it?"
The answer reveals a key insight about Dapr's virtual actor model: actors don't need to exist before you call them. When you invoke an actor that hasn't been activated:
This means your ChatAgent can delegate to a ResponseAgent that has never been called before. The first method invocation creates and activates it automatically.
You built a dapr-deployment skill earlier. Does it understand actor communication patterns?
"Using my dapr-deployment skill, generate code for two actors that communicate: a TaskManagerActor that delegates to WorkerActors using ActorProxy."
If you found gaps:
"Help me design a parent-child actor hierarchy for a task processing system. I need a TaskManagerActor that delegates to workers, tracking completion via predictable IDs like 'worker-{task_id}'."
"I have an OrderActor and an InventoryActor. When OrderActor processes an order, it should query InventoryActor to check stock availability before completing. Show me the ActorProxy calls."
"My ChatAgent calls ResponseAgent, but I get a 'Method not found' error. Here is my interface and my proxy call. What is wrong with the naming convention?"
Actor-to-actor communication adds latency (each proxy call goes through the Dapr sidecar) and complexity (more actors to debug). Start with simple hierarchies before building deep actor chains. For synchronous request-response patterns, consider whether actors are the right abstraction or whether direct service invocation would be simpler.