Skip to content

Comments

Standalone Activity client#950

Open
dandavison wants to merge 84 commits intotemporalio:mainfrom
dandavison:standalone-activity-client
Open

Standalone Activity client#950
dandavison wants to merge 84 commits intotemporalio:mainfrom
dandavison:standalone-activity-client

Conversation

@dandavison
Copy link
Contributor

@dandavison dandavison commented Feb 23, 2026

What was changed

Implement all Standalone Activity client commands.

$ temporal activity <tab>
cancel          -- Request cancellation of a Standalone Activity (Experimental)
complete        -- Mark an activity as completed successfully with a result
count           -- Count Standalone Activities matching a query (Experimental)
describe        -- Show detailed info for a Standalone Activity (Experimental)
execute         -- Start a new Standalone Activity and wait for its result (Experimental)
fail            -- Mark an Activity as completed unsuccessfully with an error
list            -- List Standalone Activities matching a query (Experimental)
pause           -- Pause an Activity
reset           -- Reset an Activity
result          -- Wait for and output the result of a Standalone Activity (Experimental)
start           -- Start a new Standalone Activity (Experimental)
terminate       -- Forcefully end a Standalone Activity (Experimental)
unpause         -- Unpause an Activity
update-options  -- Change the values of options affecting a running Activity

Why?

Standalone Activity is a new product feature which the CLI will support.

Checklist

  1. How was this tested
  • Integration tests in PR
  • Manual testing against Python worker
  1. Any docs updates needed?
    Yes. https://docs.temporal.io/cli/activity must be updated.

dandavison and others added 30 commits February 23, 2026 11:29
Define 9 new commands under `temporal activity` for standalone
(top-level) Activity Executions: start, execute, describe, list,
count, cancel, terminate, delete, result. Each mirrors the
corresponding `temporal workflow` command pattern.

Modify `complete` and `fail` to make --workflow-id optional so they
work for both workflow-scoped and standalone Activities.

Add two new reusable option sets: `activity-execution-reference`
(activity-id + run-id) and `activity-start` (full set of start
options including timeouts, retry policy, ID policies, search
attributes, headers, metadata, and priority).

All new commands are marked Experimental. Existing workflow-only
commands (pause, unpause, reset, update-options) are unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
- cancel: Rewrite description following Python SDK style, explaining
  that cancellation is a request delivered via heartbeat response
- complete/fail: Use activity-reference option set instead of
  inlining activity-id/run-id, fixing missing short flag and
  missing "latest run" description
- fail: Clarify detail vs reason option descriptions (detail is the
  failure details payload; reason is the failure message)
- count/list: Add example queries, improve query option descriptions,
  add visibility docs links
- delete: Add note about async deletion and running activity
  termination (from proto docs)
- execute: Use "output" instead of "printed"
- update-options/pause/unpause/reset: Add "Not supported for
  standalone Activities" sentence
- Remove resolved TODO comments

Co-authored-by: Cursor <cursoragent@cursor.com>
Phase 2: Run code generation producing command structs for all 9 new
commands and 2 new option sets (ActivityReferenceOptions,
ActivityStartOptions).

Phase 3: Implement run() methods for all new commands:
- start: calls StartActivityExecution, outputs activity ID and run ID
- execute: calls StartActivityExecution + PollActivityExecution,
  outputs the activity result
- describe: calls DescribeActivityExecution with include_input and
  include_outcome
- list: calls ListActivityExecutions with pagination, table output
- count: calls CountActivityExecutions with group support
- cancel: calls RequestCancelActivityExecution
- terminate: calls TerminateActivityExecution with default reason
- delete: calls DeleteActivityExecution
- result: calls PollActivityExecution, outputs the activity result

Shared helper buildStartActivityRequest() constructs the gRPC
request from ActivityStartOptions, handling retry policy, ID
policies, search attributes, headers, user metadata, and priority.

Shared helper printActivityOutcome() formats activity results for
both text and JSON output modes.

Also adds description-header to temporal activity docs (required by
code generator) and fixes import aliasing (common/v1 -> commonpb).

Co-authored-by: Cursor <cursoragent@cursor.com>
Verify all 11 activity subcommands appear in help output (cancel,
complete, count, delete, describe, execute, fail, list, result,
start, terminate).

Verify start command exposes expected flags (activity-id, type,
task-queue, timeouts, input).

Verify complete and fail commands expose both activity-reference
options (activity-id, run-id) and the optional workflow-id flag.

Integration tests for the new RPCs are deferred until a
standalone-activity-enabled test server is available.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Use 'activity complete' as the primary example instead of experimental
  'activity start' command
- Restore original description-header text for docs
- Revert unnecessary commonpb import alias (use default 'common' since no
  conflict exists in this file)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Activity count/list/result: use Standalone Activity Execution wording and
  'only supported for Standalone Activity Execution'
