github temporalio/temporal v1.32.0

4 hours ago

Standalone Activities GA

Standalone Activities and start delay are now enabled by default via activity.enableStandalone and activity.startDelayEnabled. This release adds delayed starts, operator APIs, and batch operations.

Operator APIs can pause or resume an Activity, reset its attempt state with optional heartbeat cleanup or option restoration, and update selected options or restore the original options. Enable them with history.enableStandaloneActivityOperatorCommands: true.

Batch operations can cancel, terminate, or delete multiple Standalone Activities selected by a visibility query or explicit Activity executions. Enable them with frontend.enableBatchOperationsForStandaloneActivities: true. Both settings are namespace-scoped and default to false.

Compatibility note: Describe/List batch responses now use explicit *_WORKFLOW enum values for existing Workflow batch operations. Clients comparing these values with the deprecated enum values must be updated.

Visibility

Query Converter

⚠️ Behavioral and Configuration Changes

This release enables the unified query converter introduced in Temporal v1.30.1. It is now the default and includes breaking changes. You can still disable it by setting the dynamic config system.visibilityEnableUnifiedQueryConverter: false. We plan to remove the legacy query converter in the next release. See below for details.

Summary

The existing (now legacy) query converter implementation was fragmented, with separate versions for Elasticsearch and SQL Visibility stores. This created an inconsistent experience depending on which store you used (for example, a query might be accepted by one store but rejected by another).

Temporal v1.30.1 introduced a unified query converter to consolidate these implementations and provide consistent behavior across all Visibility stores. However, achieving that consistency required a couple of breaking changes:

  • Type validation: Comparing a search attribute of type T with a value of type S where S differs from T returns an error (eg: CustomKeyword = 123 and CustomInt = '123' will return an error). In the legacy query converter, some of these expressions were accepted and relied on the underlying store to accept or reject them. One exception is numeric types (Int and Double): for those, CustomInt = 1.5 and CustomDouble = 15 are allowed.
  • Filtering a Text type search attribute against an empty string (or equivalent) returns an error (eg: CustomText = '' and CustomText = ' ' will return an error). The Text type search attribute is meant for full-text search, so you must provide at least one token. In the legacy query converter, these expressions were accepted or rejected depending on the store you chose. However, even when accepted, they match no workflows, which indicates they weren’t intended.

Other Changes

  • The type of VisibilityRow.ExecutionDuration has changed from *time.Duration to *int64. The type VisibilityRow is meant to represent a row from the SQL table executions_visibility, which defines the execution_duration column as BIGINT. Given that the underlying database driver has the freedom to translate Go types at will, using time.Duration instead of int64 can introduce a bug (eg: it might be translated into a string representation of time.Duration). If you have your own implementation of the VisibilityStore interface that depends on VisibilityRow, you will have to review your code and make the necessary changes.
  • When using Visibility with Elasticsearch, we changed the search query to disallow partial search results, i.e., we are now setting the query parameter allow_partial_search_results=false. This ensures that ListWorkflowExecutions (and related APIs) won’t miss results and that paginating through the results will go over all matching executions.
  • Search-attribute-related APIs in the admin handler (eg: AddSearchAttributes) have been deprecated and now forward to the operator handler. If you were using the deprecated tctl CLI tool to manage search attributes, it was calling the admin handler. Please replace it with the temporal CLI (docs).

Upcoming changes in v1.33.0

  • Removing support for the legacy query converter.
  • The default value of system.visibilityAllowList will change to false. This might cause issues if you are using Elasticsearch as the Visibility store and assigning a list of values to a custom search attribute that does not support lists of values (all types except KeywordList type). Eg: if you are storing ["foo", "bar"] in a Keyword type custom search attribute, or if you are storing [1, 2, 3] in an Int type custom search attribute. Although it currently works, this is unexpected behavior that Elasticsearch happens to accept (see these release notes for additional details). We highly recommend that you fix your usages of lists of values, as they are not officially supported.

Activity Eager Execution

⚠️ Behavioral and Configuration Changes

system.enableActivityEagerExecution is now enabled by default.

Worker Versioning

⚠️💥 Final release before removal of deprecated Worker Versioning

Correction to the v1.31 release notes: v1.32 is the final release containing the deprecated Worker Versioning implementations. Complete removal is now planned for server v1.33, rather than v1.32.

The following deprecated APIs are planned for removal in v1.33:

  • UpdateWorkerBuildIdCompatibility
  • GetWorkerBuildIdCompatibility
  • UpdateWorkerVersioningRules
  • GetWorkerVersioningRules
  • GetWorkerTaskReachability
  • SetCurrentDeployment
  • GetCurrentDeployment
  • DescribeDeployment
  • ListDeployments
  • GetDeploymentReachability

The Build ID Compatibility and Versioning Rules management APIs remain disabled by default in v1.32. Existing legacy routing information and workflow-progress behavior remain available so operators can complete their migration. The deprecated pre-release Deployment APIs already return Unimplemented.

Before upgrading to v1.33, migrate to the current Worker Deployment APIs and drain or complete every running, sleeping, or backlogged Workflow that still depends on a legacy Build ID, compatible Version Set, Assignment Rule, Redirect Rule, or deprecated Deployment routing state.

Rolling back to v1.32 should be treated as emergency recovery for a missed legacy Workflow, not as a migration strategy.

New Capabilities

  • Added one-time Versioning Overrides, allowing a Workflow to route to a specified Worker Deployment Version without creating a permanent Pinned override. The override is automatically cleared after a Workflow Task successfully completes on the target Version.
  • Added explicit Versioning Overrides when starting Child Workflows, allowing a Child Workflow to use Pinned, Auto-Upgrade, or one-time routing independently of its parent.

⚠️ Behavioral and Configuration Changes

  • Version reactivation signals are now enabled by default. A Workflow pinned or moved to a Drained or Inactive Worker Deployment Version can reactivate that Version’s drainage state. This can be disabled with history.enableVersionReactivationSignals.
  • The signal-based Worker Deployment Version demotion path is controlled by the new matching.enableWorkerDeploymentVersionDemotionSignal dynamic config. It defaults to false in v1.32, retaining the existing update-based behavior during upgrades. Enabling it before older Version workflows have Continued-As-New can leave Versions stuck in Draining. The default is expected to change to true in v1.33.

Other Changes

  • Added the worker_deployment_versioning_one_time_override_count metric to track fulfilled one-time Versioning Overrides.
  • Reduced unnecessary version-reactivation signals by skipping active Versions and deduplicating signals using routing revisions.
  • Fixed current and ramping Version selection when a Task Queue moves between Worker Deployments.
  • ListWorkerDeployments now removes duplicate Deployments appearing within a single Visibility result page.
  • Deleting an already-missing Worker Deployment Version now succeeds and removes its stale reference from the Worker Deployment.
  • Worker Deployment system workflows are now assigned higher priority, reducing delays when their per-Namespace Task Queue is busy.

Poller Autoscaling

New Capabilities

  • Export new metric for debugging poller autoscaling behavior (PR)
    • We add an opt-in poller_scale_decision counter metric. If enabled, we emit a metric for each poller scaling decision made by the server (+1s, -1s, or no change), tagged with the reason.
    • This metric is off by default, and can be enabled for a namespace or a task queue.
  • Support enabling poller autoscaling per namespace from the server side (PR)
    • We add a new namespace-level dynamic config (frontend.pollerAutoscalingAutoEnroll).
    • Each SDK is then responsible for enabling poller autoscaling if this DC is enabled, and if the user hasn’t configured a specific fixed poller count. This has been implemented in all SDKs.
  • The poller scaling add-to-dispatch ratio threshold is now configurable via the new
    matching.pollerScalingTaskAddToDispatchRatio dynamic config (PR). This is the ratio of task add rate to task dispatch rate above which a scale-up decision
    is issued, previously hard-coded to 1.2. The default is unchanged, so no action is
    needed.

Bug Fixes

  • Fixed a bug where sticky queues could only issue poller scale-up decisions once a backlog
    had formed, never on add/dispatch rate alone (PR).

Matching Observability Improvements

Matching now exposes a per-task-queue view of task arrival and silent drops, and removes the coarser tasks_expired counter that the new metrics replace.

New Capabilities

  • tasks_added - counts tasks arriving at a task queue. Tagged with:
    • task_add_result: (sync_match, sync_match_unavailable, backlog, throttled, failure)
    • forwarded: whether the add task was forwarded from a child partition.
  • tasks_dropped - counts backlog task-matching drops. Sync-match tasks are not counted. Tagged with:
    • reason: (internal_error, data_loss, not_found, invalid, expired_read, expired_memory)

⚠️ Behavioral Changes

  • The tasks_expired metric has been removed; it is superseded by the tasks_dropped metric with finer reason tags. Alerts on the tasks_expired metric will need to be updated.

Task Queue Dynamic Partitioning

Task Queue Dynamic Partitioning can adjust the number of read and write partitions for selected Workflow and Activity Task Queues based on task-add rate and backlog. This is an experimental operator capability that is disabled by default: matching.partitionScaler has no default policy.

