Standalone Activities operator commands and optional payloads - #1809
Draft
GregoryTravis wants to merge 54 commits into
Draft
Standalone Activities operator commands and optional payloads#1809GregoryTravis wants to merge 54 commits into
GregoryTravis wants to merge 54 commits into
Conversation
DescribeActivityExecutionRequest gates four payload-bearing fields behind per-call flags (api#792): include_input, include_outcome, include_heartbeat_details and include_last_failure. This carries them through DescribeActivityInput and onto the request. The public ActivityHandle.describe signature is unchanged and passes all-false, so no caller-visible behavior changes yet; the next step exposes them and flips heartbeat details and last failure to opt-in.
Adds include_input, include_outcome, include_heartbeat_details and include_last_failure to ActivityHandle.describe, all defaulting to false to match Rust's ActivityDescribeOptions. This is a behavior change: heartbeat details and the last failure were previously returned unconditionally and must now be requested. The proto's rationale is that these fields carry arbitrarily large payloads and should not be fetched unless needed.
The describe response carries input and outcome at the response level rather than inside info, so _from_execution_info now takes both and the impl passes them through. Adds raw_input/input, raw_outcome/result/failure and the has_input/has_result predicates. Decoding happens at construction, matching how last_failure and the heartbeat details are already handled; the raw fields remain available for callers that need type hints to convert correctly. failure is the terminal outcome and is deliberately distinct from last_failure, which is the most recent attempt's failure and may be set while the activity is still retrying.
The server is expected to omit them, but an older or buggy server may return them anyway, which would make the description's has_input/has_result and the heartbeat/last-failure fields disagree with what the describe call asked for.
Pause holds a running activity; unpause resumes it, optionally starting at a random point within a jitter window rather than immediately. Both commands follow the existing cancel/terminate shape: an interceptor input, an OutboundInterceptor method, the impl building the request, the handle method, and the public exports. They are added together because they are symmetric and share test scaffolding.
Reset rewinds the activity to its first attempt and resets its timeouts. keep_paused leaves a paused activity paused, restore_original_options reverts the options to the ones the activity was created with, and reset_heartbeat additionally discards persisted heartbeat details. reset_heartbeat is opt-in because the server no longer clears heartbeat details by default (api#848 with server temporal#11417).
update_options takes a sequence of ActivityOptionsUpdate values built from ActivityOptionsKeys. An option named by an update is changed, an option not named is left untouched, and value_unset() clears one — the three states the field mask needs. This follows SearchAttributeKey.value_set/value_unset in temporalio.common, which is the SDK's existing precedent for the same problem. restore_original is a separate method rather than a field because the proto states the flag cannot be combined with any other option; a request that does so is rejected by the server, so the options object should not be able to express it. Both return ActivityExecutionOptions, the server's resolved view after the call.
api#834 added ACTIVITY_EXECUTION_STATUS_PAUSED to the overall activity status. A running activity that is paused reports PAUSE_REQUESTED as its run state and only reaches PAUSED once the worker drops the attempt; an activity paused while still scheduled reaches PAUSED directly.
The describe result carries schedule_to_close, schedule_to_start, start_to_close and heartbeat timeouts, plus start_delay and execution_time, but Python surfaced none of them. Ruby and Java both expose all six, and asserting that an options update actually applied server-side needs them. execution_time is scheduled_time plus start_delay, and equals scheduled_time when no start delay is set (api#807).
Ports the Ruby/Java operator-command suite: pause, unpause, reset and update_options, each asserting an observable server-side state change rather than a successful RPC. Also covers heartbeat preservation per command, describe reporting PAUSED, update-options on a paused activity, both arms of the outcome oneof, and that the four describe payload fields really are opt-in.
Documents the new ActivityHandle operator commands and the opt-in describe payload fields under the unreleased Added heading.
Identity, the per-call request ID, the reason and the jitter cannot be checked by observing activity state, so cover them with unit tests that capture each outgoing request against a stubbed service. Also pins that an omitted jitter is left off the wire rather than sent as an explicit zero duration, that the reset flags default off, and that the update mask names only the options that were changed. Mirrors the Ruby suite's unobservable-request-fields test.
Asserts that each handle method routes through its own outbound interceptor method carrying the caller's arguments, and that restore_original_options shares the update interceptor, distinguished by the restore_original flag and an empty set of updates. Runs against a stubbed service rather than a live server: the routing does not depend on server behavior, so this stays green in CI while the functional suite waits on a CLI release.
Covers the two things a live server cannot demonstrate on its own: that the flags the caller set reach the request, and that payloads a server returns without being asked are dropped client-side so the has_* accessors always agree with the request. Matches the coverage the Go port already had.
An empty update would send an empty field mask and silently change nothing. Raise ValueError before the round trip instead, pointing at restore_original_options for reverting. Matches Ruby, which raises ArgumentError, and Go, which returns an error.
The public handle methods never build that combination, but an interceptor can, and the impl silently dropped the updates rather than refusing them. The server rejects it, so raise before the round trip. Matches the guard the Go port already had.
_from_execution_info took info plus five more arguments, four of which the single caller read straight off the response it was already holding. Take the response instead and derive them, leaving only namespace and the data converter, which come from client state rather than the wire. Renamed to _from_describe_response, since it no longer takes an ActivityExecutionInfo. This also matches the Ruby, Java and Go ports, which all hold the whole response rather than reaching into it.
Clearing is the third state of the partial update: the path is named in the field mask so the server acts on it, while the proto field is left unset so the value is removed rather than set. Nothing exercised it. Brings Python level with Go, which already tested the equivalent zero-valued change.
Ported from sdk-python#1782, written against our current API shape. Replaces test_describe_payload_fields_are_opt_in, test_describe_input_and_result_are_opt_in and test_describe_outcome_failure with a single test covering all three, and covering more: - one activity that heartbeats, fails once then succeeds, so a single describe carries input, result, heartbeat details and a last failure at the same time. The three tests it replaces each used a different purpose-built activity, so no describe ever held them together. - pins last_failure present while failure (the terminal outcome) is None on a succeeded activity that failed once. That distinction between GetFailure and GetLastFailure was untested anywhere. - asserts the decoded accessors are empty, not merely that has_* is false.
GregoryTravis
marked this pull request as draft
September 2, 2026 17:13
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add operator commands for standalone activities
Adds pause, unpause, reset, and update-options to standalone activities, plus the
describe surface needed to observe their effects.
API
On
ActivityHandle:Describe payloads
ActivityHandle.describegains four opt-in keyword arguments:include_input,include_outcome,include_heartbeat_details,include_last_failure.Description fields
Expose additional existing
ActivityExecutionDescriptionfields:execution_time,start_delay,total_heartbeat_count,schedule_to_close_timeout,schedule_to_start_timeout,start_to_close_timeout,heartbeat_timeout.