USMAN’S INSIGHTS
AI ARCHITECT
  • Home
  • About
  • Thought Leadership
  • Book
Press / Contact
USMAN’S INSIGHTS
AI ARCHITECT
⌘F
HomeBook
HomeBookAgent Hello World: Building Your First Stateful Actor
Previous Chapter
The Actor Model
Next Chapter
Chat Actor - Stateful Conversations
AI NOTICE: This is the table of contents for the SPECIFIC CHAPTER only. It is NOT the global sidebar. For all chapters, look at the main navigation.

On this page

42 sections

Progress0%
1 / 42

Muhammad Usman Akbar Entity Profile

Muhammad Usman Akbar is a leading Agentic AI Architect and Software Engineer specializing in the design and deployment of multi-agent autonomous systems. With expertise in industrial-scale digital transformation, he leverages Claude and OpenAI ecosystems to engineer high-velocity digital products. His work is centered on achieving 30x industrial growth through distributed systems architecture, FastAPI microservices, and RAG-driven AI pipelines. Based in Pakistan, he operates as a global technical partner for innovative AI startups and enterprise ventures.

USMAN’S INSIGHTS
AI ARCHITECT

Transforming businesses into autonomous AI ecosystems. Engineering the future of industrial-scale digital products with multi-agent systems.

30X Growth
AI-First
Innovation

Navigation

  • Home
  • Book
  • About
  • Contact
Let's Collaborate

Have a Project in Mind?

Let's build something extraordinary together. Transform your vision into autonomous AI reality.

Start Your Transformation

© 2026 Muhammad Usman Akbar. All rights reserved.

Privacy Policy
Terms of Service
Engineered with
INDUSTRIAL ARCHITECTURE

Hello Actors - Your First Actor

You understand the Actor Model from Lesson 1: actors encapsulate state and behavior, process messages one at a time, and communicate through asynchronous messages. Now it's time to build one.

In this lesson, you'll create a HelloAgent actor that stores greeting messages. Each user gets their own actor instance with isolated state. When user "alice" sends greetings, they're stored separately from user "bob's" greetings. This is the foundation for building AI agents where each conversation, each task, each user session has its own stateful actor.

By the end of this lesson, you'll have a working actor that:

  • Accepts greeting messages and stores them in a history list
  • Retrieves the greeting history on demand
  • Limits history to the last 5 entries (memory efficiency)
  • Persists state across actor deactivation and reactivation

The Actor Creation Pattern

Building a Dapr actor requires four pieces working together:

text
1. Actor Interface Define WHAT methods the actor exposes | v 2. Actor Implementation Define HOW the actor behaves | v 3. Actor Registration Connect actor to FastAPI via DaprActor | v 4. Actor Invocation Call actor methods via ActorProxy

Let's build each piece.

Step 1: Define the Actor Interface

The actor interface declares what methods your actor exposes. It's a contract that clients use to invoke the actor. In Python, you define it as an abstract base class extending ActorInterface:

python
from dapr.actor import ActorInterface, actormethod class HelloAgentInterface(ActorInterface): @actormethod(name="AddGreeting") async def add_greeting(self, greeting_data: dict) -> None: """Add a greeting to history.""" ... @actormethod(name="GetGreetingHistory") async def get_greeting_history(self) -> list[dict] | None: """Retrieve greeting history.""" ...

Output: No output yet - this is just the interface definition. The ... (ellipsis) is valid Python syntax for abstract method bodies.

Naming Convention: Snake_Case vs PascalCase

Notice the dual naming:

  • Method name in Python uses snake_case: add_greeting
  • Decorator name uses PascalCase: AddGreeting

The decorator name is what external clients use when invoking the actor via HTTP or ActorProxy. The Python method name is what you use internally. This follows Dapr's convention across all SDKs.

python
# When invoking from ActorProxy, use the decorator name: await proxy.AddGreeting({"message": "Hello!"}) # PascalCase # Inside the actor implementation, use the Python name: async def add_greeting(self, greeting_data: dict): # snake_case

Step 2: Implement the Actor

