Skip to main content
Version: 2.x

Cross-namespace service calls

A service operation, also called a Nexus operation, is the supported durable call between namespaces in one Durable Workflow cluster. It is addressed by the target namespace and an endpoint/service/operation triple. A child workflow or ordinary signal stays within its namespace. Neither a queue name nor a different language changes that rule.

Choose a supported authoring surface​

CallerPublished surfaceTarget namespace
Embedded PHPWorkflow::serviceOperation() or the serviceOperation() helperServiceOperationOptions(targetNamespace: 'billing')
PHP SDKWorkflowContext::serviceOperation()Array option ['namespace' => 'billing']
Python SDKyield context.call_nexus_service(...)Keyword target_namespace='billing'
Rust SDKAuthored Nexus waits are not publishedTracked in Rust SDK #86. A held draft is not installed support.

Use the embedded example for a Laravel workflow, the PHP SDK service-call example for its straight-line worker API, or the Python workflow reference for generator-based authoring. Discover the connected runtime's advertised service-operation contract before deploying a caller. A client management API alone does not prove workflow-worker support.

The PHP SDK's array option is currently the cross-namespace authoring path. An equivalent typed option is tracked in PHP SDK #193.

Publish the target operation​

The target namespace owns the endpoint, service, operation and handler worker. Create the namespace first and run a worker that registers the target workflow type on its selected queue. The following catalog uses target namespace billing, workflow type billing.invoice and queue billing.

Catalog mutations require an admin credential. The runtime enforces caller authority separately when a leased workflow emits its service command.

curl --fail-with-body -X POST "$DURABLE_WORKFLOW_SERVER_URL/api/service-endpoints" \
-H "Authorization: Bearer $DW_ADMIN_TOKEN" \
-H "X-Namespace: billing" \
-H "X-Durable-Workflow-Control-Plane-Version: 2" \
-H "Content-Type: application/json" \
-d '{"endpoint_name":"billing"}'

curl --fail-with-body -X POST "$DURABLE_WORKFLOW_SERVER_URL/api/service-endpoints/billing/services" \
-H "Authorization: Bearer $DW_ADMIN_TOKEN" \
-H "X-Namespace: billing" \
-H "X-Durable-Workflow-Control-Plane-Version: 2" \
-H "Content-Type: application/json" \
-d '{"service_name":"invoices"}'

curl --fail-with-body -X POST "$DURABLE_WORKFLOW_SERVER_URL/api/service-endpoints/billing/services/invoices/operations" \
-H "Authorization: Bearer $DW_ADMIN_TOKEN" \
-H "X-Namespace: billing" \
-H "X-Durable-Workflow-Control-Plane-Version: 2" \
-H "Content-Type: application/json" \
-d '{"operation_name":"create","operation_mode":"sync","handler_binding_kind":"start_workflow","handler_target_reference":"billing.invoice","handler_binding":{"workflow_type":"billing.invoice","queue":"billing"},"deadline_policy":{"timeout_seconds":30}}'

The catalog names are normalized to lowercase. Endpoint and service records organize the operation; they do not replace its real handler. Keep a compatible worker available in the target namespace. Registration does not prove that work has executed.

Waiting and recorded results​

The PHP SDK defaults to mode_override=sync and wait_for=completed. Python uses mode='sync' and wait_for='completed'. Both suspend until the service reaches a durable terminal outcome and replay that recorded result or typed failure at the original call. A pending or started call is not a successful business result.

For admission-only behavior, PHP uses wait_for='accepted' or mode_override='async'; Python supports wait_for='accepted' or mode='async' and the start_nexus_operation() helper. Preserve the returned call id for inspection. A later completion does not replace the caller's original recorded admission. Choose completion waiting when workflow decisions need the target result.

Bound service execution with the catalog's deadline_policy, as above. It accepts exactly one positive integer timeout_seconds or an offset-bearing RFC3339 deadline_at. The runtime records one call_deadline_at from original acceptance. Replay, retries and later catalog edits cannot reset it. A null policy does not create a new service deadline.

Do not use the caller's wait_timeout_seconds as an execution timeout. A one-second waiting option with no catalog deadline can still deliver a target that completes after three seconds. Inspect the original call and linked target after a timeout; expiration does not prove that external effects did not happen, and target cancellation is a separate policy.

Failures, retries and idempotency​

PHP exposes terminal service outcomes through ServiceOperationFailed; Python raises NexusOperationFailed at the yield point. Keep typed failure details and the call id when handling an error. Expected business failures may be handled by workflow code; unexpected ones can remain workflow failures.

The runtime owns the recorded service retry policy. Do not add a second HTTP retry loop inside workflow code. PHP and Python derive stable keys for normal workflow calls. If the application supplies a key, keep the same target, request and intended operation for retries and replay. A new key is a new business request, not recovery of a lost acknowledgment.

Keep target namespace, catalog triple, payload and waiting options stable for open histories. Use a workflow version branch when that durable intent changes.

Enforce the namespace boundary​

The cross-namespace default is allow. Authentication, role checks, other boundary policies and namespace authorization still apply. Caller identity comes from the leased workflow; application input cannot impersonate a different caller namespace.

For a self-hosted PHP Server that should refuse cross-namespace service calls, configure and restart its API/worker roles consistently:

DW_SERVICE_BOUNDARY_CROSS_NAMESPACE_DEFAULT=deny

The policy refuses the cross-namespace call before handler dispatch. Same- namespace calls and other boundary policies still need their own review. Do not treat this variable as a replacement for credentials or tenant ACLs.

Inspect the original call​

Use the service-call detail and catalog API or CLI service-call inspection. Keep the caller workflow/run and target service-call ids together. Read-only inspection does not request another execution. Reuse the original id when checking a lost response or worker replacement.