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..bb8f1a1bc5b --- /dev/null +++ b/daprdocs/content/en/developing-applications/building-blocks/actors/actors-app-initiated-streams.md @@ -0,0 +1,113 @@ +--- +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. + +## Why app-initiated streams? + +| | 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 | SDKs adding support (see below) | +| **Stability** | Stable | Alpha (v1.18+) | + +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. + +## 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 + +SDK helpers for `SubscribeActorEventsAlpha1` are not yet available; use the generated gRPC client directly. See the [how-to guide]({{% ref "howto-actors-app-initiated-streams" %}}) for a raw gRPC example in Go. SDK support is being tracked in the v1.18 SDK releases. + +## 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..cb2b367bb7f --- /dev/null +++ b/daprdocs/content/en/developing-applications/building-blocks/actors/howto-actors-app-initiated-streams.md @@ -0,0 +1,316 @@ +--- +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. + +## 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 + +``` +App daprd (Dapr sidecar) + | | + |-- SubscribeActorEventsAlpha1() ---------->| open stream + | | + |-- SubscribeActorEventsRequestInitialAlpha1 -->| register actor types + config + |<-- SubscribeActorEventsResponseInitialAlpha1 --| ack (empty = success) + | | + |<-- invoke_request (id="abc", method="x") --| callback + |-- invoke_response (id="abc", data=...) -->| response + | | + |<-- reminder_request (id="def") ----------| callback + |-- reminder_response (id="def") -------->| ack + | | + |<-- deactivate_request (id="ghi") --------| callback + |-- deactivate_response (id="ghi") ------->| ack +``` + +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 aa09466d501..9a925ddfb14 100644 --- a/daprdocs/content/en/operations/support/alpha-beta-apis.md +++ b/daprdocs/content/en/operations/support/alpha-beta-apis.md @@ -16,6 +16,7 @@ description: "List of current alpha and beta APIs" | Jobs | [Jobs proto](https://github.com/dapr/dapr/blob/master/dapr/proto/runtime/v1/dapr.proto#L212-219) | `v1.0-alpha1/jobs` | The jobs API enables you to schedule and orchestrate jobs. | [Jobs API]({{% ref "jobs-overview.md" %}}) | v1.14 | | 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/86440a8a624c8ceb6d8cf882682bcd34f6025772/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).