Skip to content

💥 Standalone activities support#2158

Merged
yuandrew merged 17 commits intomasterfrom
standalone-activities
Feb 10, 2026
Merged

💥 Standalone activities support#2158
yuandrew merged 17 commits intomasterfrom
standalone-activities

Conversation

@maciejdudko
Copy link
Contributor

@maciejdudko maciejdudko commented Jan 21, 2026

What was changed

  • Implemented support for running standalone activity tasks.
  • Client calls for standalone activities: execute, get result, describe, cancel, terminate, list, count.
  • Changes to TestActivityEnvironment to support executing activities as standalone.
  • 💥 activity task tokens for tests have been changed, now testActivityToken (containing activityID and runID) are used to keep track of activities, instead of just activityID. Most users should not be affected, only if you're manually constructing task tokens in your tests.

Checklist

  1. Closes Support standalone activities #2124

  2. How was this tested:

New integration tests: TestIntegrationSuite/TestExecuteActivitySuite

  1. Any docs updates needed?

Standalone activity docs are in progress.

@maciejdudko maciejdudko force-pushed the standalone-activities branch from 44508ad to 0dba4dd Compare January 22, 2026 23:34
@maciejdudko maciejdudko marked this pull request as ready for review January 22, 2026 23:52
@maciejdudko maciejdudko requested a review from a team as a code owner January 22, 2026 23:52
@yuandrew yuandrew force-pushed the standalone-activities branch from 9eb5bc4 to fc21cb8 Compare January 29, 2026 23:29
@yuandrew yuandrew changed the title Standalone activities support 💥 Standalone activities support Jan 30, 2026
Copy link
Contributor

@cretz cretz left a comment

Choose a reason for hiding this comment

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

Looks great, most of my concerns are minor

client/client.go Outdated
// Returns an ActivityExecutionAlreadyStarted error if an activity with the same ID already exists
// in this namespace, unless permitted by the specified ID conflict policy.
//
// ActivityHandle has the following methods:
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure we should have to keep an up-to-date list of the return type's interface methods here

Copy link
Contributor

Choose a reason for hiding this comment

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

We do this for ExecuteWorkflow, I think it's fine to keep it for consistency

Copy link
Contributor

Choose a reason for hiding this comment

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

ActivityHandle is not the same as WorkflowRun. When we add new operations for workflows, we make top-level methods, so we don't have to update that list.

Regardless, this is a perfect example of what I'm talking about. That list is missing GetWithOptions, but I wasn't around to predict that would happen then like I am now :-) It shouldn't be there either.

Copy link
Contributor

Choose a reason for hiding this comment

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

yeah, that's fair, one less thing to keep synchronized, removed

//
// Exposed as: [go.temporal.io/sdk/interceptor.ClientExecuteActivityInput]
type ClientExecuteActivityInput struct {
Options *ClientStartActivityOptions
Copy link
Contributor

Choose a reason for hiding this comment

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

FWIW, it is arguable that this should not be a pointer, but since this matches other interceptor calls, no prob

Comment on lines +474 to +475
if e, ok := err.(*serviceerror.ActivityExecutionAlreadyStarted); ok &&
in.Options.ActivityIDConflictPolicy == enumspb.ACTIVITY_ID_CONFLICT_POLICY_USE_EXISTING {
Copy link
Contributor

Choose a reason for hiding this comment

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

This conditional doesn't really make sense. If the conflict policy was use existing, you'd never get this error. Rather, we need to confirm if, like workflows, we default to already-exists returning the handle (w/ WorkflowExecutionErrorWhenAlreadyStarted-like option to change it) or not.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the callout, added ActivityExecutionErrorWhenAlreadyStarted to mirror workflow

Copy link
Contributor

Choose a reason for hiding this comment

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

This works for me, though may want to confirm with others on the standalone activity team they have no concerns

Copy link
Contributor

Choose a reason for hiding this comment

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

Discussed offline, removing ActivityExecutionErrorWhenAlreadyStarted and instead relying on conflict policy, as that was added into workflows after WorkflowExecutionErrorWhenAlreadyStarted, we're diverging from mirroring workflows, but conflict policy should be sufficient here.

RunId: in.RunID,
}

resp, err := w.client.WorkflowService().PollActivityExecution(grpcCtx, request)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this needs to loop while there is no outcome

Copy link
Contributor

Choose a reason for hiding this comment

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

added a loop for resp.GetOutcome() != nil

…che results, clarify CompleteActivity* docs, add ActivityExecutionErrorWhenAlreadyStarted
Copy link
Contributor

@cretz cretz left a comment

Choose a reason for hiding this comment

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

Looks good, I have nothing blocking

client/client.go Outdated
// Returns an ActivityExecutionAlreadyStarted error if an activity with the same ID already exists
// in this namespace, unless permitted by the specified ID conflict policy.
//
// ActivityHandle has the following methods:
Copy link
Contributor

Choose a reason for hiding this comment

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

ActivityHandle is not the same as WorkflowRun. When we add new operations for workflows, we make top-level methods, so we don't have to update that list.

Regardless, this is a perfect example of what I'm talking about. That list is missing GetWithOptions, but I wasn't around to predict that would happen then like I am now :-) It shouldn't be there either.

// NOTE: Experimental
//
// Exposed as: [go.temporal.io/sdk/client.ListActivitiesResult]
ClientListActivitiesResult struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

Pedantic, but could be called ClientActivityList, but meh, this is fine

Comment on lines +474 to +475
if e, ok := err.(*serviceerror.ActivityExecutionAlreadyStarted); ok &&
in.Options.ActivityIDConflictPolicy == enumspb.ACTIVITY_ID_CONFLICT_POLICY_USE_EXISTING {
Copy link
Contributor

Choose a reason for hiding this comment

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

This works for me, though may want to confirm with others on the standalone activity team they have no concerns

@yuandrew yuandrew merged commit 215920a into master Feb 10, 2026
16 checks passed
@yuandrew yuandrew deleted the standalone-activities branch February 10, 2026 00:18
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.

Support standalone activities

3 participants