- Activity complete/fail: restore --workflow-id YourWorkflowId in examples
- Keywords: restore 'activity execution'
- Activity count/list: Search Attributes and queries; filter/to be counted
- Workflow execute: add backslash after execute in example
- Workflow start: revert to main (Initiate, Returns Workflow- and Run-IDs)
- Workflow delete: revert to main (Remove, Executions typo); remove TODO
- Workflow result: remove TODO (output verb already used)
- Workflow count/list: example query and remove TODOs; queries. wording
- Restore trailing spaces in worker deployment and task-queue docs to avoid
  whitespace-only diff noise

Co-authored-by: Cursor <cursoragent@cursor.com>
- Activity complete/fail: restore --workflow-id in examples
- Activity count/list: Standalone Activity Execution wording, filter/sentences, Search Attributes and queries
- All standalone-only activity commands: summary + only supported sentence
- Restore activity execution keyword
- Workflow describe: revert summary to Show Workflow Execution info
- Workflow delete: remove TODO
- Workflow terminate: revert summary to Forcefully end a Workflow Execution
- Workflow execute: add backslash after execute in example
- Workflow count/list: remove TODO show an example query
- Workflow result: remove TODO

Co-authored-by: Cursor <cursoragent@cursor.com>
- Revert all trailing-whitespace-only changes in deployment/worker sections
- Restore trailing whitespace on workflow headers line to match main
- Apply workflow start description suggestion (mention workflow execute)
- Apply id-reuse-policy description suggestion
- Apply id-conflict-policy description suggestion
- Add visibility docs link to search-attribute description

Co-authored-by: Cursor <cursoragent@cursor.com>
Individual verb subcommands already have examples.

Co-authored-by: Cursor <cursoragent@cursor.com>
The summaries already say "Standalone", making this sentence redundant
in the descriptions.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…ityExecution)

Neither the Go nor Python SDK exposes this RPC, and the server
does not support it yet.
The examples were missing the required timeout, which is confusing since
either schedule-to-close-timeout or start-to-close-timeout must be
provided. Use different timeouts in each example to illustrate both options.
The server returns an empty non-error PollActivityExecution response
when the long-poll timeout (default 20s) expires before the activity
completes. This is the server's signal to re-issue the poll. Both
execute and result were making a single call and treating the nil
outcome as a terminal error.

Extract shared pollActivityOutcome that loops until an outcome arrives.
Verifies that pollActivityOutcome retries when the server returns an
empty PollActivityExecution response (nil outcome), rather than treating
it as a terminal error. Uses a gRPC interceptor to simulate the
long-poll timeout on the first poll, then return a result on the second.

Without the fix in 384741e, this test fails with "activity outcome not
available".
- pollActivityOutcome: use `for resp.GetOutcome() == nil` loop matching
  the SDK-Go's PollActivityResult pattern
- printActivityOutcome: use type switch on GetValue() matching SDK-Go
  style, with informative default case
- Enable standalone activities in dev server (history.enableChasm,
  activity.enableStandalone) with short long-poll timeout (2s)
- Rewrite long-poll retry test as proper integration test: activity
  sleeps 3s, exceeding the 2s long-poll timeout, forcing a retry
…mands

Workflow command tests are 100% integration tests (SharedServerSuite),
with no TestHelp_* unit tests. Conform to that precedent:

- Remove TestHelp_ActivitySubcommands, TestHelp_ActivityStartFlags,
  TestHelp_ActivityCompleteFlags, TestHelp_ActivityFailFlags
- Add integration tests: Start, Execute (success, failure, poll retry),
  Result, Describe, List, Count, Cancel, Terminate
- Add startStandaloneActivity helper for tests that need a running
  standalone activity
… count/delete wording

