Skip to content

💥 Standalone Activities for Ruby#443

Open
GregoryTravis wants to merge 46 commits into
mainfrom
gmt/ruby-standalone-activities
Open

💥 Standalone Activities for Ruby#443
GregoryTravis wants to merge 46 commits into
mainfrom
gmt/ruby-standalone-activities

Conversation

@GregoryTravis
Copy link
Copy Markdown
Contributor

Add standalone activity API (Client#start_activity + friends)

Introduces support for standalone activities — activities that execute independently of any workflow.

💥 Breaking Changes:

  • Activity::Info#workflow_id, #workflow_run_id, #workflow_type are now nullable (String?); they return nil when the activity is standalone.
  • Activity::Info#workflow_namespace is now marked @deprecated in favor of the new #namespace accessor. Both fields always carry the same value regardless of standalone-vs-workflow context; the rename matches the cross-SDK convention.

New public API surface:

  • Client#start_activity / Client#execute_activity — start a standalone activity execution by name, class, instance, or Activity::Definition::Info; execute_activity is start_activity + handle.result as a shortcut
  • Client#activity_handle(activity_id, activity_run_id:, result_hint:) — get a handle to an existing standalone activity (e.g. one started by a different process)
  • Client#list_activities(query) / Client#count_activities(query) — visibility queries over standalone activities; list_activities returns an Enumerator<ActivityExecution>
  • Client::ActivityHandle — handle returned by start_activity / activity_handle; provides #result(result_hint:, rpc_options:), #describe, #cancel(reason), #terminate(reason)
  • Client::ActivityExecution — lightweight metadata used in list_activities results
  • Client::ActivityExecution::Description — rich descriptor returned by ActivityHandle#describe; subclass of ActivityExecution. Exposes ~25 fields from the proto (status, attempt, retry_policy, last_failure, last_heartbeat_time, priority, canceled_reason, static_summary/static_details, etc.)
  • Client::ActivityExecutionCount + ::AggregationGroup — result of count_activities(), with group-by support
  • Client::ActivityExecutionStatus — enum module wrapping the proto's status values
  • ActivityIDReusePolicy + ActivityIDConflictPolicy — enum modules in lib/temporalio/common_enums.rb mirroring the workflow-side policies
  • Error::ActivityAlreadyStartedError (in error/failure.rb) — raised by start_activity when the server rejects a duplicate ID
  • Error::ActivityFailedError (in error.rb) — raised by handle.result when the activity terminates in failure; cause carries the underlying failure (ApplicationError, CanceledError, TimeoutError, TerminatedError)

New interceptor API:

  • Client::Interceptor::StartActivityInput, DescribeActivityInput, CancelActivityInput, TerminateActivityInput, ListActivitiesInput, CountActivitiesInput, FetchActivityOutcomeInput — seven new Data.define input types
  • Client::Interceptor::Outbound extended with seven matching pass-through methods (start_activity, describe_activity, cancel_activity, terminate_activity, list_activities, count_activities, fetch_activity_outcome)
  • Chain ordering convention is first-added-is-outermost.

Activity::Info additions:

  • #activity_run_id — run-scoped id assigned by the server to each standalone activity execution (nil for workflow-dispatched)
  • #namespace — canonical namespace accessor (preferred over the deprecated #workflow_namespace)
  • #in_workflow? — predicate distinguishing workflow-dispatched activities from standalone ones

Client#async_activity_handle extension (no new method):

  • Now accepts an ActivityIDReference constructed via ActivityIDReference.for_standalone(activity_id:, activity_run_id:).

ActivityIDReference additions:

  • ActivityIDReference.for_standalone(activity_id:, activity_run_id: nil) — class factory for the new standalone shape.
  • #standalone? — predicate for branching in async-completion routing

CI / dev server:

  • Enable standalone-activity server feature flags in the Temporal CLI dev server used by unit tests: frontend.activityAPIsEnabled, activity.enableStandalone, history.enableChasm, history.enableTransitionHistory (added to test/test.rb).
  • Bumped sdk-core submodule to a22517e4 (Rust crate 0.4.0) to pick up the worker-side coresdk.activity_task.Start.run_id proto field used to plumb activity_run_id into Activity::Info.

Tests:

  • test/client_activity_test.rb (28 tests) — integration tests covering the full lifecycle: start, execute, by-name, already-started, timeouts, failure-throws-ActivityFailedError, describe (running / terminated / canceled / retried / raw-info parity), state-transition-count, terminate-result-throws, polling-correctness (with timing assertions across one server long-poll deadline boundary), list, count, get-handle-with-nil-run-id, cancel-transitions-to-CANCELED, retry-policy / priority / id-reuse / id-conflict-policy round trips
  • test/client_activity_async_completion_test.rb (6 tests) — async completion via AsyncActivityHandle against a standalone ActivityIDReference: complete by activity_id, complete with run_id, heartbeat, fail, heartbeat+fail, report_cancellation
  • test/client_activity_hints_test.rb (6 tests) — definition hints used for client-side arg encode and worker-side decode, definition result_hint used for worker-side encode and client-side decode, call-site overrides on start_activity, by-name activities produce nil hints, handle.result(result_hint:) overrides, activity_handle(id, result_hint:) constructor-time hint, AsyncActivityHandle#complete(result, result_hint:) propagates the hint
  • test/client_activity_interceptor_chain_test.rb (5 tests) — multi-interceptor ordering for activity client calls; verifies first-added-is-outermost
  • test/client_workflow_interceptor_chain_test.rb (2 tests) — mirror of the above for the workflow client chain
  • test/activity_info_standalone_test.rb (2 tests) — Activity::Info standalone-vs-workflow fields asserted from inside the activity body
  • test/client/activity_id_reference_test.rb (5 tests) — factory shapes, for_standalone correctness, both forms coexist independently

Documentation:

  • README.md — added "Standalone Activities" subsection under "Activities" with five runnable code examples (start, execute, get-handle + describe/result/cancel/terminate, list/count, in-activity-body context introspection)

…ds, and new nullability for old fields, and a factory for creating the standalone version.

Enums for activity id reuse and conflict policies.
SAA-specific errors.
…e polling.

Add test_async_completion_heartbeat_and_fail_standalone in addition to previous heartbeat test.
Don’t use queue mechanism for gathering id info when it can be gotten directly.
Consistency of parameter names and ordering.
Add static_details.
Use constant for “activity:” prefix.
@GregoryTravis GregoryTravis requested a review from a team as a code owner May 19, 2026 21:15
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds first-class support for standalone activities (activities started directly from a Temporalio::Client, not from within a workflow), including lifecycle operations (start/execute/result/describe/cancel/terminate), visibility querying, interceptor hooks, and updated activity context metadata to distinguish workflow-scheduled vs standalone execution.

Changes:

  • Introduces Client#start_activity / #execute_activity, Client::ActivityHandle, visibility APIs (list_activities / count_activities), and supporting data types/enums/errors.
  • Extends async-activity completion to accept standalone-form ActivityIDReference, and updates Activity::Info to surface standalone-vs-workflow metadata (with workflow fields becoming nullable).
  • Bumps embedded core (Rust crates) and refreshes generated API/proto bindings; adds extensive integration tests and README documentation.

Reviewed changes

Copilot reviewed 57 out of 62 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
temporalio/test/worker_workflow_test.rb Adjusts test to account for nullable workflow_id in Activity::Info.
temporalio/test/worker_activity_test.rb Adjusts tests to account for nullable workflow_id in Activity::Info.
temporalio/test/test.rb Enables dev-server dynamic config flags needed for standalone activities in tests.
temporalio/test/sig/client_workflow_interceptor_chain_test.rbs RBS for new workflow interceptor chain ordering tests.
temporalio/test/sig/client_activity_test.rbs RBS for standalone activity client tests.
temporalio/test/sig/client_activity_interceptor_chain_test.rbs RBS for standalone activity interceptor chain ordering tests.
temporalio/test/sig/client_activity_hints_test.rbs RBS for standalone activity hint propagation tests.
temporalio/test/sig/client_activity_async_completion_test.rbs RBS for standalone activity async-completion tests.
temporalio/test/sig/activity_info_standalone_test.rbs RBS for Activity::Info standalone-vs-workflow tests.
temporalio/test/client/activity_id_reference_test.rb Tests new standalone form of ActivityIDReference.
temporalio/test/client_workflow_interceptor_chain_test.rb Verifies workflow interceptor ordering convention.
temporalio/test/client_activity_test.rb End-to-end tests for standalone activity lifecycle + visibility + policies.
temporalio/test/client_activity_interceptor_chain_test.rb Verifies standalone activity interceptor coverage and ordering.
temporalio/test/client_activity_hints_test.rb Verifies arg/result hint propagation across standalone + async-completion paths.
temporalio/test/client_activity_async_completion_test.rb Tests async completion (complete/heartbeat/fail/cancel) for standalone activities.
temporalio/test/activity_info_standalone_test.rb Validates Activity::Info fields inside activity body for standalone vs workflow execution.
temporalio/sig/temporalio/internal/client/implementation.rbs Adds typing for new internal constant used by standalone async completion routing.
temporalio/sig/temporalio/error/failure.rbs Adds RBS for ActivityAlreadyStartedError.
temporalio/sig/temporalio/error.rbs Adds RBS for ActivityFailedError.
temporalio/sig/temporalio/common_enums.rbs Adds RBS for ActivityIDReusePolicy and ActivityIDConflictPolicy.
temporalio/sig/temporalio/client/interceptor.rbs Adds RBS for new activity-related interceptor inputs and outbound hooks.
temporalio/sig/temporalio/client/connection/cloud_service.rbs Adds RBS for new CloudService custom role RPCs.
temporalio/sig/temporalio/client/activity_id_reference.rbs Updates RBS for standalone-form fields and constructors.
temporalio/sig/temporalio/client/activity_handle.rbs Adds RBS for new Client::ActivityHandle.
temporalio/sig/temporalio/client/activity_execution.rbs Adds RBS for ActivityExecution and rich Description.
temporalio/sig/temporalio/client/activity_execution_status.rbs Adds RBS for standalone activity execution status enum wrapper.
temporalio/sig/temporalio/client/activity_execution_count.rbs Adds RBS for activity count + group-by result type.
temporalio/sig/temporalio/client.rbs Adds RBS for new public standalone activity APIs.
temporalio/sig/temporalio/api/payload_visitor.rbs Updates visitor signatures for new CloudService responses.
temporalio/sig/temporalio/api/cloud/namespace/v1/message.rbs Updates generated Cloud namespace message typings.
temporalio/sig/temporalio/api/cloud/identity/v1/message.rbs Updates generated Cloud identity message typings (custom roles).
temporalio/sig/temporalio/api/cloud/cloudservice/v1/request_response.rbs Updates generated CloudService request/response typings.
temporalio/sig/temporalio/activity/info.rbs Updates Activity::Info typing (new fields + workflow fields now nullable).
temporalio/sig/temporalio/activity/definition.rbs Adds RBS for _type_and_hints_from_parameter helper used by client APIs.
temporalio/lib/temporalio/testing/activity_environment.rb Updates test activity environment default Activity::Info construction for new required fields.
temporalio/lib/temporalio/internal/worker/activity_worker.rb Populates new Activity::Info fields (standalone-aware) when executing activities.
temporalio/lib/temporalio/internal/client/implementation.rb Implements standalone activity RPCs + visibility + long-poll outcome fetch + async completion routing changes.
temporalio/lib/temporalio/error/failure.rb Adds Error::ActivityAlreadyStartedError.
temporalio/lib/temporalio/error.rb Adds Error::ActivityFailedError.
temporalio/lib/temporalio/common_enums.rb Adds standalone activity ID reuse/conflict policy enums.
temporalio/lib/temporalio/client/interceptor.rb Adds activity interceptor inputs and outbound methods.
temporalio/lib/temporalio/client/connection/cloud_service.rb Adds CloudService custom role RPC wrappers.
temporalio/lib/temporalio/client/activity_id_reference.rb Extends ActivityIDReference with standalone form + predicate.
temporalio/lib/temporalio/client/activity_handle.rb Adds Client::ActivityHandle (result/describe/cancel/terminate).
temporalio/lib/temporalio/client/activity_execution.rb Adds ActivityExecution + Description wrappers for list/describe results.
temporalio/lib/temporalio/client/activity_execution_status.rb Adds standalone activity execution status constants.
temporalio/lib/temporalio/client/activity_execution_count.rb Adds count result type with aggregation groups.
temporalio/lib/temporalio/client.rb Adds activity_handle, start_activity, execute_activity, list_activities, count_activities public APIs.
temporalio/lib/temporalio/api/payload_visitor.rb Updates payload visitor for new CloudService custom role responses.
temporalio/lib/temporalio/api/cloud/namespace/v1/message.rb Updates generated Cloud namespace protobuf Ruby bindings.
temporalio/lib/temporalio/api/cloud/identity/v1/message.rb Updates generated Cloud identity protobuf Ruby bindings.
temporalio/lib/temporalio/api/cloud/cloudservice/v1/request_response.rb Updates generated CloudService protobuf Ruby bindings.
temporalio/ext/src/client_rpc_generated.rs Adds dispatch entries for CloudService custom role RPCs.
temporalio/ext/Cargo.toml Bumps embedded Rust crates to 0.4.0.
temporalio/Cargo.lock Lockfile updates for Rust dependency bump.
README.md Documents standalone activity usage with runnable examples.
CHANGELOG.md Adds changelog entry for standalone activities.
.gitattributes Adds union merge driver config for CHANGELOG.md.
Files not reviewed (4)
  • temporalio/lib/temporalio/api/cloud/cloudservice/v1/request_response.rb: Language not supported
  • temporalio/lib/temporalio/api/cloud/cloudservice/v1/service.rb: Language not supported
  • temporalio/lib/temporalio/api/cloud/identity/v1/message.rb: Language not supported
  • temporalio/lib/temporalio/api/cloud/namespace/v1/message.rb: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread README.md
Comment thread temporalio/lib/temporalio/client/interceptor.rb Outdated
Comment thread temporalio/lib/temporalio/internal/worker/activity_worker.rb Outdated
Copy link
Copy Markdown
Member

@chris-olszewski chris-olszewski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks very good! Mostly minor things and a few areas that I think could use more tests.

Comment thread temporalio/lib/temporalio/activity/info.rb Outdated
Comment thread temporalio/lib/temporalio/activity/info.rb Outdated
Comment thread temporalio/lib/temporalio/activity/info.rb Outdated
Comment thread temporalio/lib/temporalio/client/activity_execution.rb Outdated
Comment thread temporalio/lib/temporalio/client/activity_handle.rb Outdated
Comment thread README.md
Comment thread README.md
Comment thread temporalio/test/client_activity_test.rb
Comment thread temporalio/test/client_activity_interceptor_chain_test.rb
Comment thread temporalio/test/client_activity_test.rb
Copy link
Copy Markdown
Contributor

@maciejdudko maciejdudko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! I left a bunch of comments, most of them are about docstrings or about nullability of some fields, but there's a couple of functional ones. The biggest is lack of input validation in start_activity.

Comment thread temporalio/lib/temporalio/activity/info.rb Outdated
Comment thread temporalio/lib/temporalio/activity/info.rb
Comment thread temporalio/lib/temporalio/internal/worker/activity_worker.rb Outdated
Comment thread temporalio/lib/temporalio/client/activity_handle.rb Outdated
Comment thread temporalio/lib/temporalio/client/activity_execution.rb Outdated
Comment thread temporalio/sig/temporalio/activity/info.rbs Outdated
Comment thread README.md
Comment thread CHANGELOG.md
Comment on lines +7 to +17
- 💥 Standalone Activities: activities that execute independently of any workflow.
```
handle = client.start_activity(
MyActivity,
'some-arg',
id: 'my-activity-id',
task_queue: 'my-task-queue',
start_to_close_timeout: 60
)
result = handle.result # blocks until the activity completes
``` No newline at end of file
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually we don't do code snippets in release notes. There should be 1-2 paragraphs of feature description and a link to documentation. Breaking changes should be described in a separate section. See release notes in other SDKs.

Comment thread temporalio/test/test.rb
Comment on lines -157 to +161
# Enable activity pause
'--dynamic-config-value', 'frontend.activityAPIsEnabled=true'
# Activity pause + Standalone Activities
'--dynamic-config-value', 'frontend.activityAPIsEnabled=true',
'--dynamic-config-value', 'activity.enableStandalone=true',
'--dynamic-config-value', 'history.enableChasm=true',
'--dynamic-config-value', 'history.enableTransitionHistory=true'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These flags are not needed anymore (as of CLI 1.7.0).

nil
end

def start_activity(input)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is missing input validation.

  • activity_id not empty
  • task_queue not empty
  • either schedule_to_close_timeout or start_to_close_timeout is set

Copy link
Copy Markdown
Member

@chris-olszewski chris-olszewski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with all of @maciejdudko's suggestions, but this seems to be in a good state.

module Temporalio
class Client
class ActivityExecution
attr_reader raw_info: untyped
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we have RBS types for our protobuf messages we can probably remove these untyped usages and use those types directly.

(I realize this is semi-standard right now as our RBS files were written before we has these types, but I am working on cleaning that up as part of our RBS/RBI type publishing story. This would just be one less thing to clean up)

def execution_duration: -> Float?

class Description < ActivityExecution
attr_reader raw_description: untyped
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

GregoryTravis and others added 2 commits June 1, 2026 15:25
Co-authored-by: Maciej Dudkowski <maciej.dudkowski@temporal.io>
Co-authored-by: Maciej Dudkowski <maciej.dudkowski@temporal.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants