diff --git a/daprdocs/content/en/developing-applications/building-blocks/actors/actors-app-initiated-streams.md b/daprdocs/content/en/developing-applications/building-blocks/actors/actors-app-initiated-streams.md new file mode 100644 index 00000000000..d77265aa046 --- /dev/null +++ b/daprdocs/content/en/developing-applications/building-blocks/actors/actors-app-initiated-streams.md @@ -0,0 +1,115 @@ +--- +type: docs +title: "Actor app-initiated gRPC event streams (Alpha)" +linkTitle: "App-initiated streams (Alpha)" +weight: 55 +description: "Receive actor callbacks over an app-initiated gRPC stream without exposing a server port" +--- + +{{% alert title="Alpha" color="warning" %}} +`SubscribeActorEventsAlpha1` is in alpha. The API shape may change in a future release. +{{% /alert %}} + +By default, Dapr delivers actor callbacks — method invocations, reminders, timers, and deactivations — by calling **inbound** HTTP or gRPC endpoints on the application. Each actor-hosting pod must expose a server port that the Dapr sidecar can reach. + +Starting with Dapr v1.18, actor hosts can instead open a **single bidirectional gRPC stream from the app to the sidecar** (`SubscribeActorEventsAlpha1`) and receive all four callback types over that connection. The app is the gRPC _client_: it dials daprd, opens the stream, and waits for callbacks. No inbound port is required. + +This aligns actor callback delivery with how Dapr handles pub/sub streaming subscriptions (`SubscribeTopicEventsAlpha1`), configuration watch streams, and scheduler job streams. + +The [Go SDK](https://github.com/dapr/go-sdk) provides a high-level helper for this API ([`client.SubscribeActorEvents`](https://github.com/dapr/go-sdk/tree/main/examples/actor-grpc)). Other Dapr SDKs currently expose `SubscribeActorEventsAlpha1` only through their generated gRPC client. See [SDK support](#sdk-support) for details. + +## Why app-initiated streams? + +The app-initiated approach is useful when NetworkPolicies restrict inbound traffic to application pods, when actors run in restricted-networking environments, or when you want a single connection-management surface instead of per-callback HTTP/gRPC routes. + +| | Traditional callbacks | App-initiated stream | +|---|---|---| +| **Connection direction** | sidecar → app | app → sidecar | +| **App server port required** | Yes | No | +| **NetworkPolicy / firewall** | Must allow sidecar→app inbound | Only app→sidecar outbound needed | +| **Callback types** | Separate endpoints per type | All four types on one stream | +| **SDK support** | All SDKs | Go SDK helper; other SDKs via generated gRPC client | +| **Stability** | Stable | Alpha (v1.18+) | + +## How it works + +The protocol follows a request–response pairing over a bidirectional gRPC stream: + +1. **App opens the stream.** The app calls `SubscribeActorEventsAlpha1` on the Dapr gRPC service and sends an initial registration message (`SubscribeActorEventsRequestInitialAlpha1`) listing the actor types it hosts, together with optional runtime configuration overrides (idle timeout, drain settings, reentrancy). + +2. **Dapr acknowledges registration.** daprd responds with a `SubscribeActorEventsResponseInitialAlpha1` on the stream. An empty message body signals success; errors surface as a gRPC stream error. + +3. **Dapr sends callbacks.** Whenever an actor method is invoked, a reminder or timer fires, or an actor is deactivated, daprd sends a `SubscribeActorEventsResponseAlpha1` message down the stream. Each message carries a unique correlation `id`. + +4. **App responds.** The app processes the callback and sends back a `SubscribeActorEventsRequestAlpha1` message containing the matching `id`. The response type determines the action: + + | Callback received | App sends back | + |---|---| + | `invoke_request` (method call) | `invoke_response` with response payload | + | `reminder_request` | `reminder_response` (optionally `cancel: true` to stop the reminder) | + | `timer_request` | `timer_response` (optionally `cancel: true` to stop the timer) | + | `deactivate_request` | `deactivate_response` (ack only, no payload) | + +5. **Error signaling.** If the app cannot handle a callback (for example, the actor method does not exist), it sends a `request_failed` message with the originating `id`, a gRPC status code, and an optional message. daprd maps `codes.NotFound` to a permanent non-retryable failure. + +### Reconnection and rolling restarts + +Dapr supports multiple concurrent streams from the same app process. This enables zero-downtime rolling restarts: + +- When a new pod opens a stream, daprd routes all **new** callbacks to the newest connection. +- The **older** connection continues to receive responses for callbacks it already sent; it drains naturally. +- Once all in-flight work on an older connection completes, that connection can be closed safely. + +Apps should reconnect with exponential back-off if the stream is interrupted. + +## NetworkPolicy and firewall considerations + +Because the app **initiates** the connection, the traffic direction is: + +``` +app pod → daprd sidecar (gRPC port, default 50001) +``` + +In environments with restrictive NetworkPolicies, this means you no longer need a rule that allows the sidecar to initiate inbound connections to the app pod. However, you do need egress from the app pod to the sidecar's gRPC port. + +{{% alert title="Operator note" color="primary" %}} +If you previously locked down actor-hosting pods by denying all inbound traffic from the sidecar, you can remove that inbound rule for pods that use `SubscribeActorEventsAlpha1`. Keep the rule in place if the same pods also use traditional HTTP/gRPC actor callbacks (the two modes can coexist during migration). + +The daprd gRPC port is configurable via the `dapr.io/grpc-port` annotation (default: `50001`). Ensure egress from app pods to that port on `localhost` / the sidecar is permitted. +{{% /alert %}} + +### Example NetworkPolicy (Kubernetes) + +The following policy restricts ingress to actor-hosting pods and allows egress to the sidecar gRPC port. Adjust the port if you set `dapr.io/grpc-port` to a non-default value. + +```yaml +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: actor-app-initiated-stream +spec: + podSelector: + matchLabels: + app: my-actor-service + policyTypes: + - Ingress + - Egress + ingress: [] # no inbound rules required for app-initiated streams + egress: + - ports: + - protocol: TCP + port: 50001 # daprd gRPC port (adjust if changed via annotation) +``` + +## SDK support + +The [Go SDK](https://github.com/dapr/go-sdk) provides a high-level helper, [`client.SubscribeActorEvents`](https://github.com/dapr/go-sdk/tree/main/examples/actor-grpc), that manages the stream lifecycle, callback dispatch, and reconnection for you. Other Dapr SDKs currently expose `SubscribeActorEventsAlpha1` only through their generated gRPC client; call it directly as shown in the [how-to guide]({{% ref "howto-actors-app-initiated-streams" %}}). + +## Related links + +- [How-to: Use actor app-initiated gRPC streams]({{% ref "howto-actors-app-initiated-streams" %}}) +- [Actor API reference — SubscribeActorEventsAlpha1]({{% ref "actors_api#subscribeactoreventsalpha1-grpc" %}}) +- [Actors overview]({{% ref "actors-overview" %}}) +- [Actor runtime configuration]({{% ref "actors-runtime-config" %}}) +- [Runtime PR dapr/dapr#9812](https://github.com/dapr/dapr/pull/9812) + diff --git a/daprdocs/content/en/developing-applications/building-blocks/actors/howto-actors-app-initiated-streams.md b/daprdocs/content/en/developing-applications/building-blocks/actors/howto-actors-app-initiated-streams.md new file mode 100644 index 00000000000..e3703c1efdc --- /dev/null +++ b/daprdocs/content/en/developing-applications/building-blocks/actors/howto-actors-app-initiated-streams.md @@ -0,0 +1,317 @@ +--- +type: docs +title: "How-to: Use actor app-initiated gRPC event streams" +linkTitle: "How-to: App-initiated streams" +weight: 56 +description: "Open a gRPC stream from your app to daprd to receive actor callbacks without exposing a server port" +--- + +{{% alert title="Alpha" color="warning" %}} +`SubscribeActorEventsAlpha1` is in alpha. The API shape may change in a future release. +{{% /alert %}} + +This guide shows how to implement `SubscribeActorEventsAlpha1` in Go using the generated gRPC client. Read the [concept doc]({{% ref "actors-app-initiated-streams" %}}) first for protocol details and when to prefer this approach over traditional callbacks. + +{{% alert title="Using the Go SDK?" color="primary" %}} +The [Dapr Go SDK](https://github.com/dapr/go-sdk) wraps this protocol in a high-level helper — [`client.SubscribeActorEvents`](https://github.com/dapr/go-sdk/tree/main/examples/actor-grpc) — that manages the stream, callback dispatch, and reconnection for you. This guide documents the raw gRPC protocol directly, which is useful for understanding the wire format or for SDKs that do not yet provide a helper. +{{% /alert %}} + +## Prerequisites + +- [Dapr v1.18 or later]({{% ref "getting-started" %}}) +- A state store configured for actors (see [actors overview]({{% ref "actors-overview" %}}) for requirements) +- The Dapr proto definitions for your language (Go example uses `github.com/dapr/dapr/pkg/proto/runtime/v1`) + +## Protocol summary + +```mermaid +sequenceDiagram + participant App + participant daprd as daprd (Dapr sidecar) + App->>daprd: SubscribeActorEventsAlpha1() — open stream + App->>daprd: SubscribeActorEventsRequestInitialAlpha1
(register actor types + config) + daprd-->>App: SubscribeActorEventsResponseInitialAlpha1
(ack — empty = success) + daprd-->>App: invoke_request (id="abc", method="x") + App->>daprd: invoke_response (id="abc", data=...) + daprd-->>App: reminder_request (id="def") + App->>daprd: reminder_response (id="def") + daprd-->>App: deactivate_request (id="ghi") + App->>daprd: deactivate_response (id="ghi") +``` + +Every callback and response is correlated by a unique `id` generated by daprd. The app **must** echo the same `id` on the response. + +## Step 1: Open the stream and register actor types + +The first message sent on the stream must be an `initial_request` advertising the actor types the app hosts. All fields other than `entities` are optional and override Dapr's defaults. + +{{< tabpane text=true >}} + +{{% tab "Go (raw gRPC)" %}} + +```go +package main + +import ( + "context" + "log" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + + runtimev1pb "github.com/dapr/dapr/pkg/proto/runtime/v1" + anypb "google.golang.org/protobuf/types/known/anypb" + durationpb "google.golang.org/protobuf/types/known/durationpb" +) + +func main() { + // Connect to the Dapr sidecar gRPC port (default 50001). + conn, err := grpc.NewClient("localhost:50001", + grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + log.Fatalf("connect: %v", err) + } + defer conn.Close() + + client := runtimev1pb.NewDaprClient(conn) + ctx := context.Background() + + // Open the bidirectional stream. + stream, err := client.SubscribeActorEventsAlpha1(ctx) + if err != nil { + log.Fatalf("open stream: %v", err) + } + + // First message: register the actor types this app hosts. + idleTimeout := durationpb.New(60 * time.Minute) + err = stream.Send(&runtimev1pb.SubscribeActorEventsRequestAlpha1{ + RequestType: &runtimev1pb.SubscribeActorEventsRequestAlpha1_InitialRequest{ + InitialRequest: &runtimev1pb.SubscribeActorEventsRequestInitialAlpha1{ + Entities: []string{"MyActor"}, + ActorIdleTimeout: idleTimeout, + DrainRebalancedActors: boolPtr(true), + }, + }, + }) + if err != nil { + log.Fatalf("send initial: %v", err) + } + + // Wait for the acknowledgement from daprd. + msg, err := stream.Recv() + if err != nil { + log.Fatalf("recv initial ack: %v", err) + } + if msg.GetInitialResponse() == nil { + log.Fatalf("unexpected first message type") + } + log.Println("registered with Dapr, waiting for callbacks") + + // Enter the callback loop. + handleCallbacks(stream) +} + +func boolPtr(b bool) *bool { return &b } +``` + +{{% /tab %}} + +{{< /tabpane >}} + +## Step 2: Handle callbacks + +After the initial handshake, the stream delivers callbacks from daprd. Each callback message uses a `oneof response_type`. The app must send a correlated response for every callback received. + +{{< tabpane text=true >}} + +{{% tab "Go (raw gRPC)" %}} + +```go +func handleCallbacks(stream runtimev1pb.Dapr_SubscribeActorEventsAlpha1Client) { + for { + msg, err := stream.Recv() + if err != nil { + log.Printf("stream closed: %v", err) + return + } + + switch v := msg.ResponseType.(type) { + + case *runtimev1pb.SubscribeActorEventsResponseAlpha1_InvokeRequest: + req := v.InvokeRequest + log.Printf("invoke %s/%s.%s (id=%s)", req.ActorType, req.ActorId, req.Method, req.Id) + + // Execute the actor method and build the response payload. + result, invokeErr := dispatchMethod(req.ActorType, req.ActorId, req.Method, req.Data) + + resp := &runtimev1pb.SubscribeActorEventsRequestAlpha1{} + if invokeErr != nil { + resp.RequestType = &runtimev1pb.SubscribeActorEventsRequestAlpha1_InvokeResponse{ + InvokeResponse: &runtimev1pb.SubscribeActorEventsRequestInvokeResponseAlpha1{ + Id: req.Id, + Data: []byte(invokeErr.Error()), + Error: true, + }, + } + } else { + resp.RequestType = &runtimev1pb.SubscribeActorEventsRequestAlpha1_InvokeResponse{ + InvokeResponse: &runtimev1pb.SubscribeActorEventsRequestInvokeResponseAlpha1{ + Id: req.Id, + Data: result, + }, + } + } + if err := stream.Send(resp); err != nil { + log.Printf("send invoke response: %v", err) + return + } + + case *runtimev1pb.SubscribeActorEventsResponseAlpha1_ReminderRequest: + req := v.ReminderRequest + log.Printf("reminder %s/%s name=%s (id=%s)", req.ActorType, req.ActorId, req.Name, req.Id) + + // Return cancel=true to cancel the reminder after this firing. + shouldCancel := handleReminder(req.ActorType, req.ActorId, req.Name, req.Data) + + if err := stream.Send(&runtimev1pb.SubscribeActorEventsRequestAlpha1{ + RequestType: &runtimev1pb.SubscribeActorEventsRequestAlpha1_ReminderResponse{ + ReminderResponse: &runtimev1pb.SubscribeActorEventsRequestReminderResponseAlpha1{ + Id: req.Id, + Cancel: shouldCancel, + }, + }, + }); err != nil { + return + } + + case *runtimev1pb.SubscribeActorEventsResponseAlpha1_TimerRequest: + req := v.TimerRequest + log.Printf("timer %s/%s name=%s (id=%s)", req.ActorType, req.ActorId, req.Name, req.Id) + + shouldCancel := handleTimer(req.ActorType, req.ActorId, req.Name, req.Data) + + if err := stream.Send(&runtimev1pb.SubscribeActorEventsRequestAlpha1{ + RequestType: &runtimev1pb.SubscribeActorEventsRequestAlpha1_TimerResponse{ + TimerResponse: &runtimev1pb.SubscribeActorEventsRequestReminderResponseAlpha1{ + Id: req.Id, + Cancel: shouldCancel, + }, + }, + }); err != nil { + return + } + + case *runtimev1pb.SubscribeActorEventsResponseAlpha1_DeactivateRequest: + req := v.DeactivateRequest + log.Printf("deactivate %s/%s (id=%s)", req.ActorType, req.ActorId, req.Id) + + releaseActorState(req.ActorType, req.ActorId) + + if err := stream.Send(&runtimev1pb.SubscribeActorEventsRequestAlpha1{ + RequestType: &runtimev1pb.SubscribeActorEventsRequestAlpha1_DeactivateResponse{ + DeactivateResponse: &runtimev1pb.SubscribeActorEventsRequestDeactivateResponseAlpha1{ + Id: req.Id, + }, + }, + }); err != nil { + return + } + } + } +} + +// Stubs — replace with your actor logic. +func dispatchMethod(actorType, actorID, method string, data []byte) ([]byte, error) { return nil, nil } +func handleReminder(actorType, actorID, name string, data *anypb.Any) bool { return false } +func handleTimer(actorType, actorID, name string, data *anypb.Any) bool { return false } +func releaseActorState(actorType, actorID string) {} +``` + +{{% /tab %}} + +{{< /tabpane >}} + +## Step 3: Signal an error + +If the app cannot process a callback (for example, the actor method is not registered), send a `request_failed` message instead of the typed response. Use gRPC status codes from [google.golang.org/grpc/codes](https://pkg.go.dev/google.golang.org/grpc/codes). + +```go +import "google.golang.org/grpc/codes" + +stream.Send(&runtimev1pb.SubscribeActorEventsRequestAlpha1{ + RequestType: &runtimev1pb.SubscribeActorEventsRequestAlpha1_RequestFailed{ + RequestFailed: &runtimev1pb.SubscribeActorEventsRequestFailedAlpha1{ + Id: req.Id, + Code: uint32(codes.NotFound), // marks the failure as permanent/non-retryable + Message: "actor method not found: " + req.Method, + }, + }, +}) +``` + +{{% alert title="Important" color="warning" %}} +daprd treats `codes.NotFound` as a permanent, non-retryable failure. Use other status codes (e.g. `codes.Internal`) for transient errors that Dapr should retry. +{{% /alert %}} + +## Step 4: Handle reconnection + +The stream is a long-lived connection. If daprd restarts or the network is interrupted, `stream.Recv()` returns an error. Reconnect with exponential back-off: + +```go +import ( + "math/rand" + "time" +) + +func runWithReconnect(ctx context.Context, client runtimev1pb.DaprClient) { + backoff := 1 * time.Second + for { + if ctx.Err() != nil { + return + } + stream, err := client.SubscribeActorEventsAlpha1(ctx) + if err != nil { + log.Printf("open stream error: %v; retrying in %s", err, backoff) + time.Sleep(backoff + time.Duration(rand.Intn(500))*time.Millisecond) + backoff = min(backoff*2, 30*time.Second) + continue + } + backoff = 1 * time.Second // reset on success + + if err := sendInitialRequest(stream); err != nil { + continue + } + if _, err := stream.Recv(); err != nil { // wait for initial ack + continue + } + handleCallbacks(stream) // blocks until stream closes + } +} +``` + +## Runtime configuration fields + +The initial registration message can include the following optional fields to override Dapr's defaults for all actor types on this stream: + +| Field | Type | Description | +|-------|------|-------------| +| `entities` | `[]string` | **Required.** Actor types this app hosts. | +| `actor_idle_timeout` | `Duration` | Deactivate an actor after this idle period. Unset = Dapr default (60 minutes). | +| `drain_ongoing_call_timeout` | `Duration` | How long to wait for in-flight calls during rebalancing. Unset = Dapr default. | +| `drain_rebalanced_actors` | `bool` | If true, wait for drain before deactivating rebalanced actors. Unset = Dapr default. | +| `reentrancy` | `ActorReentrancyConfig` | Enable actor reentrancy and set max stack depth. Default: disabled. | +| `entities_config` | `[]ActorEntityConfig` | Per-actor-type overrides for any of the fields above. | + +See [actor runtime configuration]({{% ref "actors-runtime-config" %}}) for a description of each parameter. + +## Coexistence with traditional callbacks + +The app-initiated stream and traditional HTTP/gRPC callbacks are not mutually exclusive. Pods that have opened `SubscribeActorEventsAlpha1` receive callbacks via the stream; pods that have not opened the stream continue to use traditional inbound endpoints. Migrate all pods before removing inbound ports and NetworkPolicy rules. + +## Related links + +- [Actor app-initiated streams concept]({{% ref "actors-app-initiated-streams" %}}) +- [Actor API reference — SubscribeActorEventsAlpha1]({{% ref "actors_api#subscribeactoreventsalpha1-grpc" %}}) +- [Actor runtime configuration]({{% ref "actors-runtime-config" %}}) +- [Actors overview]({{% ref "actors-overview" %}}) diff --git a/daprdocs/content/en/operations/support/alpha-beta-apis.md b/daprdocs/content/en/operations/support/alpha-beta-apis.md index 730c0bdb6bd..54e2e0843fc 100644 --- a/daprdocs/content/en/operations/support/alpha-beta-apis.md +++ b/daprdocs/content/en/operations/support/alpha-beta-apis.md @@ -15,6 +15,7 @@ description: "List of current alpha and beta APIs" | Cryptography | [Crypto proto](https://github.com/dapr/dapr/blob/5aba3c9aa4ea9b3f388df125f9c66495b43c5c9e/dapr/proto/runtime/v1/dapr.proto#L118) | `v1.0-alpha1/crypto` | The cryptography API enables you to perform **high level** cryptography operations for encrypting and decrypting messages. | [Cryptography API]({{% ref "cryptography-overview.md" %}}) | v1.11 | | Streaming Subscription | [Streaming Subscription proto](https://github.com/dapr/dapr/blob/310c83140b2f0c3cb7d2bef19624df88af3e8e0a/dapr/proto/runtime/v1/dapr.proto#L454) | N/A | Subscription is defined in the application code. Streaming subscriptions are dynamic, meaning they allow for adding or removing subscriptions at runtime. | [Streaming Subscription API]({{% ref "subscription-methods/#streaming-subscriptions" %}}) | v1.14 | | Conversation | [Conversation proto](https://github.com/dapr/dapr/blob/master/dapr/proto/runtime/v1/dapr.proto#L226) | `v1.0-alpha2/conversation` | Converse between different large language models using the conversation API. | [Conversation API]({{% ref "conversation-overview.md" %}}) | v1.15 | +| Actor App-Initiated Streams | [SubscribeActorEventsAlpha1 proto](https://github.com/dapr/dapr/blob/2fe035dfd8d248a3272dd29574ce2e315b8cf570/dapr/proto/runtime/v1/dapr.proto#L125) | N/A | Actor hosts open a single bidirectional gRPC stream to daprd and receive all actor callbacks (invoke, reminder, timer, deactivate) over that connection. Apps do not need to expose a server port. | [Actor app-initiated streams]({{% ref "actors-app-initiated-streams" %}}) | v1.18 | ## Beta APIs diff --git a/daprdocs/content/en/reference/api/actors_api.md b/daprdocs/content/en/reference/api/actors_api.md index 288c4dcafb4..ac3ef46d1db 100644 --- a/daprdocs/content/en/reference/api/actors_api.md +++ b/daprdocs/content/en/reference/api/actors_api.md @@ -741,6 +741,136 @@ Example of getting a health check response from the app: curl -X GET http://localhost:3000/healthz \ ``` +## SubscribeActorEventsAlpha1 (gRPC) + +{{% alert title="Alpha" color="warning" %}} +`SubscribeActorEventsAlpha1` is in **alpha**. The API shape may change in future releases. +{{% /alert %}} + +`SubscribeActorEventsAlpha1` is a bidirectional gRPC streaming RPC on the **Dapr service** (`dapr.proto.runtime.v1.Dapr`). The application is the gRPC _client_: it dials daprd, opens the stream, and receives all actor callbacks — method invocations, reminders, timers, and deactivations — over that single connection. Apps using this RPC do not need to expose an HTTP or gRPC server port. + +See the [actor app-initiated streams concept doc]({{% ref "actors-app-initiated-streams" %}}) and [how-to guide]({{% ref "howto-actors-app-initiated-streams" %}}) for usage guidance and code examples. + +### gRPC service definition + +```protobuf +// On the Dapr service (app dials daprd): +rpc SubscribeActorEventsAlpha1(stream SubscribeActorEventsRequestAlpha1) + returns (stream SubscribeActorEventsResponseAlpha1) {} +``` + +### App → Dapr: SubscribeActorEventsRequestAlpha1 + +Messages sent **from the app to daprd**. The first message must be `initial_request`; all subsequent messages must be responses correlated by `id`. + +| Field (oneof `request_type`) | When to send | +|---|---| +| `initial_request` (`SubscribeActorEventsRequestInitialAlpha1`) | **First message only.** Registers actor types and runtime config. | +| `invoke_response` (`SubscribeActorEventsRequestInvokeResponseAlpha1`) | Response to an `invoke_request` callback. | +| `reminder_response` (`SubscribeActorEventsRequestReminderResponseAlpha1`) | Response to a `reminder_request` callback. | +| `timer_response` (`SubscribeActorEventsRequestReminderResponseAlpha1`) | Response to a `timer_request` callback. | +| `deactivate_response` (`SubscribeActorEventsRequestDeactivateResponseAlpha1`) | Response to a `deactivate_request` callback. | +| `request_failed` (`SubscribeActorEventsRequestFailedAlpha1`) | Sent instead of any typed response when the app cannot handle the callback. | + +#### SubscribeActorEventsRequestInitialAlpha1 + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `entities` | `[]string` | Yes | Actor types hosted by this app. | +| `actor_idle_timeout` | `Duration` | No | Idle timeout before deactivation. Unset = Dapr default (60 min). | +| `drain_ongoing_call_timeout` | `Duration` | No | How long to wait for in-flight calls during rebalancing. Unset = Dapr default. | +| `drain_rebalanced_actors` | `bool` | No | Drain in-flight calls before deactivating rebalanced actors. Unset = Dapr default. | +| `reentrancy` | `ActorReentrancyConfig` | No | Reentrancy configuration for all actor types on this stream. | +| `entities_config` | `[]ActorEntityConfig` | No | Per-actor-type overrides. Each entry must reference a type listed in `entities`. | + +#### SubscribeActorEventsRequestInvokeResponseAlpha1 + +| Field | Type | Description | +|-------|------|-------------| +| `id` | `string` | Correlation ID from the originating `invoke_request`. | +| `data` | `bytes` | Response payload. | +| `metadata` | `map` | Response-level headers, including `content-type`. | +| `error` | `bool` | When `true`, `data` is an application-defined error payload passed verbatim to the caller. | + +#### SubscribeActorEventsRequestReminderResponseAlpha1 + +Used for both `reminder_response` and `timer_response`. + +| Field | Type | Description | +|-------|------|-------------| +| `id` | `string` | Correlation ID from the originating reminder or timer request. | +| `cancel` | `bool` | When `true`, instructs Dapr to cancel the reminder or timer after this firing. | + +#### SubscribeActorEventsRequestDeactivateResponseAlpha1 + +| Field | Type | Description | +|-------|------|-------------| +| `id` | `string` | Correlation ID from the originating `deactivate_request`. | + +#### SubscribeActorEventsRequestFailedAlpha1 + +| Field | Type | Description | +|-------|------|-------------| +| `id` | `string` | Correlation ID from the originating request. | +| `code` | `uint32` | gRPC status code. `codes.NotFound` (5) signals a permanent, non-retryable failure. | +| `message` | `string` | Human-readable error description. | + +### Dapr → App: SubscribeActorEventsResponseAlpha1 + +Messages sent **from daprd to the app**. The first message is `initial_response`; all subsequent messages are callback requests. + +| Field (oneof `response_type`) | Description | +|---|---| +| `initial_response` (`SubscribeActorEventsResponseInitialAlpha1`) | Empty ack confirming successful registration. Errors surface as a gRPC stream error. | +| `invoke_request` (`SubscribeActorEventsResponseInvokeRequestAlpha1`) | Actor method invocation. | +| `reminder_request` (`SubscribeActorEventsResponseReminderRequestAlpha1`) | Actor reminder fired. | +| `timer_request` (`SubscribeActorEventsResponseTimerRequestAlpha1`) | Actor timer fired. | +| `deactivate_request` (`SubscribeActorEventsResponseDeactivateRequestAlpha1`) | Actor instance being deactivated. | + +#### SubscribeActorEventsResponseInvokeRequestAlpha1 + +| Field | Type | Description | +|-------|------|-------------| +| `id` | `string` | Unique correlation ID. Echo on `invoke_response`. | +| `actor_type` | `string` | Actor type. | +| `actor_id` | `string` | Actor ID. | +| `method` | `string` | Method name to invoke. | +| `data` | `bytes` | Request payload. | +| `metadata` | `map` | Request-level headers including `content-type` and `Dapr-Reentrancy-Id` (when reentrancy is enabled). | + +#### SubscribeActorEventsResponseReminderRequestAlpha1 + +| Field | Type | Description | +|-------|------|-------------| +| `id` | `string` | Unique correlation ID. Echo on `reminder_response`. | +| `actor_type` | `string` | Actor type. | +| `actor_id` | `string` | Actor ID. | +| `name` | `string` | Reminder name. | +| `due_time` | `string` | Reminder due time (time.ParseDuration format). | +| `period` | `string` | Reminder period (time.ParseDuration format). | +| `data` | `google.protobuf.Any` | Reminder data payload. | + +#### SubscribeActorEventsResponseTimerRequestAlpha1 + +| Field | Type | Description | +|-------|------|-------------| +| `id` | `string` | Unique correlation ID. Echo on `timer_response`. | +| `actor_type` | `string` | Actor type. | +| `actor_id` | `string` | Actor ID. | +| `name` | `string` | Timer name. | +| `due_time` | `string` | Timer due time. | +| `period` | `string` | Timer period. | +| `callback` | `string` | Callback method name registered with the timer. | +| `data` | `google.protobuf.Any` | Timer data payload. | + +#### SubscribeActorEventsResponseDeactivateRequestAlpha1 + +| Field | Type | Description | +|-------|------|-------------| +| `id` | `string` | Unique correlation ID. Echo on `deactivate_response`. | +| `actor_type` | `string` | Actor type. | +| `actor_id` | `string` | Actor ID. | + ## Activating an Actor Conceptually, activating an actor means creating the actor's object and adding the actor to a tracking table. [Review an example from the .NET SDK](https://github.com/dapr/dotnet-sdk/blob/6c271262231c41b21f3ca866eb0d55f7ce8b7dbc/src/Dapr.Actors/Runtime/ActorManager.cs#L199).