- Revert whitespace-only reformatting of `activity complete` description
- Apply "Output a count" for `workflow count` per review suggestion
- Apply "queries" for `workflow delete` per review suggestion
- Apply suggestion: parent description → "Perform operations on Activity Executions."
- Replace "Temporal Markdown" with "standard Markdown excluding images, HTML, and script tags"
The test was asserting map[foo:bar] which is Go's internal format.
The expected output is JSON: {"foo":"bar"}.
printActivityOutcome now JSON-marshals values so complex types
(maps, slices) render as JSON rather than Go's fmt representation.
Tests verify that `activity complete` and `activity fail` work
for standalone activities (without --workflow-id, using --run-id).
Replace hand-rolled PollActivityExecution long poll loop with the SDK's
client.GetActivityHandle().Get(), which provides proper gRPC long-poll
handling and retry semantics. This is analogous to how workflow update
uses UpdateWorkflow() + updateHandle.Get().
Move workflow command text improvements (summaries, descriptions) out of
this PR to keep it focused on standalone activity client functionality.
- cancel: direct verb instead of verbose "Request cancellation of"
- complete/fail: add "with a result" / "with an error" for specificity
- describe: "Show ... info" instead of parroting command name
- execute: "Start ... and wait for its result" to distinguish from start
- result: "Get the result" instead of verbose "Wait for and output"
- start: drop redundant "a new"
- terminate: "Forcefully end" to convey what terminate means
The command is 'temporal activity', so test names should use Activity
not StandaloneActivity. Also rename startStandaloneActivity helper
to startActivity. Complete/Fail tests that use --run-id get _ByRunId
suffix to avoid collision with pre-existing tests that use --workflow-id.
Add runId assertions to execute JSON tests. Add comments to tests
explaining why they lack JSON or text variants.
activityStatusShorthand and pendingActivityStateShorthand are only
called from printActivityDescription; define them as closures within
that function.
Make it clear the commands produce no output in any mode, not just JSON.
Combine TestActivity_Execute_Failure and _Failure_JSON into one test
with // Text and // JSON sections, matching the workflow test pattern.
Remove comment on RetriesOnEmptyPollResponse.
Remove verbose inline comments, simplify section headers to // Text,
// JSON, // Raw. Clarify cancel/terminate JSON behavior.
Describe the symptom without diagnosing the external root cause.
Reference the SQLite STRFTIME vs Go query converter format mismatch
and point to the investigation doc.
Equality queries on custom Datetime search attributes don't work on the
dev server, but range queries (> / <) do. Use > to get real test
coverage instead of skipping entirely.
@dandavison dandavison force-pushed the standalone-activity-client branch 4 times, most recently from efcf621 to 1195f42 Compare February 23, 2026 16:57
Expand TestActivity_SearchAttributes to cover Keyword, Double, Bool,
KeywordList, and Int. Each type is registered, set on a started
activity, and verified queryable. The Int case confirms the server
correctly decodes float64-metadata payloads via its schema.
@dandavison dandavison force-pushed the standalone-activity-client branch from 1195f42 to 73b6eaa Compare February 23, 2026 17:07
@dandavison dandavison force-pushed the standalone-activity-client branch from 4ccb9d7 to 14ea604 Compare February 23, 2026 20:20
printActivityFailure should only be responsible for printing, not for
signalling the activity outcome. This matches the workflow pattern
where fmt.Errorf("workflow failed") lives at the call site.
@dandavison dandavison marked this pull request as ready for review February 23, 2026 22:00
@dandavison dandavison requested review from a team as code owners February 23, 2026 22:00
//
// The per-request timeout matches the SDK's PollActivityResult implementation.
// Unlike the SDK, we retry at the application level on per-request timeout
// since we don't have the SDK's gRPC-level retry interceptor.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't like this. It doesn't need to block this PR, but it's an imperfect emulation of what the SDK is doing, which will diverge over time leading to more bugs. I am proposing that we introduce new poll methods in the Go SDK for Update and Activity that expose the raw proto responses to the CLI.

module github.com/temporalio/cli

go 1.26.0
go 1.25.5
Copy link
Member

Choose a reason for hiding this comment

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

Why did you downgrade Go?

go.temporal.io/sdk v1.39.1-0.20260205231726-1a609f101fd5
go.temporal.io/sdk/contrib/envconfig v0.1.0
go.temporal.io/server v1.30.0
go.temporal.io/server v1.31.0-150.0
Copy link
Member

Choose a reason for hiding this comment

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

Use 151 please.

go.temporal.io/sdk v1.38.0
github.com/temporalio/ui-server/v2 v2.45.0
go.temporal.io/api v1.62.1
go.temporal.io/sdk v1.39.1-0.20260205231726-1a609f101fd5
Copy link
Member

Choose a reason for hiding this comment

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

Assuming that this will be bumped to a stable release before merging.

short: r
description: |
Run ID.
If not set, targets the latest run.
Copy link
Member

Choose a reason for hiding this comment

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

Some commands the run ID is for the activity and for others for the workflow. We need to make sure we print out what the run ID refers to.

- name: activity-id
type: string
short: a
description: Activity ID.
Copy link
Member

Choose a reason for hiding this comment

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

Needs to be called out that this applies to either an ID of an activity within a workflow execution or a standalone activity execution ID.

required: true
- name: task-queue
type: string
description: Activity Task queue.
Copy link
Member

Choose a reason for hiding this comment

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

Why is Task capitalized and queue isn't?

- name: heartbeat-timeout
type: duration
description: |
Maximum time between successful Worker heartbeats.
Copy link
Member

Choose a reason for hiding this comment

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

Also worth calling out that when expired, the current activity attempt fails.

JSON values.
Can be passed multiple times.
See https://docs.temporal.io/visibility.
- name: headers
Copy link
Member

Choose a reason for hiding this comment

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

We don't expose this on workflow start or signal, we shouldn't for activity start either.

```

Activity code cannot see or respond to terminations. To
perform clean-up work, use `temporal activity cancel` instead.
Copy link
Member

Choose a reason for hiding this comment

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

This isn't accurate because if you activity is backing off it's the same as terminate. Cancelation only propagates to the activity if there's an active attempt and the activity is heartbeating.

- name: temporal activity start
summary: Start a new Standalone Activity (Experimental)
description: |
Start a new Standalone Activity. Outputs the Activity ID and
Copy link
Member

Choose a reason for hiding this comment

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

Why are the line lengths so short in this file?

--activity-id YourActivityId \
--type YourActivity \
--task-queue YourTaskQueue \
--schedule-to-close-timeout 5m \
Copy link
Member

Choose a reason for hiding this comment

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

We recommend setting start to close.

Suggested change
--schedule-to-close-timeout 5m \
--start-to-close-timeout 5m \

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.

2 participants