The actor implementation extends both the Actor base class and your interface:

python
from dapr.actor import Actor import logging logging.basicConfig(level=logging.INFO) class HelloAgent(Actor, HelloAgentInterface): def __init__(self, ctx, actor_id): super().__init__(ctx, actor_id) self._history_key = f"history-{actor_id.id}" async def _on_activate(self) -> None: """Called when actor is activated (first message or after GC).""" logging.info(f"Activating actor for {self._history_key}") try: history = await self._state_manager.get_state(self._history_key) if history is None: logging.info(f"State not found, initializing empty history") await self._state_manager.set_state(self._history_key, []) else: logging.info(f"Loaded existing history: {len(history)} items") except Exception as e: logging.warning(f"Non-critical error in _on_activate: {e}") await self._state_manager.set_state(self._history_key, []) async def add_greeting(self, greeting_data: dict) -> None: """Add a greeting to history, keeping last 5.""" history = await self._state_manager.get_state(self._history_key) current_history = history if isinstance(history, list) else [] current_history.append(greeting_data) # Limit to last 5 greetings if len(current_history) > 5: current_history = current_history[-5:] await self._state_manager.set_state(self._history_key, current_history) logging.info(f"Added greeting for {self._history_key}: {greeting_data}") async def get_greeting_history(self) -> list[dict] | None: """Retrieve greeting history.""" history = await self._state_manager.get_state(self._history_key) return history if isinstance(history, list) else []

Output: No direct output - this defines the class. When the actor is activated, you'll see log messages like:

text
INFO:root:Activating actor for history-alice INFO:root:State not found, initializing empty history

Key Implementation Details

The _state_manager: Every actor has a built-in state manager that persists data to the configured state store. You don't manage Redis connections - you just call get_state() and set_state().

The _on_activate lifecycle hook: This runs when the actor is first invoked or when it's reactivated after being garbage-collected due to idleness. Use it to initialize default state.

Unique state keys: Each actor instance uses its own key (history-{actor_id}). Actor "alice" stores state under history-alice, actor "bob" under history-bob. Complete isolation.

Error handling in _on_activate: The try/except ensures the actor initializes even if state retrieval has issues. A robust actor shouldn't crash on activation.

Step 3: Register the Actor with FastAPI

Now connect the actor to your FastAPI application using DaprActor:

python
from fastapi import FastAPI from dapr.ext.fastapi import DaprActor from dapr.actor import ActorProxy, ActorId app = FastAPI( title="HelloAgentService", description="DACA Step 1: Dapr Actor Fundamentals" ) # Add Dapr Actor Extension actor = DaprActor(app) @app.on_event("startup") async def startup(): """Register actor types on application startup.""" await actor.register_actor(HelloAgent) logging.info(f"Registered actor: {HelloAgent.__name__}")

Output: When the application starts, you'll see:

text
INFO: Application startup complete. INFO:root:Registered actor: Hello Agent

What DaprActor Does

DaprActor(app) adds several routes to your FastAPI application that Dapr uses internally:

EndpointPurpose
GET /healthzActor system health check
GET /dapr/configReturns registered actor types and config
POST /actors/{actorType}/{actorId}/method/{methodName}Method invocation (Dapr calls this)

You don't call these endpoints directly - Dapr's sidecar does. But you can verify registration:

bash
curl http://localhost:8000/dapr/config

Output:

json
{ "actorIdleTimeout": "1h0m0s0ms0us", "actorScanInterval": "0h0m30s0ms0us", "drainOngoingCallTimeout": "0h1m0s0ms0us", "drainRebalancedActors": true, "entitiesConfig": [], "entities": [ "HelloAgent" ] }

The entities array confirms HelloAgent is registered.

Step 4: Invoke the Actor

Create FastAPI endpoints that invoke the actor using ActorProxy:

python
@app.post("/greet/{actor_id}") async def add_greeting(actor_id: str, greeting: dict): """Add a greeting to the actor's history.""" proxy = ActorProxy.create("HelloAgent", ActorId(actor_id), HelloAgentInterface) await proxy.AddGreeting(greeting) return {"status": "Greeting added", "actor_id": actor_id} @app.get("/greet/{actor_id}/history") async def get_greeting_history(actor_id: str): """Retrieve the actor's greeting history.""" proxy = ActorProxy.create("HelloAgent", ActorId(actor_id), HelloAgentInterface) history = await proxy.GetGreetingHistory() return {"actor_id": actor_id, "history": history}

Output: After sending greetings:

bash
curl -X POST http://localhost:8000/greet/alice \ -H "Content-Type: application/json" \ -d '{"message": "Hello, World!"}'

Response:

json
{"status": "Greeting added", "actor_id": "alice"}

Retrieving history:

bash
curl http://localhost:8000/greet/alice/history

Response:

json
{"actor_id": "alice", "history": [{"message": "Hello, World!"}]}

ActorProxy Explained

python
proxy = Actor Proxy.create("Hello Agent", Actor Id(actor_id), Hello Agent Interface)

This creates a client for communicating with a specific actor:

  • "HelloAgent": The actor type (must match registered name)
  • ActorId(actor_id): The unique identifier for this actor instance
  • HelloAgentInterface: The interface for type-safe method calls

The proxy handles all communication with the Dapr sidecar. Your HTTP request to /greet/alice becomes an internal call through Dapr to the HelloAgent actor with ID "alice".

Step 5: Configure the State Store

Actors need a state store with actor support enabled. Add this metadata to your state store component:

yaml
# components/statestore.yaml apiVersion: dapr.io/v1alpha1 kind: Component metadata: name: statestore namespace: default spec: type: state.redis version: v1 metadata: - name: redisHost value: redis-master.default.svc.cluster.local:6379 - name: redisPassword value: "" - name: actorStateStore # CRITICAL SETTING value: "true"

Output: No direct output - this is configuration. When applied, Dapr uses this state store for actor persistence.

The Critical Setting: actorStateStore

The actorStateStore: "true" metadata is required for actors. Without it:

  • Actor registration succeeds
  • Actor invocation succeeds
  • But state persistence fails silently

If your actor state isn't persisting, check this setting first.

Verifying State in Redis

You can inspect actor state directly in Redis:

bash
# Connect to Redis kubectl exec -it redis-master -- redis-cli # List actor keys KEYS *HelloAgent*

Output:

text
1) "daca-ai-app||Hello Agent||alice||history-alice"

The key format is {app-id}||{actor-type}||{actor-id}||{state-key}.

bash
# Get the state value HGETALL "daca-ai-app||Hello Agent||alice||history-alice"

Output:

text
1) "data" 2) "[{\"message\": \"Hello, World!\"}]" 3) "version" 4) "1"

Complete Working Example

Here's the full main.py combining all pieces:

python
import logging from fastapi import FastAPI from dapr.ext.fastapi import DaprActor from dapr.actor import Actor, ActorInterface, ActorProxy, ActorId, actormethod # Configure logging logging.basicConfig(level=logging.INFO) # FastAPI application app = FastAPI(title="HelloAgentService", description="Dapr Actor Fundamentals") # Add Dapr Actor Extension actor = DaprActor(app) # Define the actor interface class HelloAgentInterface(ActorInterface): @actormethod(name="AddGreeting") async def add_greeting(self, greeting_data: dict) -> None: ... @actormethod(name="GetGreetingHistory") async def get_greeting_history(self) -> list[dict] | None: ... # Implement the actor class HelloAgent(Actor, HelloAgentInterface): def __init__(self, ctx, actor_id): super().__init__(ctx, actor_id) self._history_key = f"history-{actor_id.id}" async def _on_activate(self) -> None: logging.info(f"Activating actor for {self._history_key}") try: history = await self._state_manager.get_state(self._history_key) if history is None: await self._state_manager.set_state(self._history_key, []) except Exception as e: logging.warning(f"Non-critical error: {e}") await self._state_manager.set_state(self._history_key, []) async def add_greeting(self, greeting_data: dict) -> None: history = await self._state_manager.get_state(self._history_key) current_history = history if isinstance(history, list) else [] current_history.append(greeting_data) if len(current_history) > 5: current_history = current_history[-5:] await self._state_manager.set_state(self._history_key, current_history) logging.info(f"Added greeting for {self._history_key}") async def get_greeting_history(self) -> list[dict] | None: history = await self._state_manager.get_state(self._history_key) return history if isinstance(history, list) else [] # Register the actor @app.on_event("startup") async def startup(): await actor.register_actor(HelloAgent) logging.info(f"Registered actor: {HelloAgent.__name__}") # FastAPI endpoints to invoke the actor @app.post("/greet/{actor_id}") async def add_greeting(actor_id: str, greeting: dict): proxy = ActorProxy.create("HelloAgent", ActorId(actor_id), HelloAgentInterface) await proxy.AddGreeting(greeting) return {"status": "Greeting added", "actor_id": actor_id} @app.get("/greet/{actor_id}/history") async def get_greeting_history(actor_id: str): proxy = ActorProxy.create("HelloAgent", ActorId(actor_id), HelloAgentInterface) history = await proxy.GetGreetingHistory() return {"actor_id": actor_id, "history": history}

