33> [ !WARNING]
44> Standalone Nexus operations are experimental and may be subject to backwards-incompatible
55> changes. They require a Temporal server that implements and enables them via the dynamic configs
6- > shown below.
7- >
6+ > shown below. Use the dev server build at
7+ > https://github.com/temporalio/cli/releases/tag/v1.7.4-standalone-nexus-operations .
8+ >
89 This sample shows how to invoke and manage ** standalone Nexus operations** — Nexus operations
910started directly by a client rather than from within a caller workflow. The long-running operation
10- (` startGreeting ` ) is backed by a ` GreetingWorkflow ` that blocks until it is cancelled or terminated;
11- the quick operation (` greet ` ) is synchronous and completes immediately.
11+ (` startGreeting ` ) is backed by a ` GreetingWorkflow ` that blocks until it is cancelled; the quick
12+ operation (` greet ` ) is synchronous and completes immediately.
1213
1314` StandaloneClientStarter ` runs each capability in turn:
14151 . ** Execute** an operation and read its result, both directly (` execute ` ) and via a handle
15- (` start ` then ` handle.getResult ` ).
16+ (` getHandle ` then ` handle.getResult ` ).
16172 . ** Cancel** a running operation (` handle.cancel ` ).
17- 3 . ** Terminate** a running operation (` handle.terminate ` ). Operation-terminate is a known gap that
18- does not stop the backing workflow, so the sample also terminates the backing workflow by ID.
19- 4 . ** Visibility** — ` list ` operations with a status filter and ` count ` them (total and grouped) via
18+ 3 . ** Visibility** — ` list ` operations with a status filter and ` count ` them (total and grouped) via
2019 ` NexusClient ` .
2120
22- ### Running
21+ The starter and worker connect to two different namespaces (a "caller" namespace and a "handler"
22+ namespace) — this mirrors how Nexus is typically used to cross namespace boundaries. The client is
23+ configured via the SDK's [ environment configuration] ( https://docs.temporal.io/develop/environment-configuration )
24+ support (` ClientConfigProfile.load() ` ), which reads ` TEMPORAL_NAMESPACE ` , ` TEMPORAL_ADDRESS ` , etc.
25+ from the environment (and optionally a profile from ` temporal.toml ` ).
2326
24- Start a Temporal server (version ` 1.7.3-standalone-nexus-operations ` ) with the standalone-Nexus dynamic configs enabled:
27+ ### Run locally against a dev server
2528
26- ``` bash
27- temporal server start-dev \
28- --dynamic-config-value nexusoperation.enableStandalone=true \
29- --dynamic-config-value history.enableChasmCallbacks=true
30- ```
29+ 1 . Start the [ Temporal dev server build that supports standalone Nexus operations] ( https://docs.temporal.io/standalone-nexus-operation#temporal-cli-support ) with the required namespaces pre-created:
3130
32- Create the namespace and the Nexus endpoint:
31+ ``` bash
32+ ./temporal server start-dev \
33+ --namespace my-caller-namespace \
34+ --namespace my-handler-namespace
35+ ```
3336
34- ``` bash
35- temporal operator nexus endpoint create \
36- --name nexus-standalone-operation-endpoint \
37- --target-namespace default \
38- --target-task-queue nexusstandalone-handler-task-queue
39- ```
37+ 2 . Create a Nexus endpoint that routes to the handler namespace and the worker's task queue:
4038
41- In one terminal, start the handler worker:
39+ ``` bash
40+ ./temporal operator nexus endpoint create \
41+ --name my-nexus-endpoint \
42+ --target-namespace my-handler-namespace \
43+ --target-task-queue nexus-handler-queue
44+ ```
4245
43- ``` bash
44- ./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexusstandalone.handler.HandlerWorker
45- ```
46+ 3 . In a second terminal, start the handler worker in the handler namespace:
4647
47- In a second terminal, run the starter:
48+ ``` bash
49+ TEMPORAL_NAMESPACE=my-handler-namespace \
50+ ./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexusstandalone.handler.HandlerWorker
51+ ```
4852
49- ``` bash
50- ./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexusstandalone.StandaloneClientStarter
51- ```
53+ 4 . In a third terminal, run the starter in the caller namespace:
5254
53- Expected output (operation IDs and Visibility counts will differ between runs):
55+ ``` bash
56+ TEMPORAL_NAMESPACE=my-caller-namespace \
57+ ./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexusstandalone.StandaloneClientStarter
58+ ```
59+
60+ Expected output (operation IDs are fixed, but Visibility counts grow across runs):
5461
5562```
5663execute() returned: Hello, execute!
57- start() id=73e77105-f7ec-4a1f-a24a-1f9a9cc87248 then getResult() returned: Hello, execute-via-handle!
58- Started 'to-cancel' id=12b554b5-d9f8-4f4f-9314-db508fd91999, requesting cancellation
59- Operation id=12b554b5-d9f8-4f4f-9314-db508fd91999 ended as expected after cancel: Nexus operation failed: operationId='12b554b5-d9f8-4f4f-9314-db508fd91999'
60- Started 'to-terminate' id=b1dae9d4-2d6b-45d6-ab3b-8725cc2cf6de, terminating
61- 'to-terminate' ended as expected after terminate: Nexus operation failed: operationId='b1dae9d4-2d6b-45d6-ab3b-8725cc2cf6de'
62- Terminated backing workflow greeting-to-terminate-ef71547a
63- List filtered to Completed returned 2 operation(s)
64- Total operation count: 4
65- Grouped count total=4, groups:
64+ getHandle(id=execute-nexus) then getResult() returned: Hello, execute!
65+ Started 'to-cancel' id=start-and-cancel-nexus, requesting cancellation
66+ Operation id=start-and-cancel-nexus ended as expected after cancel: Nexus operation failed: operationId='start-and-cancel-nexus'
67+ List filtered to Completed returned 1 operation(s)
68+ Total operation count: 2
69+ Grouped count total=2, groups:
6670 group values=[[Canceled]] count=1
67- group values=[[Completed]] count=2
68- group values=[[Terminated]] count=1
71+ group values=[[Completed]] count=1
6972```
7073
71- ### Cancellation vs. termination
74+ ### Run against Temporal Cloud
75+
76+ 1 . Create two namespaces in Temporal Cloud (for example ` my-caller-namespace.<account> ` and
77+ ` my-handler-namespace.<account> ` ) and generate an API key (or mTLS cert) that can access both.
78+
79+ 2 . Create a Nexus endpoint that targets the handler namespace and the worker's task queue. See the
80+ Temporal Cloud instructions at https://docs.temporal.io/nexus/registry#create-a-nexus-endpoint .
81+ Use:
82+ - Endpoint name: ` my-nexus-endpoint `
83+ - Target namespace: ` my-handler-namespace.<account> `
84+ - Target task queue: ` nexus-handler-queue `
85+ - Allowed caller namespaces: include ` my-caller-namespace.<account> ` (endpoints reject callers
86+ that are not on this list)
87+
88+ 3 . Add two profiles to your [ environment configuration file] ( https://docs.temporal.io/develop/environment-configuration ) ,
89+ one per namespace. Using API keys:
90+
91+ ``` toml
92+ [profile .handler ]
93+ address = " <region>.<cloud>.api.temporal.io:7233"
94+ namespace = " my-handler-namespace.<account>"
95+ api_key = " <your-api-key>"
96+
97+ [profile .caller ]
98+ address = " <region>.<cloud>.api.temporal.io:7233"
99+ namespace = " my-caller-namespace.<account>"
100+ api_key = " <your-api-key>"
101+ ```
102+
103+ For mTLS instead of API keys, set ` tls.client_cert_path ` and ` tls.client_key_path ` on each profile
104+ (see the [ docs] ( https://docs.temporal.io/develop/environment-configuration ) for the full schema).
105+
106+ 4 . Run the worker and starter in separate terminals, selecting the appropriate profile in each:
107+
108+ ``` bash
109+ # terminal 1 (worker, handler namespace)
110+ TEMPORAL_PROFILE=handler \
111+ ./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexusstandalone.handler.HandlerWorker
112+ ```
113+
114+ ``` bash
115+ # terminal 2 (starter, caller namespace)
116+ TEMPORAL_PROFILE=caller \
117+ ./gradlew -q :core:execute -PmainClass=io.temporal.samples.nexusstandalone.StandaloneClientStarter
118+ ```
119+
120+ ### Cancellation
72121
73122A workflow-backed Nexus operation does ** not** need any explicit cancel handling to be cancellable.
74123When you call ` handle.cancel(...) ` , the server delivers a cancellation request to the backing
@@ -78,8 +127,3 @@ operation as cancelled. Cancellation is **cooperative**, though: if the backing
78127ignored ` CanceledFailure ` (or did all of its waiting inside a detached cancellation scope), the
79128cancel request would have no effect and the operation would run until it completes or hits its
80129schedule-to-close timeout.
81-
82- ` handle.terminate(...) ` is different. It forcefully closes the ** operation** record, but currently
83- does ** not** propagate to the backing workflow (a known gap) — the workflow keeps running and
84- nothing appears in its history. Until that gap is closed, terminate the backing workflow directly by
85- its workflow ID, as ` StandaloneClientStarter.terminateBackingWorkflow ` does.
0 commit comments