From f885636a7fdca67d771efc2beb660bf8afdd012f Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Tue, 28 Jul 2026 10:14:08 -0700 Subject: [PATCH 1/7] API surface area of SANO worker callbacks --- openapi/openapiv2.json | 159 ++++++++++++++++-- openapi/openapiv3.yaml | 85 ++++++++++ temporal/api/callback/v1/message.proto | 16 +- temporal/api/common/v1/message.proto | 41 +++++ temporal/api/enums/v1/common.proto | 2 +- temporal/api/nexusoperation/v1/message.proto | 42 +++++ .../v1/request_response.proto | 40 +++++ .../workflowservice/v1/request_response.proto | 12 ++ 8 files changed, 379 insertions(+), 18 deletions(-) create mode 100644 temporal/api/nexusoperation/v1/message.proto create mode 100644 temporal/api/notificationservice/v1/request_response.proto diff --git a/openapi/openapiv2.json b/openapi/openapiv2.json index 9a475d2d2..4e379041f 100644 --- a/openapi/openapiv2.json +++ b/openapi/openapiv2.json @@ -10815,6 +10815,10 @@ "type": "object", "description": "Trigger for when the activity is closed." }, + "CallbackInfoOperationCompleted": { + "type": "object", + "description": "Trigger for when the Nexus operation is completed, covering both success cases as\nwell as any type of failure." + }, "CallbackInfoUpdateWorkflowExecutionCompleted": { "type": "object", "properties": { @@ -10932,20 +10936,6 @@ }, "description": "Target an external server by URL.\nAt a later point, this will support providing credentials, in the meantime, an http.RoundTripper can be injected\ninto the server to modify the request." }, - "EndpointTargetWorker": { - "type": "object", - "properties": { - "namespace": { - "type": "string", - "description": "Namespace to route requests to." - }, - "taskQueue": { - "type": "string", - "description": "Nexus task queue to route requests to." - } - }, - "description": "Target a worker polling on a Nexus task queue in a specific namespace." - }, "EnvironmentInfoArchitecture": { "type": "string", "enum": [ @@ -11117,6 +11107,21 @@ }, "description": "A link to a standalone Nexus operation." }, + "LinkNexusOperationCallback": { + "type": "object", + "properties": { + "operationId": { + "type": "string" + }, + "runId": { + "type": "string" + }, + "requestId": { + "type": "string" + } + }, + "description": "A link to a worker callback attached to a Nexus operation within the same namespace. e.g. the completion handler attached\nto a standalone Nexus operation, not the source Nexus operation itself. A standalone Nexus operation can have multiple\ncallbacks attached to them, and will be differentiated by the server-generated request_id used when the callback was invoked." + }, "LinkWorkflow": { "type": "object", "properties": { @@ -12725,6 +12730,10 @@ "$ref": "#/definitions/v1NexusOperationIdConflictPolicy", "description": "Defines how to resolve an operation id conflict with a *running* operation.\nThe default policy is NEXUS_OPERATION_ID_CONFLICT_POLICY_FAIL." }, + "onConflictOptions": { + "$ref": "#/definitions/apiNexusoperationV1OnConflictOptions", + "description": "Defines actions to be done to the existing running standalone Nexus when the conflict policy\nNEXUS_OPERATION_ID_CONFLICT_POLICY_USE_EXISTING is used. If not set or set to a empty object\n(all options with default value), it won't do modify running operation." + }, "searchAttributes": { "$ref": "#/definitions/v1SearchAttributes", "description": "Search attributes for indexing." @@ -12739,6 +12748,14 @@ "userMetadata": { "$ref": "#/definitions/v1UserMetadata", "description": "Metadata for use by user interfaces to display the fixed as-of-start summary and details of the operation." + }, + "completionCallbacks": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1Callback" + }, + "description": "Completion callbacks to be invoked once the Nexus operation reaches a terminal state." } } }, @@ -13497,6 +13514,10 @@ "blockedReason": { "type": "string", "description": "If the state is BLOCKED, blocked reason provides additional information." + }, + "outcome": { + "$ref": "#/definitions/v1CallbackOutcome", + "description": "Result of the callback's execution, only set when the callback reaches a terminal state." } }, "description": "Common callback information. Specific CallbackInfo messages should embed this and may include additional fields." @@ -13519,6 +13540,46 @@ }, "description": "When starting an execution with a conflict policy that uses an existing execution and there is already an existing\nrunning execution, OnConflictOptions defines actions to be taken on the existing running execution." }, + "apiNexusoperationV1CallbackInfo": { + "type": "object", + "properties": { + "trigger": { + "$ref": "#/definitions/apiNexusoperationV1CallbackInfoTrigger", + "description": "Trigger for this callback." + }, + "info": { + "$ref": "#/definitions/apiCallbackV1CallbackInfo", + "description": "Common callback info." + } + }, + "description": "CallbackInfo contains the state of a callback attached to a standalone Nexus operation." + }, + "apiNexusoperationV1CallbackInfoTrigger": { + "type": "object", + "properties": { + "operationCompleted": { + "$ref": "#/definitions/CallbackInfoOperationCompleted" + } + } + }, + "apiNexusoperationV1OnConflictOptions": { + "type": "object", + "properties": { + "attachRequestId": { + "type": "boolean", + "description": "Attaches the request ID to the running operation." + }, + "attachCompletionCallbacks": { + "type": "boolean", + "description": "Attaches the completion callbacks to the running operation." + }, + "attachLinks": { + "type": "boolean", + "description": "Attaches any new links to the running operation." + } + }, + "description": "When StartNexusOperationExecutionRequest uses the conflict policy NEXUS_OPERATION_ID_CONFLICT_POLICY_USE_EXISTING\nand there is already an existing, running standalone Nexus operation, OnConflictOptions defines actions to be\ntaken." + }, "apiWorkflowV1CallbackInfo": { "type": "object", "properties": { @@ -14695,6 +14756,9 @@ "internal": { "$ref": "#/definitions/CallbackInternal" }, + "worker": { + "$ref": "#/definitions/v1CallbackWorker" + }, "links": { "type": "array", "items": { @@ -14706,6 +14770,20 @@ }, "description": "Callback to attach to various events in the system, e.g. workflow run completion." }, + "v1CallbackOutcome": { + "type": "object", + "properties": { + "success": { + "type": "object", + "properties": {}, + "title": "The callback completed successfully. (Which may include delivering a \"failed\" result successfully.)" + }, + "failure": { + "$ref": "#/definitions/v1Failure", + "description": "The failure if the callback was not able to complete successfully. e.g. timed out, received an\nunretriable error, etc." + } + } + }, "v1CallbackState": { "type": "string", "enum": [ @@ -14718,7 +14796,31 @@ "CALLBACK_STATE_BLOCKED" ], "default": "CALLBACK_STATE_UNSPECIFIED", - "description": "State of a callback.\n\n - CALLBACK_STATE_UNSPECIFIED: Default value, unspecified state.\n - CALLBACK_STATE_STANDBY: Callback is standing by, waiting to be triggered.\n - CALLBACK_STATE_SCHEDULED: Callback is in the queue waiting to be executed or is currently executing.\n - CALLBACK_STATE_BACKING_OFF: Callback has failed with a retryable error and is backing off before the next attempt.\n - CALLBACK_STATE_FAILED: Callback has failed.\n - CALLBACK_STATE_SUCCEEDED: Callback has succeeded.\n - CALLBACK_STATE_BLOCKED: Callback is blocked (eg: by circuit breaker)." + "description": "State of a callback.\n\n - CALLBACK_STATE_UNSPECIFIED: Default value, unspecified state.\n - CALLBACK_STATE_STANDBY: Callback is standing by, waiting to be triggered.\n - CALLBACK_STATE_SCHEDULED: Callback is in the queue waiting to be executed or is currently executing.\n - CALLBACK_STATE_BACKING_OFF: Callback has failed with a retryable error and is backing off before the next attempt.\n - CALLBACK_STATE_FAILED: Callback has failed.\n - CALLBACK_STATE_SUCCEEDED: Callback has succeeded.\n - CALLBACK_STATE_BLOCKED: Callback is blocked, e.g. by circuit breaker." + }, + "v1CallbackWorker": { + "type": "object", + "properties": { + "taskQueueName": { + "type": "string", + "description": "Nexus task queue the Temporal worker is listening on.\n\nNOTE: This is not a temporal.api.taskqueue.v1.TaskQueue to avoid a circular dependency." + }, + "service": { + "type": "string", + "description": "Target Nexus service, e.g. \"HTTPAdapter\"." + }, + "operation": { + "type": "string", + "description": "Target operation, e.g. \"DeliverAsWebhook\"." + }, + "sourceContext": { + "$ref": "#/definitions/v1Payload", + "description": "There is a relatively small maximum size the source context can be, e.g. 32KiB.", + "title": "Arbitrary user-supplied data from the source operation's callsite. (As applicable, not all operations\nsupport attaching context data.)" + } + }, + "description": "The targeted Nexus service must be registered within the same namespace as the source operation\nthe callback is attached to. (While Nexus allows for cross-namespace operations, worker callbacks\nare purely \"caller-side\".)\n\nWorker callbacks are only supported for certain types of operations, e.g. standalone Nexus operations.\nAttempting to attach a Worker callback for an unsupported operation will result in an INVALID_ARGUMENT\nerror from the server.", + "title": "Worker callbacks are requests to invoke a specific shape of Nexus operation on a Temporal worker.\nThe specified Nexus operation must have the following:\n- Input: temporal.api.notificationservice.v1.OnCompleteRequest\n- Output: temporal.api.notificationservice.v1.OnCompleteResponse" }, "v1CancelExternalWorkflowExecutionFailedCause": { "type": "string", @@ -15643,6 +15745,14 @@ "type": "string", "format": "byte", "description": "Token for follow-on long-poll requests. Absent only if the operation is complete." + }, + "completionCallbacks": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/apiNexusoperationV1CallbackInfo" + }, + "description": "Completion callbacks to be invoked once the Nexus operation reaches a terminal state.\nThey will remain in the CALLBACK_STATE_STANDBY state until the Nexus operation is finished." } } }, @@ -15880,7 +15990,7 @@ "type": "object", "properties": { "worker": { - "$ref": "#/definitions/EndpointTargetWorker" + "$ref": "#/definitions/v1EndpointTargetWorker" }, "external": { "$ref": "#/definitions/EndpointTargetExternal" @@ -15888,6 +15998,20 @@ }, "description": "Target to route requests to." }, + "v1EndpointTargetWorker": { + "type": "object", + "properties": { + "namespace": { + "type": "string", + "description": "Namespace to route requests to." + }, + "taskQueue": { + "type": "string", + "description": "Nexus task queue to route requests to." + } + }, + "description": "Target a worker polling on a Nexus task queue in a specific namespace." + }, "v1EnvironmentInfo": { "type": "object", "properties": { @@ -16765,6 +16889,9 @@ }, "workflow": { "$ref": "#/definitions/LinkWorkflow" + }, + "nexusOperationCallback": { + "$ref": "#/definitions/LinkNexusOperationCallback" } }, "description": "Link can be associated with history events. It might contain information about an external entity\nrelated to the history event. For example, workflow A makes a Nexus call that starts workflow B:\nin this case, a history event in workflow A could contain a Link to the workflow started event in\nworkflow B, and vice-versa." diff --git a/openapi/openapiv3.yaml b/openapi/openapiv3.yaml index 1cba692a1..78c3ef72d 100644 --- a/openapi/openapiv3.yaml +++ b/openapi/openapiv3.yaml @@ -10826,6 +10826,8 @@ components: $ref: '#/components/schemas/Callback_Nexus' internal: $ref: '#/components/schemas/Callback_Internal' + worker: + $ref: '#/components/schemas/Callback_Worker' links: type: array items: @@ -10878,7 +10880,20 @@ components: blockedReason: type: string description: If the state is BLOCKED, blocked reason provides additional information. + outcome: + allOf: + - $ref: '#/components/schemas/CallbackOutcome' + description: Result of the callback's execution, only set when the callback reaches a terminal state. description: Common callback information. Specific CallbackInfo messages should embed this and may include additional fields. + CallbackOutcome: + type: object + properties: + failure: + allOf: + - $ref: '#/components/schemas/Failure' + description: |- + The failure if the callback was not able to complete successfully. e.g. timed out, received an + unretriable error, etc. Callback_Internal: type: object properties: @@ -10902,6 +10917,42 @@ components: additionalProperties: type: string description: Header to attach to callback request. + Callback_Worker: + type: object + properties: + taskQueueName: + type: string + description: |- + Nexus task queue the Temporal worker is listening on. + + NOTE: This is not a temporal.api.taskqueue.v1.TaskQueue to avoid a circular dependency. + service: + type: string + description: Target Nexus service, e.g. "HTTPAdapter". + operation: + type: string + description: Target operation, e.g. "DeliverAsWebhook". + sourceContext: + allOf: + - $ref: '#/components/schemas/Payload' + description: |- + Arbitrary user-supplied data from the source operation's callsite. (As applicable, not all operations + support attaching context data.) + + There is a relatively small maximum size the source context can be, e.g. 32KiB. + description: |- + Worker callbacks are requests to invoke a specific shape of Nexus operation on a Temporal worker. + The specified Nexus operation must have the following: + - Input: temporal.api.notificationservice.v1.OnCompleteRequest + - Output: temporal.api.notificationservice.v1.OnCompleteResponse + + The targeted Nexus service must be registered within the same namespace as the source operation + the callback is attached to. (While Nexus allows for cross-namespace operations, worker callbacks + are purely "caller-side".) + + Worker callbacks are only supported for certain types of operations, e.g. standalone Nexus operations. + Attempting to attach a Worker callback for an unsupported operation will result in an INVALID_ARGUMENT + error from the server. CanceledFailureInfo: type: object properties: @@ -11886,6 +11937,13 @@ components: type: string description: Token for follow-on long-poll requests. Absent only if the operation is complete. format: bytes + completionCallbacks: + type: array + items: + $ref: '#/components/schemas/CallbackInfo' + description: |- + Completion callbacks to be invoked once the Nexus operation reaches a terminal state. + They will remain in the CALLBACK_STATE_STANDBY state until the Nexus operation is finished. DescribeScheduleResponse: type: object properties: @@ -13070,6 +13128,8 @@ components: $ref: '#/components/schemas/Link_NexusOperation' workflow: $ref: '#/components/schemas/Link_Workflow' + nexusOperationCallback: + $ref: '#/components/schemas/Link_NexusOperationCallback' description: |- Link can be associated with history events. It might contain information about an external entity related to the history event. For example, workflow A makes a Nexus call that starts workflow B: @@ -13104,6 +13164,19 @@ components: runId: type: string description: A link to a standalone Nexus operation. + Link_NexusOperationCallback: + type: object + properties: + operationId: + type: string + runId: + type: string + requestId: + type: string + description: |- + A link to a worker callback attached to a Nexus operation within the same namespace. e.g. the completion handler attached + to a standalone Nexus operation, not the source Nexus operation itself. A standalone Nexus operation can have multiple + callbacks attached to them, and will be differentiated by the server-generated request_id used when the callback was invoked. Link_Workflow: type: object properties: @@ -17330,6 +17403,13 @@ components: Defines how to resolve an operation id conflict with a *running* operation. The default policy is NEXUS_OPERATION_ID_CONFLICT_POLICY_FAIL. format: enum + onConflictOptions: + allOf: + - $ref: '#/components/schemas/OnConflictOptions' + description: |- + Defines actions to be done to the existing running standalone Nexus when the conflict policy + NEXUS_OPERATION_ID_CONFLICT_POLICY_USE_EXISTING is used. If not set or set to a empty object + (all options with default value), it won't do modify running operation. searchAttributes: allOf: - $ref: '#/components/schemas/SearchAttributes' @@ -17349,6 +17429,11 @@ components: allOf: - $ref: '#/components/schemas/UserMetadata' description: Metadata for use by user interfaces to display the fixed as-of-start summary and details of the operation. + completionCallbacks: + type: array + items: + $ref: '#/components/schemas/Callback' + description: Completion callbacks to be invoked once the Nexus operation reaches a terminal state. StartNexusOperationExecutionResponse: type: object properties: diff --git a/temporal/api/callback/v1/message.proto b/temporal/api/callback/v1/message.proto index f881a4eef..2c678745b 100644 --- a/temporal/api/callback/v1/message.proto +++ b/temporal/api/callback/v1/message.proto @@ -9,6 +9,7 @@ option java_outer_classname = "MessageProto"; option ruby_package = "Temporalio::Api::Callback::V1"; option csharp_namespace = "Temporalio.Api.Callback.V1"; +import "google/protobuf/empty.proto"; import "google/protobuf/timestamp.proto"; import "temporal/api/common/v1/message.proto"; @@ -34,4 +35,17 @@ message CallbackInfo { google.protobuf.Timestamp next_attempt_schedule_time = 7; // If the state is BLOCKED, blocked reason provides additional information. string blocked_reason = 8; -} \ No newline at end of file + + // Result of the callback's execution, only set when the callback reaches a terminal state. + CallbackOutcome outcome = 9; +} + +message CallbackOutcome { + oneof value { + // The callback completed successfully. (Which may include delivering a "failed" result successfully.) + google.protobuf.Empty success = 1; + // The failure if the callback was not able to complete successfully. e.g. timed out, received an + // unretriable error, etc. + temporal.api.failure.v1.Failure failure = 2; + } +} diff --git a/temporal/api/common/v1/message.proto b/temporal/api/common/v1/message.proto index 3e2fc0e1c..df6ec72d7 100644 --- a/temporal/api/common/v1/message.proto +++ b/temporal/api/common/v1/message.proto @@ -201,10 +201,41 @@ message Callback { bytes data = 1; } + // Worker callbacks are requests to invoke a specific shape of Nexus operation on a Temporal worker. + // The specified Nexus operation must have the following: + // - Input: temporal.api.notificationservice.v1.OnCompleteRequest + // - Output: temporal.api.notificationservice.v1.OnCompleteResponse + // + // The targeted Nexus service must be registered within the same namespace as the source operation + // the callback is attached to. (While Nexus allows for cross-namespace operations, worker callbacks + // are purely "caller-side".) + // + // Worker callbacks are only supported for certain types of operations, e.g. standalone Nexus operations. + // Attempting to attach a Worker callback for an unsupported operation will result in an INVALID_ARGUMENT + // error from the server. + message Worker { + // Nexus task queue the Temporal worker is listening on. + // + // NOTE: This is not a temporal.api.taskqueue.v1.TaskQueue to avoid a circular dependency. + string task_queue_name = 1; + + // Target Nexus service, e.g. "HTTPAdapter". + string service = 2; + // Target operation, e.g. "DeliverAsWebhook". + string operation = 3; + + // Arbitrary user-supplied data from the source operation's callsite. (As applicable, not all operations + // support attaching context data.) + // + // There is a relatively small maximum size the source context can be, e.g. 32KiB. + temporal.api.common.v1.Payload source_context = 4; + } + reserved 1; // For a generic callback mechanism to be added later. oneof variant { Nexus nexus = 2; Internal internal = 3; + Worker worker = 4; } // Links associated with the callback. It can be used to link to underlying resources of the @@ -273,12 +304,22 @@ message Link { string reason = 4; } + // A link to a worker callback attached to a Nexus operation within the same namespace. e.g. the completion handler attached + // to a standalone Nexus operation, not the source Nexus operation itself. A standalone Nexus operation can have multiple + // callbacks attached to them, and will be differentiated by the server-generated request_id used when the callback was invoked. + message NexusOperationCallback { + string operation_id = 1; + string run_id = 2; + string request_id = 3; + } + oneof variant { WorkflowEvent workflow_event = 1; BatchJob batch_job = 2; Activity activity = 3; NexusOperation nexus_operation = 4; Workflow workflow = 5; + NexusOperationCallback nexus_operation_callback = 6; } } diff --git a/temporal/api/enums/v1/common.proto b/temporal/api/enums/v1/common.proto index cdc387173..e2929337e 100644 --- a/temporal/api/enums/v1/common.proto +++ b/temporal/api/enums/v1/common.proto @@ -47,7 +47,7 @@ enum CallbackState { CALLBACK_STATE_FAILED = 4; // Callback has succeeded. CALLBACK_STATE_SUCCEEDED = 5; - // Callback is blocked (eg: by circuit breaker). + // Callback is blocked, e.g. by circuit breaker. CALLBACK_STATE_BLOCKED = 6; } diff --git a/temporal/api/nexusoperation/v1/message.proto b/temporal/api/nexusoperation/v1/message.proto new file mode 100644 index 000000000..f247013b0 --- /dev/null +++ b/temporal/api/nexusoperation/v1/message.proto @@ -0,0 +1,42 @@ +syntax = "proto3"; + +package temporal.api.nexusoperation.v1; + +option go_package = "go.temporal.io/api/nexusoperation/v1;nexusoperation"; +option java_package = "io.temporal.api.nexusoperation.v1"; +option java_multiple_files = true; +option java_outer_classname = "MessageProto"; +option ruby_package = "Temporalio::Api::NexusOperation::V1"; +option csharp_namespace = "Temporalio.Api.NexusOperation.V1"; + +import "temporal/api/callback/v1/message.proto"; + +// CallbackInfo contains the state of a callback attached to a standalone Nexus operation. +message CallbackInfo { + // Trigger for when the Nexus operation is completed, covering both success cases as + // well as any type of failure. + message OperationCompleted {} + + message Trigger { + oneof variant { + OperationCompleted operation_completed = 1; + } + } + + // Trigger for this callback. + Trigger trigger = 1; + // Common callback info. + temporal.api.callback.v1.CallbackInfo info = 2; +} + +// When StartNexusOperationExecutionRequest uses the conflict policy NEXUS_OPERATION_ID_CONFLICT_POLICY_USE_EXISTING +// and there is already an existing, running standalone Nexus operation, OnConflictOptions defines actions to be +// taken. +message OnConflictOptions { + // Attaches the request ID to the running operation. + bool attach_request_id = 1; + // Attaches the completion callbacks to the running operation. + bool attach_completion_callbacks = 2; + // Attaches any new links to the running operation. + bool attach_links = 3; +} diff --git a/temporal/api/notificationservice/v1/request_response.proto b/temporal/api/notificationservice/v1/request_response.proto new file mode 100644 index 000000000..edf861c77 --- /dev/null +++ b/temporal/api/notificationservice/v1/request_response.proto @@ -0,0 +1,40 @@ +syntax = "proto3"; + +package temporal.api.notificationservice.v1; + +option go_package = "go.temporal.io/api/notificationservice/v1;notificationservice"; +option java_package = "io.temporal.api.notificationservice.v1"; +option java_multiple_files = true; +option java_outer_classname = "RequestResponseProto"; +option ruby_package = "Temporalio::Api::NotificationService::V1"; +option csharp_namespace = "Temporalio.Api.NotificationService.V1"; + +import "temporal/api/common/v1/message.proto"; +import "temporal/api/failure/v1/message.proto"; + +// OnCompleteRequest is the request type to the NotificationService's OnComplete operation, +// allowing for defining completion handlers for arbitrary asynchronous operations. +// +// Information about the source operation will be available in the form of a commonpb.Link, +// which will be available separately from this OnCompleteRequest. e.g. a link to the source +// standalone Nexus operation would be found in the nexuspb.StartOperationRequest parameter +// sent to the worker callback. (In addition to this OnCompleteRequest.) +message OnCompleteRequest { + + // The outcome of the source operation. + message Outcome { + oneof result { + // The operation was successful, and resulted in the given payload(s). + temporal.api.common.v1.Payloads success = 1; + // The operation failed. Includes timeout, cancellation, and application errors. + temporal.api.failure.v1.Failure failure = 2; + } + } + Outcome outcome = 1; + + // User-supplied data which was added to the source invocation. (As applicable.) + temporal.api.common.v1.Payload source_context = 2; +} + +// OnCompleteResponse is the return type of the OnComplete operation. +message OnCompleteResponse {} diff --git a/temporal/api/workflowservice/v1/request_response.proto b/temporal/api/workflowservice/v1/request_response.proto index de1c271cd..2c9215ab1 100644 --- a/temporal/api/workflowservice/v1/request_response.proto +++ b/temporal/api/workflowservice/v1/request_response.proto @@ -45,6 +45,7 @@ import "temporal/api/batch/v1/message.proto"; import "temporal/api/sdk/v1/task_complete_metadata.proto"; import "temporal/api/sdk/v1/user_metadata.proto"; import "temporal/api/nexus/v1/message.proto"; +import "temporal/api/nexusoperation/v1/message.proto"; import "temporal/api/worker/v1/message.proto"; import "google/protobuf/duration.proto"; @@ -3404,6 +3405,10 @@ message StartNexusOperationExecutionRequest { // Defines how to resolve an operation id conflict with a *running* operation. // The default policy is NEXUS_OPERATION_ID_CONFLICT_POLICY_FAIL. temporal.api.enums.v1.NexusOperationIdConflictPolicy id_conflict_policy = 13; + // Defines actions to be done to the existing running standalone Nexus when the conflict policy + // NEXUS_OPERATION_ID_CONFLICT_POLICY_USE_EXISTING is used. If not set or set to a empty object + // (all options with default value), it won't do modify running operation. + temporal.api.nexusoperation.v1.OnConflictOptions on_conflict_options = 17; // Search attributes for indexing. temporal.api.common.v1.SearchAttributes search_attributes = 14; @@ -3416,6 +3421,9 @@ message StartNexusOperationExecutionRequest { map nexus_header = 15; // Metadata for use by user interfaces to display the fixed as-of-start summary and details of the operation. temporal.api.sdk.v1.UserMetadata user_metadata = 16; + + // Completion callbacks to be invoked once the Nexus operation reaches a terminal state. + repeated temporal.api.common.v1.Callback completion_callbacks = 18; } message StartNexusOperationExecutionResponse { @@ -3464,6 +3472,10 @@ message DescribeNexusOperationExecutionResponse { // Token for follow-on long-poll requests. Absent only if the operation is complete. bytes long_poll_token = 6; + + // Completion callbacks to be invoked once the Nexus operation reaches a terminal state. + // They will remain in the CALLBACK_STATE_STANDBY state until the Nexus operation is finished. + repeated temporal.api.nexusoperation.v1.CallbackInfo completion_callbacks = 7; } message PollNexusOperationExecutionRequest { From c37e1689e897a3b592dfb1a503f84ff465963347 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Fri, 7 Aug 2026 14:55:15 -0700 Subject: [PATCH 2/7] Remove 'Outcome' types, moving fields to parent protos (#849) **What changed?** **Why?** There are two changes in this PR, **both breaking changes**. (But since the feature is under development, this won't impact users in any way.) (1) Remove the `callbackpb.CallbackOutcome` proto, and instead move the `success`/`failure` fields directly into `callbackpb.CallbackInfo`. Do the same for `OnCompleteRequest`, removing the `OnCompleteRequest.Outcome` message. It's extremely unlikely we would ever add a new variant for the callback's result, and having the extra layer of indirection made the SDK and server-side code cumbersome. (2) Have `OnCompleteRequest.result` variant `success` be a `commonpb.Payload` instead of a `commonpb.Payloads`. The Temporal API makes it look as if a Workflow can return multiple values. (i.e. a `commonpb.Payloads`.) But in reality, the SDK only ever deals with a single value (`commonpb.Payload`). And the extra layer of indirection via `commonpb.Payloads` also made the SDK and server-side code more cumbersome. **Breaking changes** Yes. But this is still a WIP feature branch. The only person being broken is, alas, @chrsmith . --- openapi/openapiv2.json | 25 ++++++------------- openapi/openapiv3.yaml | 9 +------ temporal/api/callback/v1/message.proto | 10 +++----- .../v1/request_response.proto | 17 ++++++------- 4 files changed, 19 insertions(+), 42 deletions(-) diff --git a/openapi/openapiv2.json b/openapi/openapiv2.json index 4e379041f..6ab6d7311 100644 --- a/openapi/openapiv2.json +++ b/openapi/openapiv2.json @@ -13515,9 +13515,14 @@ "type": "string", "description": "If the state is BLOCKED, blocked reason provides additional information." }, - "outcome": { - "$ref": "#/definitions/v1CallbackOutcome", - "description": "Result of the callback's execution, only set when the callback reaches a terminal state." + "success": { + "type": "object", + "properties": {}, + "title": "The callback completed successfully. (Which may include delivering a \"failed\" result successfully.)" + }, + "failure": { + "$ref": "#/definitions/v1Failure", + "description": "The failure if the callback was not able to complete successfully. e.g. timed out, received an\nunretriable error, etc." } }, "description": "Common callback information. Specific CallbackInfo messages should embed this and may include additional fields." @@ -14770,20 +14775,6 @@ }, "description": "Callback to attach to various events in the system, e.g. workflow run completion." }, - "v1CallbackOutcome": { - "type": "object", - "properties": { - "success": { - "type": "object", - "properties": {}, - "title": "The callback completed successfully. (Which may include delivering a \"failed\" result successfully.)" - }, - "failure": { - "$ref": "#/definitions/v1Failure", - "description": "The failure if the callback was not able to complete successfully. e.g. timed out, received an\nunretriable error, etc." - } - } - }, "v1CallbackState": { "type": "string", "enum": [ diff --git a/openapi/openapiv3.yaml b/openapi/openapiv3.yaml index 78c3ef72d..8951cfeff 100644 --- a/openapi/openapiv3.yaml +++ b/openapi/openapiv3.yaml @@ -10880,20 +10880,13 @@ components: blockedReason: type: string description: If the state is BLOCKED, blocked reason provides additional information. - outcome: - allOf: - - $ref: '#/components/schemas/CallbackOutcome' - description: Result of the callback's execution, only set when the callback reaches a terminal state. - description: Common callback information. Specific CallbackInfo messages should embed this and may include additional fields. - CallbackOutcome: - type: object - properties: failure: allOf: - $ref: '#/components/schemas/Failure' description: |- The failure if the callback was not able to complete successfully. e.g. timed out, received an unretriable error, etc. + description: Common callback information. Specific CallbackInfo messages should embed this and may include additional fields. Callback_Internal: type: object properties: diff --git a/temporal/api/callback/v1/message.proto b/temporal/api/callback/v1/message.proto index 2c678745b..e72ff6fdb 100644 --- a/temporal/api/callback/v1/message.proto +++ b/temporal/api/callback/v1/message.proto @@ -37,15 +37,11 @@ message CallbackInfo { string blocked_reason = 8; // Result of the callback's execution, only set when the callback reaches a terminal state. - CallbackOutcome outcome = 9; -} - -message CallbackOutcome { - oneof value { + oneof result { // The callback completed successfully. (Which may include delivering a "failed" result successfully.) - google.protobuf.Empty success = 1; + google.protobuf.Empty success = 9; // The failure if the callback was not able to complete successfully. e.g. timed out, received an // unretriable error, etc. - temporal.api.failure.v1.Failure failure = 2; + temporal.api.failure.v1.Failure failure = 10; } } diff --git a/temporal/api/notificationservice/v1/request_response.proto b/temporal/api/notificationservice/v1/request_response.proto index edf861c77..9cd1f4863 100644 --- a/temporal/api/notificationservice/v1/request_response.proto +++ b/temporal/api/notificationservice/v1/request_response.proto @@ -21,19 +21,16 @@ import "temporal/api/failure/v1/message.proto"; // sent to the worker callback. (In addition to this OnCompleteRequest.) message OnCompleteRequest { - // The outcome of the source operation. - message Outcome { - oneof result { - // The operation was successful, and resulted in the given payload(s). - temporal.api.common.v1.Payloads success = 1; - // The operation failed. Includes timeout, cancellation, and application errors. - temporal.api.failure.v1.Failure failure = 2; - } + // The result of the source operation. + oneof result { + // The operation was successful, and resulted in the given payload. + temporal.api.common.v1.Payload success = 1; + // The operation failed. Includes timeout, cancellation, and application errors. + temporal.api.failure.v1.Failure failure = 2; } - Outcome outcome = 1; // User-supplied data which was added to the source invocation. (As applicable.) - temporal.api.common.v1.Payload source_context = 2; + temporal.api.common.v1.Payload source_context = 3; } // OnCompleteResponse is the return type of the OnComplete operation. From f488ce379f2c948d9288fbc9a2eda29b32aa4b8c Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Fri, 14 Aug 2026 09:40:00 -0700 Subject: [PATCH 3/7] Add links to StartNexusOperationExecutionRequest --- openapi/openapiv2.json | 8 ++++++++ openapi/openapiv3.yaml | 7 +++++++ temporal/api/workflowservice/v1/request_response.proto | 3 +++ 3 files changed, 18 insertions(+) diff --git a/openapi/openapiv2.json b/openapi/openapiv2.json index 6ab6d7311..fae518ea9 100644 --- a/openapi/openapiv2.json +++ b/openapi/openapiv2.json @@ -12756,6 +12756,14 @@ "$ref": "#/definitions/v1Callback" }, "description": "Completion callbacks to be invoked once the Nexus operation reaches a terminal state." + }, + "links": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/apiCommonV1Link" + }, + "description": "Links to be associated with the Nexus operation. Callbacks may also have associated links;\nlinks already included with a callback should not be duplicated here." } } }, diff --git a/openapi/openapiv3.yaml b/openapi/openapiv3.yaml index 8951cfeff..07a73f1cb 100644 --- a/openapi/openapiv3.yaml +++ b/openapi/openapiv3.yaml @@ -17427,6 +17427,13 @@ components: items: $ref: '#/components/schemas/Callback' description: Completion callbacks to be invoked once the Nexus operation reaches a terminal state. + links: + type: array + items: + $ref: '#/components/schemas/Link' + description: |- + Links to be associated with the Nexus operation. Callbacks may also have associated links; + links already included with a callback should not be duplicated here. StartNexusOperationExecutionResponse: type: object properties: diff --git a/temporal/api/workflowservice/v1/request_response.proto b/temporal/api/workflowservice/v1/request_response.proto index 2c9215ab1..e113e0ac2 100644 --- a/temporal/api/workflowservice/v1/request_response.proto +++ b/temporal/api/workflowservice/v1/request_response.proto @@ -3424,6 +3424,9 @@ message StartNexusOperationExecutionRequest { // Completion callbacks to be invoked once the Nexus operation reaches a terminal state. repeated temporal.api.common.v1.Callback completion_callbacks = 18; + // Links to be associated with the Nexus operation. Callbacks may also have associated links; + // links already included with a callback should not be duplicated here. + repeated temporal.api.common.v1.Link links = 19; } message StartNexusOperationExecutionResponse { From 24ea1a5dba776ee268b4bb1948532ae16bf6ddcb Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Wed, 19 Aug 2026 09:50:55 -0700 Subject: [PATCH 4/7] Wire request_id through CallbackInfo for linking (#851) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ⚠️ This is to be merged into the `feature/worker-callbacks` branch, and not `main`. Only after the feature is complete will that branch be rebased and merged into `main`. --- This PR makes three changes, all so that resources spanwed from the invocation of a worker callback can be linked correctly. (1) Remove the `Link_NexusOperationCallback` variant with a more general `Link_Callback` proto Previously we were scoping the feature to only be applicable for SANO callbacks. But if we are going to support worker callbacks for any async operation, having a general link type (that uses the existing [Execution proto](https://github.com/temporalio/api/blob/0066de621239ca9ddc6c976e091e27a6bc474752/temporal/api/common/v1/message.proto#L73-L77)) will avoid needing to create additional link variants in the future. (2) Add a `callbackpb.CallbackInfo::request_id` field This type is used in the `Describe-` operations for standalone Activities and standalone Nexus operations. Without it, there would be no way to determine _which_ completion callback is being referred to. (Instead, we couldn't be any more accurate than to have the link point to "one of these N" callbacks.) (3) Add `workflowpb.CallbackInfo::{request_id, result}` The `workflowpb` namespace forked rather than embedded the `callbackpb.CallbackInfo` message. The changes here add the missing fields, so that `DescribeWorkflowExecution` can disambiguate callbacks as well. (In addition to carrying the result of those callbacks.) **Why?** With these changes, the server will be able to properly cross-link resources spawned from completion callbacks. On the Caller-side, any resources spawned from the completion callbacks would be available on the `commonpb.Callback::links` field. (*) ```graphql query GetSpawnedResourceLinks(workflowID: string { DescribeWorkflowExecution(workflowID) { completion_callbacks { callback { links } } } } ``` > (*) Only the resources _initially_ created from the worker callback invocation will be present. e.g. the Workflow that backs an asynchronous Nexus handler. It would not contain links for any subsequent resources created. On the Handler-side, a single `Link_Callback` would be supplied to the Nexus handler receiving the worker callback. (This would be in the form of a `nexuspb.Link`.) **Breaking changes** Yes, this PR contains breaking proto changes. However, in the context of a PR into a long-lived feature branch for an unshipped feature this is safe. (The protos haven't ever been persisted by a production service.) **Server PR** It isn't out yet, but will be stacked on top of this: https://github.com/temporalio/temporal/pull/11589 --- openapi/openapiv2.json | 111 +++++++++++++------------ openapi/openapiv3.yaml | 37 +++++---- temporal/api/callback/v1/message.proto | 8 +- temporal/api/common/v1/message.proto | 23 +++-- temporal/api/enums/v1/common.proto | 4 +- 5 files changed, 101 insertions(+), 82 deletions(-) diff --git a/openapi/openapiv2.json b/openapi/openapiv2.json index fae518ea9..1d5d0d671 100644 --- a/openapi/openapiv2.json +++ b/openapi/openapiv2.json @@ -11107,21 +11107,6 @@ }, "description": "A link to a standalone Nexus operation." }, - "LinkNexusOperationCallback": { - "type": "object", - "properties": { - "operationId": { - "type": "string" - }, - "runId": { - "type": "string" - }, - "requestId": { - "type": "string" - } - }, - "description": "A link to a worker callback attached to a Nexus operation within the same namespace. e.g. the completion handler attached\nto a standalone Nexus operation, not the source Nexus operation itself. A standalone Nexus operation can have multiple\ncallbacks attached to them, and will be differentiated by the server-generated request_id used when the callback was invoked." - }, "LinkWorkflow": { "type": "object", "properties": { @@ -11549,7 +11534,7 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/v1Callback" + "$ref": "#/definitions/commonV1Callback" }, "description": "Completion callbacks attached to the running workflow update." } @@ -12591,7 +12576,7 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/v1Callback" + "$ref": "#/definitions/commonV1Callback" }, "description": "Callbacks to be called by the server when this activity reaches a terminal state.\nCallback addresses must be whitelisted in the server's dynamic configuration." }, @@ -12753,7 +12738,7 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/v1Callback" + "$ref": "#/definitions/commonV1Callback" }, "description": "Completion callbacks to be invoked once the Nexus operation reaches a terminal state." }, @@ -12844,7 +12829,7 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/v1Callback" + "$ref": "#/definitions/commonV1Callback" }, "description": "Callbacks to be called by the server when this workflow reaches a terminal state.\nIf the workflow continues-as-new, these callbacks will be carried over to the new execution.\nCallback addresses must be whitelisted in the server's dynamic configuration." }, @@ -13388,7 +13373,7 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/v1Callback" + "$ref": "#/definitions/commonV1Callback" }, "description": "Callbacks to be called by the server when this update reaches a terminal state." }, @@ -13488,7 +13473,7 @@ "type": "object", "properties": { "callback": { - "$ref": "#/definitions/v1Callback", + "$ref": "#/definitions/commonV1Callback", "description": "Information on how this callback should be invoked (e.g. its URL and type)." }, "registrationTime": { @@ -13523,6 +13508,10 @@ "type": "string", "description": "If the state is BLOCKED, blocked reason provides additional information." }, + "requestId": { + "type": "string", + "description": "Server-generated request ID used as an idempotency token when invoking callbacks.\nIt has no relation to caller-side request_id sent in operations like StartNexusOperationExecutionRequest." + }, "success": { "type": "object", "properties": {}, @@ -13597,7 +13586,7 @@ "type": "object", "properties": { "callback": { - "$ref": "#/definitions/v1Callback", + "$ref": "#/definitions/commonV1Callback", "description": "Information on how this callback should be invoked (e.g. its URL and type)." }, "trigger": { @@ -13667,6 +13656,29 @@ }, "description": "When StartWorkflowExecution uses the conflict policy WORKFLOW_ID_CONFLICT_POLICY_USE_EXISTING and\nthere is already an existing running workflow, OnConflictOptions defines actions to be taken on\nthe existing running workflow. In this case, it will create a WorkflowExecutionOptionsUpdatedEvent\nhistory event in the running workflow with the changes requested in this object." }, + "commonV1Callback": { + "type": "object", + "properties": { + "nexus": { + "$ref": "#/definitions/CallbackNexus" + }, + "internal": { + "$ref": "#/definitions/CallbackInternal" + }, + "worker": { + "$ref": "#/definitions/v1CallbackWorker" + }, + "links": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1Link" + }, + "description": "Links associated with the callback. It can be used to link to underlying resources of the\ncallback." + } + }, + "description": "Callback to attach to various events in the system, e.g. workflow run completion." + }, "protobufAny": { "type": "object", "properties": { @@ -14760,29 +14772,6 @@ }, "description": "CalendarSpec describes an event specification relative to the calendar,\nsimilar to a traditional cron specification, but with labeled fields. Each\nfield can be one of:\n *: matches always\n x: matches when the field equals x\n x/y : matches when the field equals x+n*y where n is an integer\n x-z: matches when the field is between x and z inclusive\n w,x,y,...: matches when the field is one of the listed values\nEach x, y, z, ... is either a decimal integer, or a month or day of week name\nor abbreviation (in the appropriate fields).\nA timestamp matches if all fields match.\nNote that fields have different default values, for convenience.\nNote that the special case that some cron implementations have for treating\nday_of_month and day_of_week as \"or\" instead of \"and\" when both are set is\nnot implemented.\nday_of_week can accept 0 or 7 as Sunday\nCalendarSpec gets compiled into StructuredCalendarSpec, which is what will be\nreturned if you describe the schedule." }, - "v1Callback": { - "type": "object", - "properties": { - "nexus": { - "$ref": "#/definitions/CallbackNexus" - }, - "internal": { - "$ref": "#/definitions/CallbackInternal" - }, - "worker": { - "$ref": "#/definitions/v1CallbackWorker" - }, - "links": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/v1Link" - }, - "description": "Links associated with the callback. It can be used to link to underlying resources of the\ncallback." - } - }, - "description": "Callback to attach to various events in the system, e.g. workflow run completion." - }, "v1CallbackState": { "type": "string", "enum": [ @@ -16146,17 +16135,18 @@ "type": "string" } }, - "description": "Identifies a specific execution within a namespace. This is used for standalone activities\nexecutions in batch jobs currently." + "description": "Identifies a specific execution within a namespace." }, "v1ExecutionType": { "type": "string", "enum": [ "EXECUTION_TYPE_UNSPECIFIED", "EXECUTION_TYPE_WORKFLOW", - "EXECUTION_TYPE_ACTIVITY" + "EXECUTION_TYPE_ACTIVITY", + "EXECUTION_TYPE_NEXUS_OPERATION" ], "default": "EXECUTION_TYPE_UNSPECIFIED", - "description": " - EXECUTION_TYPE_WORKFLOW: A workflow execution archetype.\n - EXECUTION_TYPE_ACTIVITY: An activity execution archetype. This is reserved for standalone activities." + "description": " - EXECUTION_TYPE_WORKFLOW: A workflow execution archetype.\n - EXECUTION_TYPE_ACTIVITY: An activity execution archetype. This is reserved for standalone activities.\n - EXECUTION_TYPE_NEXUS_OPERATION: A Nexus operation execution archetype. This is reserved for standalone Nexus operations." }, "v1ExternalWorkflowExecutionCancelRequestedEventAttributes": { "type": "object", @@ -16889,12 +16879,27 @@ "workflow": { "$ref": "#/definitions/LinkWorkflow" }, - "nexusOperationCallback": { - "$ref": "#/definitions/LinkNexusOperationCallback" + "callback": { + "$ref": "#/definitions/v1LinkCallback" } }, "description": "Link can be associated with history events. It might contain information about an external entity\nrelated to the history event. For example, workflow A makes a Nexus call that starts workflow B:\nin this case, a history event in workflow A could contain a Link to the workflow started event in\nworkflow B, and vice-versa." }, + "v1LinkCallback": { + "type": "object", + "properties": { + "namespace": { + "type": "string" + }, + "execution": { + "$ref": "#/definitions/v1Execution" + }, + "requestId": { + "type": "string" + } + }, + "description": "A link to a worker callback attached to an execution. An execution (e.g. standalone Nexus operation) can have\nmultiple callbacks attached, and will be differentiated by the request_id used when the callback is invoked." + }, "v1ListActivityExecutionsResponse": { "type": "object", "properties": { @@ -18895,7 +18900,7 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/v1Callback" + "$ref": "#/definitions/commonV1Callback" }, "description": "Callbacks to be called by the server when this update reaches a terminal state." }, @@ -21800,7 +21805,7 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/v1Callback" + "$ref": "#/definitions/commonV1Callback" }, "description": "Completion callbacks attached to the running workflow execution." }, @@ -22016,7 +22021,7 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/v1Callback" + "$ref": "#/definitions/commonV1Callback" }, "description": "Completion callbacks attached when this workflow was started." }, diff --git a/openapi/openapiv3.yaml b/openapi/openapiv3.yaml index 07a73f1cb..bf222bd52 100644 --- a/openapi/openapiv3.yaml +++ b/openapi/openapiv3.yaml @@ -10880,6 +10880,11 @@ components: blockedReason: type: string description: If the state is BLOCKED, blocked reason provides additional information. + requestId: + type: string + description: |- + Server-generated request ID used as an idempotency token when invoking callbacks. + It has no relation to caller-side request_id sent in operations like StartNexusOperationExecutionRequest. failure: allOf: - $ref: '#/components/schemas/Failure' @@ -12406,15 +12411,14 @@ components: - EXECUTION_TYPE_UNSPECIFIED - EXECUTION_TYPE_WORKFLOW - EXECUTION_TYPE_ACTIVITY + - EXECUTION_TYPE_NEXUS_OPERATION type: string format: enum businessId: type: string runId: type: string - description: |- - Identifies a specific execution within a namespace. This is used for standalone activities - executions in batch jobs currently. + description: Identifies a specific execution within a namespace. ExternalWorkflowExecutionCancelRequestedEventAttributes: type: object properties: @@ -13121,8 +13125,8 @@ components: $ref: '#/components/schemas/Link_NexusOperation' workflow: $ref: '#/components/schemas/Link_Workflow' - nexusOperationCallback: - $ref: '#/components/schemas/Link_NexusOperationCallback' + callback: + $ref: '#/components/schemas/Link_Callback' description: |- Link can be associated with history events. It might contain information about an external entity related to the history event. For example, workflow A makes a Nexus call that starts workflow B: @@ -13147,29 +13151,28 @@ components: A link to a built-in batch job. Batch jobs can be used to perform operations on a set of workflows (e.g. terminate, signal, cancel, etc). This link can be put on workflow history events generated by actions taken by a batch job. - Link_NexusOperation: + Link_Callback: type: object properties: namespace: type: string - operationId: - type: string - runId: + execution: + $ref: '#/components/schemas/Execution' + requestId: type: string - description: A link to a standalone Nexus operation. - Link_NexusOperationCallback: + description: |- + A link to a worker callback attached to an execution. An execution (e.g. standalone Nexus operation) can have + multiple callbacks attached, and will be differentiated by the request_id used when the callback is invoked. + Link_NexusOperation: type: object properties: + namespace: + type: string operationId: type: string runId: type: string - requestId: - type: string - description: |- - A link to a worker callback attached to a Nexus operation within the same namespace. e.g. the completion handler attached - to a standalone Nexus operation, not the source Nexus operation itself. A standalone Nexus operation can have multiple - callbacks attached to them, and will be differentiated by the server-generated request_id used when the callback was invoked. + description: A link to a standalone Nexus operation. Link_Workflow: type: object properties: diff --git a/temporal/api/callback/v1/message.proto b/temporal/api/callback/v1/message.proto index e72ff6fdb..ca5b7ea0b 100644 --- a/temporal/api/callback/v1/message.proto +++ b/temporal/api/callback/v1/message.proto @@ -36,12 +36,16 @@ message CallbackInfo { // If the state is BLOCKED, blocked reason provides additional information. string blocked_reason = 8; + // Server-generated request ID used as an idempotency token when invoking callbacks. + // It has no relation to caller-side request_id sent in operations like StartNexusOperationExecutionRequest. + string request_id = 9; + // Result of the callback's execution, only set when the callback reaches a terminal state. oneof result { // The callback completed successfully. (Which may include delivering a "failed" result successfully.) - google.protobuf.Empty success = 9; + google.protobuf.Empty success = 10; // The failure if the callback was not able to complete successfully. e.g. timed out, received an // unretriable error, etc. - temporal.api.failure.v1.Failure failure = 10; + temporal.api.failure.v1.Failure failure = 11; } } diff --git a/temporal/api/common/v1/message.proto b/temporal/api/common/v1/message.proto index df6ec72d7..ec2a108b6 100644 --- a/temporal/api/common/v1/message.proto +++ b/temporal/api/common/v1/message.proto @@ -68,8 +68,7 @@ message WorkflowExecution { string run_id = 2; } -// Identifies a specific execution within a namespace. This is used for standalone activities -// executions in batch jobs currently. +// Identifies a specific execution within a namespace. message Execution { temporal.api.enums.v1.ExecutionType type = 1; string business_id = 2; @@ -304,13 +303,19 @@ message Link { string reason = 4; } - // A link to a worker callback attached to a Nexus operation within the same namespace. e.g. the completion handler attached - // to a standalone Nexus operation, not the source Nexus operation itself. A standalone Nexus operation can have multiple - // callbacks attached to them, and will be differentiated by the server-generated request_id used when the callback was invoked. - message NexusOperationCallback { - string operation_id = 1; - string run_id = 2; + // A link to a worker callback attached to an execution. An execution (e.g. standalone Nexus operation) can have + // multiple callbacks attached, and will be differentiated by the request_id used when the callback is invoked. + message Callback { + string namespace = 1; + Execution execution = 2; string request_id = 3; + + // NOTE: An Execution alone is not precise enough for all cases. e.g. a callback attached to a + // workflow update would be tied to a workflow execution as well as an update "component" (with + // an associated "component_id"). + // + // Worker callbacks can only be associated with standalone Nexus operations, so those additional + // fields aren't currently necessary. } oneof variant { @@ -319,7 +324,7 @@ message Link { Activity activity = 3; NexusOperation nexus_operation = 4; Workflow workflow = 5; - NexusOperationCallback nexus_operation_callback = 6; + Callback callback = 6; } } diff --git a/temporal/api/enums/v1/common.proto b/temporal/api/enums/v1/common.proto index e2929337e..ef8381cda 100644 --- a/temporal/api/enums/v1/common.proto +++ b/temporal/api/enums/v1/common.proto @@ -113,4 +113,6 @@ enum ExecutionType { EXECUTION_TYPE_WORKFLOW = 1; // An activity execution archetype. This is reserved for standalone activities. EXECUTION_TYPE_ACTIVITY = 2; -} \ No newline at end of file + // A Nexus operation execution archetype. This is reserved for standalone Nexus operations. + EXECUTION_TYPE_NEXUS_OPERATION = 3; +} From d8bb944c5ff96188f7e46c25cf471b7ddd87d79c Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Mon, 24 Aug 2026 15:59:11 -0700 Subject: [PATCH 5/7] Add Link_Callback.component_id for Workflow Update references --- openapi/openapiv2.json | 4 ++++ openapi/openapiv3.yaml | 6 ++++++ temporal/api/common/v1/message.proto | 12 +++++------- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/openapi/openapiv2.json b/openapi/openapiv2.json index 1d5d0d671..12d649eb7 100644 --- a/openapi/openapiv2.json +++ b/openapi/openapiv2.json @@ -16894,6 +16894,10 @@ "execution": { "$ref": "#/definitions/v1Execution" }, + "componentId": { + "type": "string", + "description": "In most cases, the Execution is sufficient to identify the callback's source. But for some types of execution,\na separate \"component ID\" is required. e.g. for completion callbacks attached to a workflow update. The\nexecution would be the workflow itself, with the component_id referring to the update of that workflow." + }, "requestId": { "type": "string" } diff --git a/openapi/openapiv3.yaml b/openapi/openapiv3.yaml index bf222bd52..d73cd8f92 100644 --- a/openapi/openapiv3.yaml +++ b/openapi/openapiv3.yaml @@ -13158,6 +13158,12 @@ components: type: string execution: $ref: '#/components/schemas/Execution' + componentId: + type: string + description: |- + In most cases, the Execution is sufficient to identify the callback's source. But for some types of execution, + a separate "component ID" is required. e.g. for completion callbacks attached to a workflow update. The + execution would be the workflow itself, with the component_id referring to the update of that workflow. requestId: type: string description: |- diff --git a/temporal/api/common/v1/message.proto b/temporal/api/common/v1/message.proto index ec2a108b6..09c829af6 100644 --- a/temporal/api/common/v1/message.proto +++ b/temporal/api/common/v1/message.proto @@ -308,14 +308,12 @@ message Link { message Callback { string namespace = 1; Execution execution = 2; - string request_id = 3; + // In most cases, the Execution is sufficient to identify the callback's source. But for some types of execution, + // a separate "component ID" is required. e.g. for completion callbacks attached to a workflow update. The + // execution would be the workflow itself, with the component_id referring to the update of that workflow. + string component_id = 3; - // NOTE: An Execution alone is not precise enough for all cases. e.g. a callback attached to a - // workflow update would be tied to a workflow execution as well as an update "component" (with - // an associated "component_id"). - // - // Worker callbacks can only be associated with standalone Nexus operations, so those additional - // fields aren't currently necessary. + string request_id = 4; } oneof variant { From 07588267f4d367d2de23a500f9514b8688350194 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Sat, 29 Aug 2026 12:37:24 -0700 Subject: [PATCH 6/7] Rename Worker to NexusHandler --- openapi/openapiv2.json | 92 ++++++++++--------- openapi/openapiv3.yaml | 24 +++-- temporal/api/common/v1/message.proto | 21 +++-- .../v1/request_response.proto | 2 +- .../workflowservice/v1/request_response.proto | 2 +- 5 files changed, 76 insertions(+), 65 deletions(-) diff --git a/openapi/openapiv2.json b/openapi/openapiv2.json index 12d649eb7..4542352dd 100644 --- a/openapi/openapiv2.json +++ b/openapi/openapiv2.json @@ -10857,7 +10857,32 @@ }, "description": "Header to attach to callback request." } - } + }, + "title": "Nexus callbacks are used to delivery Nexus operation completions, as defined in the Nexus RPC spec: \nhttps://github.com/nexus-rpc/api/blob/main/SPEC.md#callback-urls" + }, + "CallbackNexusHandler": { + "type": "object", + "properties": { + "taskQueueName": { + "type": "string", + "description": "Nexus task queue the Temporal worker is listening on.\n\nNOTE: This is not a temporal.api.taskqueue.v1.TaskQueue to avoid a circular dependency." + }, + "service": { + "type": "string", + "description": "Target Nexus service, e.g. \"HTTPAdapter\"." + }, + "operation": { + "type": "string", + "description": "Target operation, e.g. \"DeliverAsWebhook\"." + }, + "sourceContext": { + "$ref": "#/definitions/v1Payload", + "description": "There are restrictions on the maxium payload size a single callback can carry, as well as the\ntotal sum of all source context payloads attached to an execution. See dynamic configuration\nsettings: \"callback.worker.sourceContext.maxSize\", \"callback.worker.sourceContext.aggregateMaxSize\".", + "title": "Arbitrary user-supplied data from the source operation's callsite. (As applicable, not all operations\nsupport attaching context data.)" + } + }, + "description": "The targeted Nexus service must be registered within the same namespace as the source operation\nthe callback is attached to. (While Nexus allows for cross-namespace operations, NexusHandler callbacks\nare strictly caller-side.)\n\nNexusHandler callbacks are only supported for certain types of operations, e.g. standalone Nexus operations.\nAttempting to attach a Worker callback for an unsupported operation will result in an INVALID_ARGUMENT\nerror from the server.", + "title": "NexusHandler callbacks are requests to invoke a specific shape of Nexus operation on a Temporal worker.\nThe specified Nexus operation must have the following:\n- Input: temporal.api.notificationservice.v1.OnCompleteRequest\n- Output: temporal.api.notificationservice.v1.OnCompleteResponse" }, "ComputeStatusProviderValidationStatus": { "type": "object", @@ -10936,6 +10961,20 @@ }, "description": "Target an external server by URL.\nAt a later point, this will support providing credentials, in the meantime, an http.RoundTripper can be injected\ninto the server to modify the request." }, + "EndpointTargetWorker": { + "type": "object", + "properties": { + "namespace": { + "type": "string", + "description": "Namespace to route requests to." + }, + "taskQueue": { + "type": "string", + "description": "Nexus task queue to route requests to." + } + }, + "description": "Target a worker polling on a Nexus task queue in a specific namespace." + }, "EnvironmentInfoArchitecture": { "type": "string", "enum": [ @@ -12717,7 +12756,7 @@ }, "onConflictOptions": { "$ref": "#/definitions/apiNexusoperationV1OnConflictOptions", - "description": "Defines actions to be done to the existing running standalone Nexus when the conflict policy\nNEXUS_OPERATION_ID_CONFLICT_POLICY_USE_EXISTING is used. If not set or set to a empty object\n(all options with default value), it won't do modify running operation." + "description": "Defines actions to be done to the existing running standalone Nexus when the conflict policy\nNEXUS_OPERATION_ID_CONFLICT_POLICY_USE_EXISTING is used. If not set or set to a empty object\n(all options with default value), it will not modify the running operation." }, "searchAttributes": { "$ref": "#/definitions/v1SearchAttributes", @@ -13665,8 +13704,8 @@ "internal": { "$ref": "#/definitions/CallbackInternal" }, - "worker": { - "$ref": "#/definitions/v1CallbackWorker" + "nexusHandler": { + "$ref": "#/definitions/CallbackNexusHandler" }, "links": { "type": "array", @@ -14786,30 +14825,6 @@ "default": "CALLBACK_STATE_UNSPECIFIED", "description": "State of a callback.\n\n - CALLBACK_STATE_UNSPECIFIED: Default value, unspecified state.\n - CALLBACK_STATE_STANDBY: Callback is standing by, waiting to be triggered.\n - CALLBACK_STATE_SCHEDULED: Callback is in the queue waiting to be executed or is currently executing.\n - CALLBACK_STATE_BACKING_OFF: Callback has failed with a retryable error and is backing off before the next attempt.\n - CALLBACK_STATE_FAILED: Callback has failed.\n - CALLBACK_STATE_SUCCEEDED: Callback has succeeded.\n - CALLBACK_STATE_BLOCKED: Callback is blocked, e.g. by circuit breaker." }, - "v1CallbackWorker": { - "type": "object", - "properties": { - "taskQueueName": { - "type": "string", - "description": "Nexus task queue the Temporal worker is listening on.\n\nNOTE: This is not a temporal.api.taskqueue.v1.TaskQueue to avoid a circular dependency." - }, - "service": { - "type": "string", - "description": "Target Nexus service, e.g. \"HTTPAdapter\"." - }, - "operation": { - "type": "string", - "description": "Target operation, e.g. \"DeliverAsWebhook\"." - }, - "sourceContext": { - "$ref": "#/definitions/v1Payload", - "description": "There is a relatively small maximum size the source context can be, e.g. 32KiB.", - "title": "Arbitrary user-supplied data from the source operation's callsite. (As applicable, not all operations\nsupport attaching context data.)" - } - }, - "description": "The targeted Nexus service must be registered within the same namespace as the source operation\nthe callback is attached to. (While Nexus allows for cross-namespace operations, worker callbacks\nare purely \"caller-side\".)\n\nWorker callbacks are only supported for certain types of operations, e.g. standalone Nexus operations.\nAttempting to attach a Worker callback for an unsupported operation will result in an INVALID_ARGUMENT\nerror from the server.", - "title": "Worker callbacks are requests to invoke a specific shape of Nexus operation on a Temporal worker.\nThe specified Nexus operation must have the following:\n- Input: temporal.api.notificationservice.v1.OnCompleteRequest\n- Output: temporal.api.notificationservice.v1.OnCompleteResponse" - }, "v1CancelExternalWorkflowExecutionFailedCause": { "type": "string", "enum": [ @@ -15978,7 +15993,7 @@ "type": "object", "properties": { "worker": { - "$ref": "#/definitions/v1EndpointTargetWorker" + "$ref": "#/definitions/EndpointTargetWorker" }, "external": { "$ref": "#/definitions/EndpointTargetExternal" @@ -15986,20 +16001,6 @@ }, "description": "Target to route requests to." }, - "v1EndpointTargetWorker": { - "type": "object", - "properties": { - "namespace": { - "type": "string", - "description": "Namespace to route requests to." - }, - "taskQueue": { - "type": "string", - "description": "Nexus task queue to route requests to." - } - }, - "description": "Target a worker polling on a Nexus task queue in a specific namespace." - }, "v1EnvironmentInfo": { "type": "object", "properties": { @@ -16896,10 +16897,11 @@ }, "componentId": { "type": "string", - "description": "In most cases, the Execution is sufficient to identify the callback's source. But for some types of execution,\na separate \"component ID\" is required. e.g. for completion callbacks attached to a workflow update. The\nexecution would be the workflow itself, with the component_id referring to the update of that workflow." + "description": "In most cases, the Execution is sufficient to identify the callback's source. But for some types of execution,\na separate \"component ID\" is required. e.g. for completion callbacks attached to a workflow update. The\nexecution would be the workflow itself, with the component_id being the update ID of the update operation." }, "requestId": { - "type": "string" + "type": "string", + "description": "Server-generate request ID sent when the callback was dispatched." } }, "description": "A link to a worker callback attached to an execution. An execution (e.g. standalone Nexus operation) can have\nmultiple callbacks attached, and will be differentiated by the request_id used when the callback is invoked." diff --git a/openapi/openapiv3.yaml b/openapi/openapiv3.yaml index d73cd8f92..6c3d7c9fb 100644 --- a/openapi/openapiv3.yaml +++ b/openapi/openapiv3.yaml @@ -10826,8 +10826,8 @@ components: $ref: '#/components/schemas/Callback_Nexus' internal: $ref: '#/components/schemas/Callback_Internal' - worker: - $ref: '#/components/schemas/Callback_Worker' + nexusHandler: + $ref: '#/components/schemas/Callback_NexusHandler' links: type: array items: @@ -10915,7 +10915,8 @@ components: additionalProperties: type: string description: Header to attach to callback request. - Callback_Worker: + description: "Nexus callbacks are used to delivery Nexus operation completions, as defined in the Nexus RPC spec: \n https://github.com/nexus-rpc/api/blob/main/SPEC.md#callback-urls" + Callback_NexusHandler: type: object properties: taskQueueName: @@ -10937,18 +10938,20 @@ components: Arbitrary user-supplied data from the source operation's callsite. (As applicable, not all operations support attaching context data.) - There is a relatively small maximum size the source context can be, e.g. 32KiB. + There are restrictions on the maxium payload size a single callback can carry, as well as the + total sum of all source context payloads attached to an execution. See dynamic configuration + settings: "callback.worker.sourceContext.maxSize", "callback.worker.sourceContext.aggregateMaxSize". description: |- - Worker callbacks are requests to invoke a specific shape of Nexus operation on a Temporal worker. + NexusHandler callbacks are requests to invoke a specific shape of Nexus operation on a Temporal worker. The specified Nexus operation must have the following: - Input: temporal.api.notificationservice.v1.OnCompleteRequest - Output: temporal.api.notificationservice.v1.OnCompleteResponse The targeted Nexus service must be registered within the same namespace as the source operation - the callback is attached to. (While Nexus allows for cross-namespace operations, worker callbacks - are purely "caller-side".) + the callback is attached to. (While Nexus allows for cross-namespace operations, NexusHandler callbacks + are strictly caller-side.) - Worker callbacks are only supported for certain types of operations, e.g. standalone Nexus operations. + NexusHandler callbacks are only supported for certain types of operations, e.g. standalone Nexus operations. Attempting to attach a Worker callback for an unsupported operation will result in an INVALID_ARGUMENT error from the server. CanceledFailureInfo: @@ -13163,9 +13166,10 @@ components: description: |- In most cases, the Execution is sufficient to identify the callback's source. But for some types of execution, a separate "component ID" is required. e.g. for completion callbacks attached to a workflow update. The - execution would be the workflow itself, with the component_id referring to the update of that workflow. + execution would be the workflow itself, with the component_id being the update ID of the update operation. requestId: type: string + description: Server-generate request ID sent when the callback was dispatched. description: |- A link to a worker callback attached to an execution. An execution (e.g. standalone Nexus operation) can have multiple callbacks attached, and will be differentiated by the request_id used when the callback is invoked. @@ -17411,7 +17415,7 @@ components: description: |- Defines actions to be done to the existing running standalone Nexus when the conflict policy NEXUS_OPERATION_ID_CONFLICT_POLICY_USE_EXISTING is used. If not set or set to a empty object - (all options with default value), it won't do modify running operation. + (all options with default value), it will not modify the running operation. searchAttributes: allOf: - $ref: '#/components/schemas/SearchAttributes' diff --git a/temporal/api/common/v1/message.proto b/temporal/api/common/v1/message.proto index 09c829af6..b349063eb 100644 --- a/temporal/api/common/v1/message.proto +++ b/temporal/api/common/v1/message.proto @@ -184,6 +184,8 @@ message ResetOptions { // Callback to attach to various events in the system, e.g. workflow run completion. message Callback { + // Nexus callbacks are used to delivery Nexus operation completions, as defined in the Nexus RPC spec: + // https://github.com/nexus-rpc/api/blob/main/SPEC.md#callback-urls message Nexus { // Callback URL. string url = 1; @@ -200,19 +202,19 @@ message Callback { bytes data = 1; } - // Worker callbacks are requests to invoke a specific shape of Nexus operation on a Temporal worker. + // NexusHandler callbacks are requests to invoke a specific shape of Nexus operation on a Temporal worker. // The specified Nexus operation must have the following: // - Input: temporal.api.notificationservice.v1.OnCompleteRequest // - Output: temporal.api.notificationservice.v1.OnCompleteResponse // // The targeted Nexus service must be registered within the same namespace as the source operation - // the callback is attached to. (While Nexus allows for cross-namespace operations, worker callbacks - // are purely "caller-side".) + // the callback is attached to. (While Nexus allows for cross-namespace operations, NexusHandler callbacks + // are strictly caller-side.) // - // Worker callbacks are only supported for certain types of operations, e.g. standalone Nexus operations. + // NexusHandler callbacks are only supported for certain types of operations, e.g. standalone Nexus operations. // Attempting to attach a Worker callback for an unsupported operation will result in an INVALID_ARGUMENT // error from the server. - message Worker { + message NexusHandler { // Nexus task queue the Temporal worker is listening on. // // NOTE: This is not a temporal.api.taskqueue.v1.TaskQueue to avoid a circular dependency. @@ -226,7 +228,9 @@ message Callback { // Arbitrary user-supplied data from the source operation's callsite. (As applicable, not all operations // support attaching context data.) // - // There is a relatively small maximum size the source context can be, e.g. 32KiB. + // There are restrictions on the maxium payload size a single callback can carry, as well as the + // total sum of all source context payloads attached to an execution. See dynamic configuration + // settings: "callback.worker.sourceContext.maxSize", "callback.worker.sourceContext.aggregateMaxSize". temporal.api.common.v1.Payload source_context = 4; } @@ -234,7 +238,7 @@ message Callback { oneof variant { Nexus nexus = 2; Internal internal = 3; - Worker worker = 4; + NexusHandler nexus_handler = 4; } // Links associated with the callback. It can be used to link to underlying resources of the @@ -310,9 +314,10 @@ message Link { Execution execution = 2; // In most cases, the Execution is sufficient to identify the callback's source. But for some types of execution, // a separate "component ID" is required. e.g. for completion callbacks attached to a workflow update. The - // execution would be the workflow itself, with the component_id referring to the update of that workflow. + // execution would be the workflow itself, with the component_id being the update ID of the update operation. string component_id = 3; + // Server-generate request ID sent when the callback was dispatched. string request_id = 4; } diff --git a/temporal/api/notificationservice/v1/request_response.proto b/temporal/api/notificationservice/v1/request_response.proto index 9cd1f4863..9a7ac529f 100644 --- a/temporal/api/notificationservice/v1/request_response.proto +++ b/temporal/api/notificationservice/v1/request_response.proto @@ -13,7 +13,7 @@ import "temporal/api/common/v1/message.proto"; import "temporal/api/failure/v1/message.proto"; // OnCompleteRequest is the request type to the NotificationService's OnComplete operation, -// allowing for defining completion handlers for arbitrary asynchronous operations. +// allowing for defining completion handlers for arbitrary operations. // // Information about the source operation will be available in the form of a commonpb.Link, // which will be available separately from this OnCompleteRequest. e.g. a link to the source diff --git a/temporal/api/workflowservice/v1/request_response.proto b/temporal/api/workflowservice/v1/request_response.proto index e113e0ac2..1aae988d8 100644 --- a/temporal/api/workflowservice/v1/request_response.proto +++ b/temporal/api/workflowservice/v1/request_response.proto @@ -3407,7 +3407,7 @@ message StartNexusOperationExecutionRequest { temporal.api.enums.v1.NexusOperationIdConflictPolicy id_conflict_policy = 13; // Defines actions to be done to the existing running standalone Nexus when the conflict policy // NEXUS_OPERATION_ID_CONFLICT_POLICY_USE_EXISTING is used. If not set or set to a empty object - // (all options with default value), it won't do modify running operation. + // (all options with default value), it will not modify the running operation. temporal.api.nexusoperation.v1.OnConflictOptions on_conflict_options = 17; // Search attributes for indexing. From ff34ca19322c54f97794f5154db1c23c8caf8898 Mon Sep 17 00:00:00 2001 From: Chris Smith Date: Mon, 31 Aug 2026 11:19:17 -0700 Subject: [PATCH 7/7] Update the dynamic config fields controlling NexusHandler callbacks --- temporal/api/common/v1/message.proto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/temporal/api/common/v1/message.proto b/temporal/api/common/v1/message.proto index b349063eb..15cc54660 100644 --- a/temporal/api/common/v1/message.proto +++ b/temporal/api/common/v1/message.proto @@ -229,8 +229,8 @@ message Callback { // support attaching context data.) // // There are restrictions on the maxium payload size a single callback can carry, as well as the - // total sum of all source context payloads attached to an execution. See dynamic configuration - // settings: "callback.worker.sourceContext.maxSize", "callback.worker.sourceContext.aggregateMaxSize". + // total sum of all source context payloads attached to an execution. See dynamic configuration: + // "callback.nexusHandler.sourceContext.maxSize", "callback.nexusHandler.sourceContext.aggregateMaxSize". temporal.api.common.v1.Payload source_context = 4; }