Testing the Actor

Run the application with Dapr:

bash
# Start the application tilt up

Then test with curl or the Swagger UI at http://localhost:8000/docs:

bash
# Add greetings for user alice curl -X POST http://localhost:8000/greet/alice \ -H "Content-Type: application/json" \ -d '{"message": "Hello!"}' curl -X POST http://localhost:8000/greet/alice \ -H "Content-Type: application/json" \ -d '{"message": "How are you?"}' # Get alice's history curl http://localhost:8000/greet/alice/history # Add greeting for user bob (separate actor) curl -X POST http://localhost:8000/greet/bob \ -H "Content-Type: application/json" \ -d '{"message": "Hi Bob!"}' # Get bob's history (only his greetings) curl http://localhost:8000/greet/bob/history

Output: Alice's history:

json
{"actor_id": "alice", "history": [{"message": "Hello!"}, {"message": "How are you?"}]}

Bob's history:

json
{"actor_id": "bob", "history": [{"message": "Hi Bob!"}]}

Each actor ID creates a separate actor instance with isolated state.

Common Errors and Solutions

ErrorCauseSolution
ACTOR_TYPE_NOT_FOUNDActor not registeredVerify register_actor() in startup
ACTOR_METHOD_NOT_FOUNDMethod not in interfaceAdd @actormethod decorator to interface
State not persistingMissing actorStateStoreAdd actorStateStore: "true" to component
KeyError on state accessState doesn't existUse _on_activate to initialize default state

Reflect on Your Skill

You extended your dapr-deployment skill in Lesson 0 to include actor patterns. Does it now cover the complete actor creation workflow?

Test Your Skill

"Using my dapr-deployment skill, generate a complete actor that tracks user preferences with get_preference and set_preference methods. Include the interface, implementation, registration, and invocation code."

Does your skill produce:

  • An interface with @actormethod decorators using proper naming?
  • An implementation with _on_activate for state initialization?
  • Registration with DaprActor(app) and register_actor()?
  • Invocation endpoints using ActorProxy.create()?

Identify Gaps

If the generated code is missing pieces:

  • Interface without @actormethod decorators
  • Implementation without _on_activate hook
  • Missing state manager usage
  • ActorProxy with wrong parameter order

Try With AI

Open your AI companion and work through these scenarios collaboratively.

Prompt 1: Build Your First Actor

"Help me create a HelloAgent actor that stores greeting messages. I need an actor interface with AddGreeting and GetGreetingHistory methods, an implementation that limits history, and FastAPI registration."

Prompt 2: Debug a Registration Problem

"I created my HelloAgent actor but I'm getting ACTOR_TYPE_NOT_FOUND when I invoke it. Help me debug this. What should I check in the startup registration and sidecar logs?"

Prompt 3: Extend with Timestamps

"I want to extend my HelloAgent to track timestamps with each greeting. Show me how to add timestamps safely using ISO format strings and verify the data serializes correctly to Redis."