Skip to content

Comments

Standalone activity client#942

Closed
dandavison wants to merge 30 commits intotemporalio:mainfrom
dandavison:standalone-activity-client
Closed

Standalone activity client#942
dandavison wants to merge 30 commits intotemporalio:mainfrom
dandavison:standalone-activity-client

Conversation

@dandavison
Copy link
Contributor

What was changed

Why?

Checklist

  1. Closes

  2. How was this tested:

  1. Any docs updates needed?

dandavison and others added 7 commits February 8, 2026 05:00
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>
@dandavison dandavison force-pushed the standalone-activity-client branch from be04db2 to f1d4185 Compare February 9, 2026 02:37
- 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>
Copy link
Contributor Author

@dandavison dandavison left a comment

Choose a reason for hiding this comment

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

Addressed in new commits.

- activity terminate
- activity delete
- activity complete
- activity update-options
Copy link
Contributor Author

Choose a reason for hiding this comment

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

why delete this? The command still exists but only for workflow activities.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Restored the original description-header in 1b728de.

--type YourActivity \
--task-queue YourTaskQueue \
--input '{"some-key": "some-value"}'
```
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since this is an experimental API, I don't think it should be the example.

Copy link
Contributor Author

@dandavison dandavison Feb 16, 2026

Choose a reason for hiding this comment

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

🤖 Agreed. Changed to use activity complete as the primary example and added activity update-options as the second example in 1b728de.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But update-options isn't even implemented is it? I thought it wasn't in the grpc API.

Copy link
Contributor Author

@dandavison dandavison Feb 16, 2026

Choose a reason for hiding this comment

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

🤖 The update-options command is implemented and functional. It uses WorkflowService().UpdateActivityOptions() from the gRPC API. You can see the implementation at commands.activity.go:101-236 and the passing tests TestActivityOptionsUpdate_Accept and TestActivityOptionsUpdate_Partial in commands.activity_test.go.

Just ran the tests to confirm:

go test -run "TestSharedServerSuite/TestActivityOptionsUpdate" ./internal/temporalcli/ -v
--- PASS: TestSharedServerSuite (1.81s)
    --- PASS: TestSharedServerSuite/TestActivityOptionsUpdate_Accept (0.11s)
    --- PASS: TestSharedServerSuite/TestActivityOptionsUpdate_Partial (0.11s)

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 see. Yes, I was confusing this with the fact that it's not implemented for standalone activity. However, let's not just not add any example here; this is temporal activity after all, not any verb.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 Agreed — the temporal activity help now shows examples for activity complete and activity update-options (both workflow-activity commands). The standalone-only activity start example has been removed from this top-level help.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 Understood — the parent command is a grouping command, not a verb. Left as-is with the short description, no example.

defer cl.Close()

var detailPayloads *common.Payloads
var detailPayloads *commonpb.Payloads
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why did you change this?

Copy link
Contributor Author

@dandavison dandavison Feb 16, 2026

Choose a reason for hiding this comment

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

🤖 Reverted in 1b728de. The commonpb alias was unnecessary since there's no import conflict in this file — the default package name common works fine.

--type YourActivity \
--task-queue YourTaskQueue \
--input '{"some-key": "some-value"}'
```
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 see. Yes, I was confusing this with the fact that it's not implemented for standalone activity. However, let's not just not add any example here; this is temporal activity after all, not any verb.

Activity ID and include a JSON result for the returned value:
Complete an Activity, marking it as successfully finished.
Specify the Activity ID and include a JSON result for the
returned value:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This change is pure whitespace and is just adding noise to the diff.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 No trailing-whitespace-only changes in current file; any prior diff noise was addressed in 2ec455d.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The change here differs only in line-wrapping. Revert it please.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 Reverted in e899625. The complete description is back to the original line wrapping.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 Already reverted in e899625. The current description matches the original line-wrapping exactly:

Complete an Activity, marking it as successfully finished. Specify the
Activity ID and include a JSON result for the returned value:

```
temporal activity complete \
--activity-id YourActivityId \
--workflow-id YourWorkflowId \
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was here before, so we shouldn't be getting rid of it. In general, standalone activity is a new feature; workflow activity is the more commonly used by a long way.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 Addressed in 2de6d1e: restored “activity execution” in keywords.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 The "activity execution" keyword is present at its current position. No content was lost.

Comment on lines 252 to 254
description: |
Return a count of Activity Executions. Use `--query` to count
a subset:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
description: |
Return a count of Activity Executions. Use `--query` to count
a subset:
description: |
Return a count of Activity Executions. Use `--query` to filter
the activities to be counted.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 Addressed in 2de6d1e: activity count/list use “filter the activities to be counted”, “Search Attributes and queries.”, and “Standalone Activity Execution” / “only supported for Standalone Activity Execution” wording.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 Applied in 2ec455d. Now reads "Return a count of Standalone Activity Executions. Use `--query` to filter the activities to be counted."

Keys must be identifiers, and values must be JSON values.
May be passed multiple times to set multiple Temporal headers.
Note: These are workflow headers, not gRPC headers.
Note: These are workflow headers, not gRPC headers.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

don't make changes in this PR that are merely trailing whitespace

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 No trailing-whitespace-only changes in current file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 Reverted all trailing-whitespace-only changes in c96bb91.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 Trailing whitespace changes removed in 2ec455d.

Comment on lines 5026 to 5027
Re-use policy for the Activity ID when a previous
Execution has completed.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
Re-use policy for the Activity ID when a previous
Execution has completed.
Policy for handling activity start when an Activity
with the same ID exists and has completed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 Applied suggestion in c96bb91.

Comment on lines 5035 to 5037
Policy for handling a conflict when starting an
Activity with a duplicate Activity ID of a running
Execution.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
Policy for handling a conflict when starting an
Activity with a duplicate Activity ID of a running
Execution.
Policy for handling activity start when an
Activity with the same ID is currently running.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 Applied suggestion in c96bb91.

experimental: true
description: |
Static Activity summary for human consumption in UIs.
Uses Temporal Markdown formatting.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can you find whether "Temporal Markdown" is documented somewhere? Use your temporal-docs MCP tool and/or the documentation repo.

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 searched the api proto repo, sdk-go, sdk-java, documentation repo, and UI repo. "Temporal Markdown" is used as a term in Go SDK and Java SDK doc comments (e.g. in single-line Temporal Markdown format) and in the generated CLI docs, but there is no dedicated documentation page or formal specification for it. The term appears to refer to standard Markdown as rendered by the Temporal UI.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Please change "Temporal Markdown" to "standard Markdown excluding images, HTML, and script tags" everywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 Applied in a661e6f. All 4 occurrences of "Temporal Markdown" changed to "standard Markdown excluding images, HTML, and script tags" (2 in workflow start options, 2 in activity start options).

- 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>
@dandavison
Copy link
Contributor Author

🤖 Addressed all review feedback in commit 2ec455d:

  • Activity count/list/result: Use "Standalone Activity Execution" and "only supported for Standalone Activity Execution".
  • Activity complete/fail: Restored --workflow-id YourWorkflowId in examples.
  • Keywords: Restored "activity execution".
  • Activity count/list: "Search Attributes and queries"; "filter the activities to be counted".
  • Workflow execute: Added backslash after execute in example.
  • Workflow start: Reverted to main (Initiate, Returns Workflow- and Run-IDs).
  • Workflow delete: Reverted to main (Remove, Executions typo); removed TODO.
  • Workflow result: Removed TODO (output verb already used).
  • Workflow count/list: Example queries and "queries." wording; removed TODOs.
  • Trailing whitespace: Restored in worker deployment and task-queue docs to avoid diff noise.

2810472090 (server repo test coverage): The server repo is not in this workspace; suggest verifying tests/standalone_activity_test.go in the server repo separately.

dandavison and others added 4 commits February 15, 2026 23:22
- 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>
summary: List Standalone Activity Executions (Experimental)
description: |
List Standalone Activity Executions. Use `--query` to filter results.
Only supported for Standalone Activity Execution.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
Only supported for Standalone Activity Execution.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 Applied: removed redundant sentence from all 9 standalone activity command descriptions in ed5405a.

The summaries already say "Standalone", making this sentence redundant
in the descriptions.

Co-authored-by: Cursor <cursoragent@cursor.com>
description: |
Update the options of a running Activity that were passed into it from
a Workflow. Updates are incremental, only changing the specified options.
Not supported for standalone Activities.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Capitalization should be Standalone Activit{y,ies} throughout this file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 Applied in 69f8335: capitalized "Standalone Activities" in all 6 remaining lowercase instances.

dandavison and others added 2 commits February 16, 2026 00:17
Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Contributor Author

@dandavison dandavison left a comment

Choose a reason for hiding this comment

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

🤖 Overall Review

This PR adds 8 new temporal activity subcommands (start, execute, describe, list, count, cancel, terminate, result) for standalone activities, and modifies complete/fail to make --workflow-id optional. The structure follows existing workflow command patterns well, and the code is clean and well-organized.

What it's trying to do is right. The mapping of gRPC RPCs to CLI commands mirrors the temporal workflow pattern closely, which is the correct approach.

Issues to address:

  1. delete command is missing. The plan calls for 9 new commands; only 8 are implemented. The API has DeleteActivityExecution. Intentional deferral, or oversight?

  2. Zero-duration timeouts sent for unset flags — see inline comment on buildStartActivityRequest.

  3. No tests for modified complete/fail — These commands changed their option structure. There should be tests for both the standalone path (omitting --workflow-id) and regression tests for the workflow-scoped path.

  4. Result formatting uses %v — see inline comment on printActivityOutcome.

  5. Unrelated workflow command doc edits — The PR modifies descriptions for workflow cancel, count, delete, describe, execute, result, start, and terminate. These should be in a separate commit to keep the diff focused.

  6. .task/, AGENTS.md, CLAUDE.md symlinks — Development scaffolding; remove before merge.

  7. Missing --name alias for --type — see inline comment on activity-start option set.

if err := converter.GetDefaultDataConverter().FromPayload(payload, &value); err != nil {
cctx.Printer.Printlnf("Result: <failed converting: %v>", err)
} else {
cctx.Printer.Printlnf("Result: %v", value)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 %v produces Go-native formatting (e.g. map[foo:bar]). For a CLI tool, users would expect JSON. Consider using json.Marshal(value) or the converter's string representation for consistency with how workflow execute prints results.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Give me a CLI command or commands that will repro the problem you're describing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 Fixed in 15c1f96. The issue: printActivityOutcome used %v which produces Go's internal format for complex types. Any activity execute or activity result where the activity returns a map will show map[foo:bar] instead of {"foo":"bar"}.

Repro (assuming a running activity that returns a map):

temporal activity execute --activity-id test --type MapReturner --task-queue q --start-to-close-timeout 30s
# Was: Result: map[foo:bar]
# Now: Result: {"foo":"bar"}

The test TestStandaloneActivity_Execute_Success was asserting map[foo:bar]. Fixed in 67942e7 to assert {"foo":"bar"}, then fixed the implementation in 15c1f96 to JSON-marshal the value.

for resp.GetOutcome() == nil {
rpcCtx, cancel := context.WithTimeout(cctx, longPollPerRPCTimeout)
var err error
resp, err = svc.PollActivityExecution(rpcCtx, req)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 If the per-RPC rpcCtx deadline fires (e.g. network hiccup causing the 70s timeout), the code falls through to return the error permanently. The parent context (cctx) won't have an error, so the check on line 663 won't save it. Consider retrying on context.DeadlineExceeded when cctx.Err() == nil.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Please compare the sdk-go long-poll loop implementation for update and activity handle.Get(). Does it have the problem your describing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 sdk-go PollActivityResult (internal_activity_client.go:642) uses the same pattern:

for resp.GetOutcome() == nil {
    grpcCtx, cancel := newGRPCContext(ctx, grpcLongPoll(true), grpcTimeout(pollActivityTimeout), ...)
    resp, err = w.client.WorkflowService().PollActivityExecution(grpcCtx, request)
    cancel()
    if err != nil {
        return nil, err
    }
}

And PollWorkflowUpdate (internal_workflow_client.go:2546) does the same with an additional ctx.Err() check after errors.

The CLI's loop is structurally identical. One difference: the CLI checks cctx.Err() (parent context) before returning errors, which correctly distinguishes user cancellation from RPC errors. sdk-go's activity poller returns the raw error without this check. So the CLI's error handling is actually slightly more careful than sdk-go's activity polling.

Neither has the problem I described in my review comment — the per-RPC context timeout is independent of the parent context, and cancel() is called promptly.

- name: type
type: string
description: Activity Type name.
required: true
Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 The analogous shared-workflow-start-options defines aliases: [name] for its --type flag (line 4653). For consistency, this type field should also have aliases: [name].

.task/plan.md Outdated
@@ -0,0 +1,620 @@
---
Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 The .task/ directory, AGENTS.md, and CLAUDE.md symlinks are development scaffolding and should be removed before merge.

@dandavison dandavison force-pushed the standalone-activity-client branch from 9311c33 to a1ae624 Compare February 19, 2026 23:29
…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
@dandavison dandavison force-pushed the standalone-activity-client branch from a1ae624 to bf0388e Compare February 20, 2026 00:38
… 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
.gitattributes Outdated
@@ -0,0 +1 @@
.task/ linguist-generated
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 was hoping that this would hide or fold the .task files in the PR, but it doesn't seem to. Do you know why my expectation wasn't met?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 The linguist-generated attribute makes GitHub collapse diffs in PR views, but the pattern .task/ matches the directory entry itself, not files within it. To match files, the pattern should be .task/** linguist-generated. However, even with the correct pattern, linguist-generated only affects the diff view (collapsing the file), not the file tree sidebar.

for resp.GetOutcome() == nil {
rpcCtx, cancel := context.WithTimeout(cctx, longPollPerRPCTimeout)
var err error
resp, err = svc.PollActivityExecution(rpcCtx, req)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Please compare the sdk-go long-poll loop implementation for update and activity handle.Get(). Does it have the problem your describing?


func (s *SharedServerSuite) TestStandaloneActivity_Execute_RetriesOnEmptyPollResponse() {
// Activity sleeps longer than the server's activity.longPollTimeout (2s),
// forcing at least one empty poll response before the result arrives.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Show me evidence that 2s is the relevant timeout here please.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 The comment claims "the server's activity.longPollTimeout (2s)" but the server default is 20 seconds, not 2. From chasm/lib/activity/config.go:

LongPollTimeout = dynamicconfig.NewNamespaceDurationSetting(
    "activity.longPollTimeout",
    20*time.Second,
    `Timeout for activity long-poll requests.`,
)

With 20s default, an activity sleeping 3s would complete well within the first poll — the test wouldn't actually exercise the retry path unless the dev server overrides this to a shorter value. I cannot find evidence that the dev server sets it to 2s. The comment appears to be incorrect.

--workflow-id=YourWorkflowId \
--result='{"YourResultKey": "YourResultValue"}'
```
Complete, fail, or update an Activity's state or options.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
Complete, fail, or update an Activity's state or options.
Perform operations on Activity Executions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 Applied in a661e6f. Description now reads "Perform operations on Activity Executions."

Activity ID and include a JSON result for the returned value:
Complete an Activity, marking it as successfully finished.
Specify the Activity ID and include a JSON result for the
returned value:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The change here differs only in line-wrapping. Revert it please.

summary: Count Workflow Executions
description: |
Show a count of Workflow Executions, regardless of execution state (running,
Return a count of Workflow Executions, regardless of execution state (running,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The diff in this comment is making a suggestion; the GitHub UI makes it possible to accept them by clicking on a UI element. Please accept the suggestion, either by using an appropriate API (if GitHub offers such a thing) or manually by making a commit and pushing it.

experimental: true
description: |
Static Activity summary for human consumption in UIs.
Uses Temporal Markdown formatting.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Please change "Temporal Markdown" to "standard Markdown excluding images, HTML, and script tags" everywhere.

- 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().
@dandavison dandavison force-pushed the standalone-activity-client branch from cc0409f to 9d569ae Compare February 20, 2026 02:18
Copy link
Contributor Author

@dandavison dandavison left a comment

Choose a reason for hiding this comment

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

🤖 Review: Standalone Activity Commands vs Workflow Counterparts

I compared every new standalone activity function and test against its most similar workflow counterpart. The overarching finding is that the workflow implementation uses the Go SDK's high-level client API (cl.ExecuteWorkflow, cl.CancelWorkflow, cl.TerminateWorkflow, cl.ListWorkflow, etc.), while the standalone activity implementation bypasses the SDK almost entirely, going directly to cl.WorkflowService() proto calls for start, describe, list, count, cancel, and terminate. Only getActivityResult (via cl.GetActivityHandle().Get()) uses the SDK.

The Go SDK at this version provides a comprehensive standalone activity API:

  • cl.ExecuteActivity(ctx, client.StartActivityOptions{...}, activityType, args...)ActivityHandle
  • ActivityHandle.Get(), .Describe(), .Cancel(), .Terminate()
  • cl.ListActivities(ctx, options), cl.CountActivities(ctx, options)

Using the SDK would bring search attribute/header/payload serialization consistency, interceptor support, and reduced maintenance surface — mirroring how workflows are implemented in this CLI.

See inline comments for details.

Copy link
Contributor Author

@dandavison dandavison left a comment

Choose a reason for hiding this comment

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

🤖 Review: Standalone Activity Commands vs Workflow Counterparts

I compared every new standalone activity function and test against its most similar workflow counterpart. The overarching finding is that the workflow implementation uses the Go SDK's high-level client API (cl.ExecuteWorkflow, cl.CancelWorkflow, cl.TerminateWorkflow, cl.ListWorkflow, etc.), while the standalone activity implementation bypasses the SDK almost entirely, going directly to cl.WorkflowService() proto calls for start, describe, list, count, cancel, and terminate. Only getActivityResult (via cl.GetActivityHandle().Get()) uses the SDK.

The Go SDK at this version provides a comprehensive standalone activity API:

  • cl.ExecuteActivity(ctx, client.StartActivityOptions{...}, activityType, args...)ActivityHandle
  • ActivityHandle.Get(), .Describe(), .Cancel(), .Terminate()
  • cl.ListActivities(ctx, options), cl.CountActivities(ctx, options)

Using the SDK would bring search attribute/header/payload serialization consistency, interceptor support, and reduced maintenance surface — mirroring how workflows are implemented in this CLI.

See inline comments for details.

return getActivityResult(cctx, cl, c.ActivityId, c.RunId)
}

func buildStartActivityRequest(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 Major: hand-rolling proto request instead of using Go SDK

The workflow counterpart uses the SDK: buildStartOptions() builds client.StartWorkflowOptions, then cl.ExecuteWorkflow() handles the proto construction, including search attributes, headers, user metadata, priority, and retry policy serialization.

Here, the entire StartActivityExecutionRequest proto is hand-built (~110 lines). The Go SDK provides client.StartActivityOptions with fields for all of these (ID, TaskQueue, timeouts, policies, RetryPolicy, TypedSearchAttributes, Summary, Details, Priority), and cl.ExecuteActivity(ctx, opts, activityType, args...) returns an ActivityHandle.

Specific issues with the hand-rolled approach:

  1. Search attributes (line 157): uses encodeMapToPayloads to build raw common.SearchAttributes. The workflow version passes map[string]any to the SDK's StartWorkflowOptions.SearchAttributes, letting the SDK serialize. Different code path, potential for subtle differences.

  2. UserMetadata (line 181): manually creates payloads with json/plain encoding and %q formatting. The workflow version just sets StaticSummary/StaticDetails as strings on StartWorkflowOptions, letting the SDK handle serialization.

  3. Headers (line 169): builds common.Header{Fields: ...} directly on the proto. The workflow version uses contextWithHeaders() + SDK interceptor pipeline. Since this call bypasses the SDK, headers won't flow through SDK interceptors.

One consideration: the SDK's ActivityHandle exposes GetID()/GetRunID() but not Started from the proto response. If Started is essential, you could call cl.ExecuteActivity() and always report started=true (since a non-error return implies start), or access it via the raw response if the SDK supports it.

See buildStartOptions in commands.workflow_exec.go:568 and startWorkflow at line 518 for the workflow pattern.

}
)

func (c *TemporalActivityStartCommand) run(cctx *CommandContext, args []string) error {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 Start output less informative than workflow counterpart

The workflow startWorkflow (commands.workflow_exec.go:518) prints:

Running execution:
  WorkflowId  my-id1
  RunId       <uuid>
  Type        DevWorkflow
  Namespace   default
  TaskQueue   my-queue

The activity start prints:

  ActivityId  start-test
  RunId       <uuid>
  Started     true

Missing: Type, TaskQueue, and Namespace. The Started field is unique to activities, which is good. But consider also printing the type, task queue, and namespace for parity with the workflow experience.

}, printer.StructuredOptions{})
}

func (c *TemporalActivityExecuteCommand) run(cctx *CommandContext, args []string) error {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 Execute: appropriate simplification vs workflow counterpart

The workflow execute (commands.workflow_exec.go:42) is much more elaborate: it shows progress (streaming history events), handles continue-as-new, prints colored status, and has separate JSON/text result formatting.

The activity execute is simpler: start + get result. This is appropriate since standalone activities don't have event history in the same way. The simplicity is good.

However, if the activity fails, the error message "activity failed: " doesn't give the structured output that the workflow version does (e.g. status, failure message, etc.). Consider whether a failed activity should still produce structured output before returning an error, as the workflow version does.

return req, nil
}

func getActivityResult(cctx *CommandContext, cl client.Client, activityID, runID string) error {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 Result output less informative than workflow counterpart

Compare with the workflow's printTextResult (commands.workflow_exec.go:470) and workflowJSONResult (commands.workflow_exec.go:390):

  • Workflow text output: colored Status (COMPLETED/FAILED/TIMEOUT/CANCELED), RunTime, Result with encoding, and Failure details — all via cctx.Printer.PrintStructured.

  • Activity text output: just Result: %s with raw JSON bytes.

  • Workflow JSON output: rich struct with workflowId, runId, type, namespace, status, closeEvent, result, and optional history.

  • Activity JSON output: {"result": ...} — minimal.

For consistency with the workflow experience, consider printing a status and using PrintStructured for text output.

Also note: the workflow result commands handle non-completion by returning fmt.Errorf("workflow failed"), reporting both the print error and the status. The activity version returns the error from handle.Get(), which is fine semantically, but the user experience is different.

return nil
}

func (c *TemporalActivityDescribeCommand) run(cctx *CommandContext, args []string) error {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 Use SDK ActivityHandle.Describe() instead of raw proto

The SDK provides ActivityHandle.Describe(ctx, client.DescribeActivityOptions{}) which returns *client.ActivityExecutionDescription. This is consistent with how workflow describe uses the SDK's typed responses.

The raw proto approach here works, but it bypasses the SDK's response transformation layer (the SDK wraps the proto into ClientActivityExecutionDescription which includes typed search attributes, etc.).

s.Equal(true, jsonOut["started"])
}

func (s *SharedServerSuite) TestStandaloneActivity_Execute_Success() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 No JSON output test — compare with TestWorkflow_Execute_SimpleSuccess (commands.workflow_exec_test.go:134)

The workflow execute test checks both text and JSON output:

// JSON
res = s.Execute("workflow", "execute", "-o", "json", ...)
var jsonOut map[string]any
s.NoError(json.Unmarshal(res.Stdout.Bytes(), &jsonOut))
s.Equal("my-id2", jsonOut["workflowId"])
s.Equal("COMPLETED", jsonOut["status"])
s.NotNil(jsonOut["closeEvent"])
s.Equal(map[string]any{"foo": "bar"}, jsonOut["result"])

This test only checks text output. Consider adding a JSON variant.

Also, the workflow test verifies the input was received (s.Equal([]any{"val1", "val2"}, s.Worker().DevWorkflowLastInput())). Consider passing input and verifying the activity received it.

s.Contains(res.Stdout.String(), "result-value")
}

func (s *SharedServerSuite) TestStandaloneActivity_Describe() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 Minimal describe test — compare with workflow describe tests

The workflow describe has extensive test coverage: TestWorkflow_Describe_ActivityFailing, TestWorkflow_Describe_Completed, TestWorkflow_Describe_Versioned, TestWorkflow_Describe_NotDecodable, TestWorkflow_Describe_ResetPoints, etc. (commands.workflow_view_test.go:28+).

This test only checks that ActivityId and type name appear in the output. Consider:

  • Testing --raw flag (the describe command has a c.Raw code path)
  • Testing JSON output (-o json)
  • Verifying more fields (status, task queue, timeouts)

}, 5*time.Second, 200*time.Millisecond)
}

func (s *SharedServerSuite) TestStandaloneActivity_Count() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 No JSON output test — compare with TestWorkflow_Count (commands.workflow_view_test.go:465)

The workflow count test verifies both text and JSON output, including checking the response structure with group values. This test only checks that "Total:" appears in the text output.

Consider adding a JSON variant and potentially testing GROUP BY behavior to match the workflow count test's coverage.

}, 5*time.Second, 200*time.Millisecond)
}

func (s *SharedServerSuite) TestStandaloneActivity_Cancel() {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 Cancel/terminate tests don't verify actual state change

Both TestStandaloneActivity_Cancel and TestStandaloneActivity_Terminate check that the command succeeds and prints the expected message, but don't verify the activity's state actually changed.

Compare with how the pre-existing TestActivityPauseUnpause (line 204) uses s.Eventually to confirm the activity is actually paused/unpaused by calling DescribeWorkflowExecution and checking PendingActivities[0].Paused.

For standalone activities, consider verifying via DescribeActivityExecution or handle.Get() that cancel/terminate took effect (e.g. handle.Get() returns a cancellation/termination error).

@@ -233,6 +233,9 @@ func (s *SharedServerSuite) SetupSuite() {
"frontend.namespaceRPS.visibility": 10000,
// Disable DescribeTaskQueue cache.
"frontend.activityAPIsEnabled": true,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤖 Config additions look correct

The three dynamic config settings (frontend.activityAPIsEnabled, history.enableChasm, activity.enableStandalone) are required for standalone activity support. The activity.longPollTimeout of 2s is used by the TestStandaloneActivity_Execute_RetriesOnEmptyPollResponse test which sleeps 3s to force an empty poll.

Minor: the comment // Disable DescribeTaskQueue cache. from the previous line appears to have been displaced — it was likely for the DescribeTaskQueue TTL line below, not for these new config lines.

@dandavison dandavison closed this Feb 21, 2026
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.

1 participant