Cluster operators can scope matching.partitionScaler and matching.partitionScaleManager by Namespace, Task Queue, and Task Queue type. The scale manager has defaults, but both manager and scaler settings should be validated together for each workload; no general-purpose production configuration is currently recommended. Refer to the Go documentation for SimplePartitionScalerSettings and PartitionScaleManagerSettings. ShadowModeLogInterval can be used to observe scaler decisions without applying them.

⚠️ Behavioral and Observability Changes

  • When an active scaler policy sets a positive BacklogCap, matching clients use backlog-aware load balancing: add-task routing favors partitions farther below the cap, while poll routing favors partitions with more backlog. When BacklogCap is not set, the existing load-balancing behavior is retained.
  • Added the partition_scale_events counter (tagged with scaler_shadow_mode) and the per-Task Queue gauges partition_scale_target, partition_scale_read, and partition_scale_write. The gauges also report changed shadow-mode recommendations when metrics.breakdownByTaskQueue is enabled.

Priority and Fairness

New Capabilities

  • New Dynamic Config option matching.fairnessPassDither: This spreads low-weight keys ahead in pass-space so they don't clump at the front after a counter reset (e.g. partition
    movement), at the cost of cross-key FIFO ordering for bursts of equal-weight new keys.

⚠️ Behavioral and Configuration Changes

  • matching.enableMigration dynamic config has been removed and is always enabled now.

Bug Fixes

  • Fixed several issues in the fair task reader that could cause a task queue partition to get stuck with a non-empty backlog, including read/ack levels moving backwards under load, backlog counts not resetting after a gap, and evicted-ack cache handling. Fairness backlogs now drain reliably under these conditions.
  • Per-Key rate limit no longer blocks lower-priority tasks from being dispatched.

Worker Management

New Capabilities

  • Proactive Activity Cancellation — The server can now cancel activities on workers without relying on heartbeats, using a Nexus-based worker commands channel. On workflow close (terminate, timeout, cancel, continue-as-new), cancel commands are dispatched for all in-flight activities. This does not yet support standalone activities.

    Dynamic config:

    • frontend.WorkerCommandsEnabled (namespace-scoped, default: off) — enables the worker commands task queue and polls
    • system.enableCancelActivityWorkerCommand (namespace-scoped, default: off) — enables the server to send a cancel command to the worker
  • CountWorkers API — New RPC to count workers matching a query filter without retrieving full details.

⚠️ Behavioral and Configuration Changes

  • System workers excluded from ListWorkers by default — opt in via include_system_workers on the request.

Nexus

⚠️ Behavioral and Configuration Changes

  • 💥 Nexus callbacks now route by URL scheme by default: worker targets always use temporal://system, while legacy Nexus-Callback-Source header-based routing is available only when callback.inspectSourceHeader is enabled. The obsolete nexusoperation.useSystemCallbackURL and component.nexusoperations.useSystemCallbackURL settings have been removed.
    This change has been introduced for security reasons. Previously, if useSystemCallbackURL was false (default was true), a callback to an external target URL, allowed in callback.allowedAddresses, and having the source header set, would have been executed as an internal request.
  • The default Nexus callback implementation is now backed by the CHASM framework instead of the previous HSM-backed implementation. Configuration knobs for this implementation differ from the previous one. For the most part, configs have been redefined in chasm/lib/callback/config.go. To force using the previous implementation, set history.enableCHASMCallbacks to false.
  • 💥 The dynamic config component.callbacks.allowedAddresses has been replaced with callback.allowedAddresses.

Workflow task completion pagination

Workers can now split a single RespondWorkflowTaskCompleted across multiple requests ("pages"), so a workflow task that exceeds the request size limit can still complete. The server buffers the intermediate pages and, when the final page arrives, reassembles them into the original RespondWorkflowTaskCompleted request and processes it as one. DescribeNamespace advertises support through the workflow_task_completion_pagination capability.

This is pre-release and disabled by default. Dynamic configs:

  • history.enableWorkflowTaskCompletionPagination (default false): enables the feature.
  • history.maximumEventBatchSizeInBytes (global, default 0, disabled): size at which the event store rolls the current history event batch and starts a new one. Experimental. This determines how much is written to persistence per batch, and each batch is persisted in a single transaction, so it must be set below system.transactionSizeLimit for pagination to work.
  • history.workflowTaskCompletionBufferSizeLimit (default 40 MiB): buffered size allowed per workflow task. Exceeding it fails the workflow task with cause REQUEST_TOO_LARGE.
  • history.workflowTaskCompletionBufferTotalSizeLimit (default 1 GiB) and history.workflowTaskCompletionBufferNamespaceRatio (default 0.5): process-wide buffer budget and the share one namespace may hold.

Don't miss a new temporal release

NewReleases is sending notifications on new releases.