vs Dapr#
TL;DR#
Dapr and Asya both inject a sidecar into your Kubernetes pods -- but for different reasons. Dapr is a general-purpose distributed application runtime that abstracts cloud services behind stable HTTP/gRPC APIs. Asya is a purpose-built actor mesh where stateless handlers communicate through durable queues, the message carries its own route, and every actor scales independently to zero via KEDA. Portable microservice abstraction? Dapr. Queue-based AI/ML pipelines with per-actor GPU scaling and no SDK? Asya.
At a Glance#
| 🎭 | Dapr | |
|---|---|---|
| One-liner | Actor mesh for AI workloads on K8s | Portable distributed application runtime |
| Execution model | Choreography: envelope carries the route | Orchestration: app calls Dapr APIs to publish, invoke, manage state |
| Sidecar role | Queue I/O, routing, retries, envelope lifecycle | API gateway to pluggable infrastructure (state, pub/sub, bindings, actors) |
| Handler UX | Pure dict -> dict Python function |
Any HTTP/gRPC server; use Dapr SDK for convenience |
| Actor model | Stateless by default; optional state proxy sidecar | Virtual actors (turn-based, single-threaded, in-memory state) |
| Scaling | Per-actor via KEDA (queue depth) | App-level; KEDA available but not integrated by default |
| Scale to zero | 🟢 Native (KEDA scales pods 0-N) | 🔴 Actor placement requires running hosts |
| State management | State travels in envelope; optional state proxy (S3, Redis, NATS KV) | Built-in state store API (Redis, CosmosDB, DynamoDB, 20+ backends) |
| Pub/Sub | Transport layer: SQS, RabbitMQ, GCP Pub/Sub | Component API: Kafka, RabbitMQ, Redis Streams, 15+ backends |
| Workflow engine | Flow DSL compiles Python to actor routes (no runtime engine) | Dapr Workflow: durable execution via replay (similar to Temporal) |
| K8s native | 🟢 CRDs, Helm, Crossplane, GitOps | 🟡 Runs on K8s but no CRDs for apps; Dapr components are CRDs |
| SDK requirement | ✅ None (plain Python functions) | ⚠️ Optional but recommended (Go, Python, Java, .NET, JS, Rust) |
| Dynamic routing | ✅ Actors rewrite route.next at runtime |
⚠️ App-level: publish to different topics or invoke different services |
| Agentic support | ✅ A2A, MCP, pause/resume, FLY streaming | ⚠️ Not built-in; achievable via workflows + pub/sub |
| Maturity | 🟡 Alpha (production at Delivery Hero) | 🟢 Mature (CNCF Graduated, v1.x stable) |
Architecture Comparison#
Dapr Sidecar Model#
Dapr injects a daprd sidecar that exposes a local HTTP/gRPC API to the
app container. The app calls localhost:3500 to perform state operations,
publish messages, invoke services, or interact with virtual actors. Dapr
translates these calls to the configured backend using component definitions
-- Kubernetes CRDs that describe the backing service.
┌──────────────────────────────────┐
│ Pod │
│ ┌────────┐ ┌──────────────┐ │
│ │ Your │───>│ daprd │──────> Redis / Kafka / ...
│ │ App │<───│ :3500 HTTP │ │
│ └────────┘ └──────────────┘ │
└──────────────────────────────────┘
The app drives the sidecar: it decides when to read state, publish events, or invoke services.
Asya Sidecar Model#
Asya also injects a sidecar, but the relationship is inverted. The sidecar
drives the app: it pulls messages from the queue, delivers the envelope to the
handler over a Unix socket, receives the response, and routes it to the next
queue based on the envelope's route.next field.
┌──────────────────────────────────┐
│ Pod │
│ ┌────────────┐ ┌────────────┐ │
│ │ asya- │─>│ Runtime │ │ SQS / RabbitMQ /
│ │ sidecar │<─│ (Python) │ │ GCP Pub/Sub
│ └─────┬──────┘ └────────────┘ │
│ └─────────────────────────────> Next actor's queue
└──────────────────────────────────┘
The handler never calls the sidecar. It receives a dict, returns a dict, and the sidecar handles everything else. Dapr's sidecar is a service API the app calls. Asya's sidecar is a message pump that calls the app.
Developer Experience#
Consider building a 3-step pipeline: validate input, call an LLM, store the result.
Dapr#
# app.py -- HTTP server; each handler publishes to the next topic via Dapr SDK
from dapr.clients import DaprClient
from flask import Flask, request
app = Flask(__name__)
@app.route("/validate", methods=["POST"])
def validate():
data = request.json
data["validated"] = True
with DaprClient() as client:
client.publish_event("pubsub", "llm-topic", json.dumps(data))
return "", 200
@app.route("/call-llm", methods=["POST"])
def call_llm():
data = request.json
data["response"] = model.generate(data["prompt"])
with DaprClient() as client:
client.publish_event("pubsub", "store-topic", json.dumps(data))
return "", 200
Each handler must know the next topic to publish to. Routing is hardcoded in application code. You also need Dapr component CRDs for the pub/sub and state store, plus subscription resources wiring topics to endpoints.
Asya#
# handler.py -- plain functions, no SDK, no imports
def validate(state: dict) -> dict:
return {**state, "validated": True}
def call_llm(state: dict) -> dict:
state["response"] = model.generate(state["prompt"])
return state
def store_result(state: dict) -> dict:
db.save(state)
return state
# asyncactor.yaml
apiVersion: asya.sh/v1alpha1
kind: AsyncActor
metadata:
name: call-llm
spec:
image: my-pipeline:latest
handler: handler.call_llm
scaling:
minReplicaCount: 0
maxReplicaCount: 10
resiliency:
actorTimeout: 300s
policies:
default:
maxAttempts: 3
backoff: exponential
Handlers have no knowledge of routing, topics, or infrastructure. The envelope carries the route. Retry policies and scaling live in the Kubernetes manifest.
When to Choose Dapr#
Dapr is a mature CNCF graduated project with broad adoption. It is the stronger choice when:
- Polyglot microservices -- you run services in Go, Java, .NET, Python, and Rust side by side and want a uniform API for state, pub/sub, and service invocation across all languages.
- Service-to-service invocation -- Dapr provides built-in service discovery, mutual TLS, and retries for synchronous request/reply between microservices.
- State store abstraction -- your services need key-value state access across 20+ backends (Redis, CosmosDB, PostgreSQL, DynamoDB) with transactions and ETags, and you want to swap backends without code changes.
- Virtual actor requirements -- Dapr's actor model provides turn-based concurrency, timers, and reminders with automatic placement across hosts. Useful for per-entity stateful processing (IoT devices, user sessions, game objects).
- Bindings and triggers -- Dapr supports input/output bindings to external systems (Cron, SMTP, Twilio, S3) with a uniform component model.
- Non-Kubernetes environments -- Dapr runs standalone (self-hosted mode) on bare metal, VMs, or edge devices. Asya requires Kubernetes.
When to Choose Asya#
Asya is purpose-built for AI/ML workloads on Kubernetes:
- Per-actor GPU scaling -- an LLM inference actor on A100 GPUs scales 0-5 while a preprocessing actor on CPU scales 0-50, each driven by its own queue depth. Dapr has no built-in per-handler autoscaling.
- Scale-to-zero -- KEDA scales actor pods to zero when queues are empty. GPU pods cost nothing between batches. Dapr virtual actors require running host processes to maintain placement.
- No SDK lock-in -- handlers are plain Python functions (
dict -> dict). No SDK, no decorators, no HTTP server boilerplate. Swap the function, redeploy. - Routing is data, not code -- the envelope carries its route. Actors can
dynamically rewrite
route.nextat runtime (e.g., an LLM judge routes high-confidence results to storage and low-confidence to human review). In Dapr, routing logic lives in application code. - Simpler mental model for pipelines -- one abstraction (actor) instead of components, subscriptions, topics, bindings, and service invocation. Platform engineers own the YAML; data scientists own the Python function.
- K8s-native operations -- AsyncActor is a CRD. Deploy with
kubectl apply, manage with Helm, compose with Crossplane. Full GitOps compatibility. - Agentic AI patterns -- built-in A2A and MCP gateway, pause/resume for human-in-the-loop, FLY streaming for live token output. Dapr has no native agent protocol support.
- Queue-native resilience -- if an actor pod is evicted mid-processing, the message reappears and another replica picks it up. No checkpointing needed.